This episode tidies up the C development workflow: a comparison of the GCC, Clang, and MSVC toolchains, integrating static analysis, sanitizer, and linting into a workflow, build automation with make, CMake, and CI pipelines, as well as binary packaging and the basics of cross-compilation.

A correct program isn't enough; it must be buildable, testable, and releasable repeatedly in a consistent way. Episode 19 discusses the modern tooling that transforms C development from a manual process into an automated pipeline.
We'll compare the three main toolchains — GCC, Clang, and MSVC — then assemble a workflow that combines static analysis, linting, and sanitizers on every change. Build automation ties all these steps together, ending with a CI pipeline that runs automatically on a server.
Finally, cross-compilation lets you build binaries for one platform while working on another — a skill used directly in episodes 20 and 21.
-fsyntax-only static analysis, and good tooling integration.All three understand the same C standard, but each has different extensions and message behavior. Building with more than one compiler finds bugs that only appear in one of them:
gcc -std=c17 -Wall -Wextra main.c -o app-gcc
clang -std=c17 -Wall -Wextra main.c -o app-clangThe two commands above compile the same code with GCC and Clang. Differences between their warnings often reveal hidden dependencies on specific behavior.
A healthy workflow runs layered checks on every change: linting for style, static analysis for potential bugs, and sanitizers for runtime verification. All these tools were introduced in episode 11; now you chain them into one pipeline:
clang-format --dry-run --Werror main.c
cppcheck --enable=all main.c
clang-tidy main.c -- -std=c17
gcc -fsanitize=address,undefined -g main.c -o app
./appThe sequence above runs a format check, cppcheck, clang-tidy, then a sanitizer build. Running this pipeline on every commit ensures regressions are caught before reaching production.
Sanitizers add overhead, so they're only active during development. Use two build configurations: a debug mode with sanitizers and full symbols, and a release mode with -O2 optimization and no sanitizers. CMake makes this easy through build types.
CMake from episode 10 is now refined into a multi-configuration project:
cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug
cmake --build build-debug
cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release
cmake --build build-release-DCMAKE_BUILD_TYPE=Debug enables debug options, while Release turns on optimization. The two build directories are separate so configurations don't overwrite each other.
The entire check pipeline can run automatically on every push via GitHub Actions:
name: build-c
on: push
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt install -y clang cppcheck
- run: cppcheck --enable=all src
- run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
- run: cmake --build build
- run: ./build/testsThe on: push workflow runs the steps on every push. CI ensures nothing gets through without passing the same checks on a clean machine, not just on your local computer.
make install copies the binary and headers into the system's standard directories. With CMake, the install target is configured in CMakeLists.txt. For distribution, look at package managers like apt that wrap binaries, libraries, and metadata into a single package users can install.
Cross-compilation builds binaries for another architecture without running them. Install the target toolchain and invoke the cross compiler:
sudo apt install -y gcc-aarch64-linux-gnu
aarch64-linux-gnu-gcc -std=c17 -static main.c -o app-arm64
file app-arm64The command aarch64-linux-gnu-gcc compiles for 64-bit ARM CPUs from an x86_64 machine. -static embeds all libraries so the binary is self-contained. file app-arm64 confirms the target architecture. This is the foundation for producing multi-architecture binaries — a common practice in containers and embedded.
Tip
Building on a repeatable foundation is the secret to quality: the commitment to always run the same pipeline makes quality independent of a programmer's mood or memory.
Key takeaways:
In the next episode 20 we will discuss cross-platform development — writing portable code with the latest C standard, conditional compilation for Windows, Linux, and macOS, using cross-platform libraries and build portability, up to testing on many architectures and toolchains.