Setup#

C has no standard package manager, no “init a project” command, and three competing build systems. The operator’s setup work is picking a compiler, a build system, and a debugger, then writing a Makefile or CMakeLists.txt once and reusing it.

Install#

1. Compiler.

  • gcc, the GNU Compiler Collection. The Linux default.

  • clang, the LLVM frontend. Better error messages and diagnostics; the default on macOS and increasingly on Linux.

$ sudo apt install build-essential gdb clang   # Debian / Ubuntu
$ sudo dnf groupinstall "Development Tools" -y  # Fedora / RHEL
$ xcode-select --install                        # macOS

2. Build system.

  • make for small and embedded work.

  • CMake for anything cross-platform or with dependencies.

  • Meson + Ninja for newer projects (faster than CMake).

3. Debugger.

  • gdb on Linux.

  • lldb on macOS (and increasingly Linux).

4. Static analyzer and linter.

5. Verify.

$ gcc --version
$ clang --version
$ gdb --version
$ make --version
$ cmake --version

Setup project#

1. Lay out the source.

my-tool/
├── Makefile                 # or CMakeLists.txt
├── README.md
├── include/
│   └── my_tool.h
├── src/
│   ├── my_tool.c
│   └── main.c
└── tests/
    └── test_my_tool.c

2. Minimal Makefile.

CC      ?= cc
CFLAGS  ?= -std=c11 -Wall -Wextra -Wpedantic -Werror -O2 -g
LDFLAGS ?=
INCLUDES = -Iinclude

SRC = $(wildcard src/*.c)
OBJ = $(SRC:.c=.o)
BIN = my-tool

$(BIN): $(OBJ)
^I$(CC) $(LDFLAGS) -o $@ $^

%.o: %.c
^I$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<

clean:
^Irm -f $(OBJ) $(BIN)

.PHONY: clean

(Make uses literal tabs; the ^I markers above are stand-ins.)

3. Minimal CMakeLists.txt.

cmake_minimum_required(VERSION 3.20)
project(my_tool C)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

add_compile_options(-Wall -Wextra -Wpedantic -O2 -g)

add_executable(my-tool src/main.c src/my_tool.c)
target_include_directories(my-tool PUBLIC include)

4. Build.

$ make                              # Make path
$ cmake -S . -B build && cmake --build build      # CMake path

Strict#

Compile flags you want on every C project.

Flag

Effect

-Wall -Wextra -Wpedantic

All routine warnings.

-Werror

Treat warnings as errors.

-std=c11 or -std=c17

Pin the standard.

-fno-common

Force-multi-defined globals into errors (default in C23).

-D_FORTIFY_SOURCE=2 -O2

libc-level overflow checks at runtime.

-fstack-protector-strong

Stack canaries.

-fsanitize=address,undefined

Address and UB sanitisers for the operator’s debug build.

Common Tasks#

Generate ``compile_commands.json`` for IDE / lint tooling.

$ cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
$ ln -s build/compile_commands.json .

Run AddressSanitizer.

$ clang -fsanitize=address,undefined -g -O1 -o tool src/*.c
$ ./tool

Run static analysis.

$ cppcheck --enable=all --inconclusive src/
$ clang-tidy src/*.c -- -Iinclude
$ clang --analyze src/*.c

Open a core dump in gdb.

$ gdb --batch -ex 'thread apply all bt' tool core.1234

Cross-compile to ARM.

$ sudo apt install gcc-aarch64-linux-gnu
$ aarch64-linux-gnu-gcc -static -o tool-arm64 src/*.c

References#

  • Tooling for gdb, valgrind, perf, sanitisers.

  • man gcc, man clang, man make, man gdb.

  • CMake docs

  • Cppreference, the community C and C++ reference.

  • The C Programming Language, Kernighan and Ritchie (K&R).