C/C++ Arena

Compilers, linking and CMake

On the Arena site one file became one program in a single click. Real projects have hundreds of files, several libraries and a build system that ties them together. In this project you'll see every stage a C or C++ build goes through, then describe a small project in CMake, the build tool most C and C++ companies use.

You'll practice: the four build stages, object files and the linker, headers vs. source files, static libraries, reading linker errors, and CMake targets.

Background

From source to program

gcc main.c -o main hides four separate steps:

Stage Command to stop there Output
1. Preprocess: expand #include and macros gcc -E main.c one big C file
2. Compile: C to assembly gcc -S main.c main.s
3. Assemble: assembly to machine code gcc -c main.c main.o (an object file)
4. Link: join objects and libraries gcc main.o util.o -o main the program

Each .c file is compiled on its own into an object file. It only needs declarations (from headers) of what it calls. The linker then connects every call to the one definition somewhere in the objects and libraries. That's why:

Try it in the terminal from this folder:

gcc -Iinclude -c src/strutil.c -o /tmp/strutil.o      # compile only
nm /tmp/strutil.o                                     # list its symbols: T = defined here
gcc -Iinclude src/wordfreq.c -o /tmp/wf               # fails: undefined reference
ar rcs /tmp/libstrutil.a /tmp/strutil.o               # bundle objects into a static library
gcc -Iinclude src/wordfreq.c /tmp/libstrutil.a -o /tmp/wf   # links now

A static library (.a) is just an archive of object files copied into the program at link time. A shared library (.so, .dll) is loaded when the program starts, so several programs share one copy and it can be updated separately.

CMake in five minutes

Typing those commands doesn't scale. CMake reads a CMakeLists.txt and generates the real build (Ninja or Make) for you. Modern CMake is all about targets:

add_library(geometry STATIC src/shapes.c)              # a library target
target_include_directories(geometry PUBLIC include)    # its headers
add_executable(app src/main.c)                         # a program target
target_link_libraries(app PRIVATE geometry)            # app uses geometry

Building is always the same two commands:

cmake -S . -B build -G Ninja      # configure: generate the build in build/
cmake --build build               # build everything that changed
ctest --test-dir build            # run the tests

The shared file cmake/arena.cmake (included at the top) already turns on C17/C++20, -Wall -Wextra -Wpedantic -Werror, optional sanitizers and GoogleTest.

Your tasks

  1. Implement the library. Fill in the four functions in src/strutil.c. The comments in include/strutil.h are the specification. Use isspace and tolower from <ctype.h>, and cast characters to unsigned char before passing them (passing a negative char is undefined behavior).
  2. Describe the build. Complete TODO 1 and TODO 2 in CMakeLists.txt.
  3. Build and test until everything is green:
    cmake -S . -B build -G Ninja && cmake --build build && ctest --test-dir build --output-on-failure
    ./build/wordfreq tests/sample.txt
    
  4. Grade it exactly like GitHub will: bash ../../tools/grade.sh 01-toolchain
  5. Commit and push. The Grade workflow runs on GitHub; open the Actions tab to see the result.

Done when

Hints

Stretch goals