# The C stack we would set up today

> Source: https://learn-c.net/tools/
> Part of Learn C, free to read.

The single most important thing to understand about C tooling in 2026: **the free tools are extremely good, and most projects use none of them.** Everything below is either bundled with your compiler or a one-line install.

:::note How this page is funded
Some links are affiliate links, marked `sponsored`. We earn a commission if you buy through one, at no cost to you. Every tool in the first three sections is free.
:::

## Sanitizers — start here

```bash
clang -g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer main.c -o app
ASAN_OPTIONS=detect_leaks=1 UBSAN_OPTIONS=print_stacktrace=1 ./app
```

AddressSanitizer finds use-after-free, buffer overflow, double free and leaks. UndefinedBehaviorSanitizer finds signed overflow, bad shifts, misaligned access and null dereference. Both give you a precise stack trace at the point of the bug rather than a crash somewhere else later.

The cost is roughly 2x runtime. That is irrelevant during development and it is the highest return on any configuration change available in this language. ThreadSanitizer (`-fsanitize=thread`, separately) covers data races.

Make the sanitized build your default `make debug`. See [the C failure modes](/review/failure-modes/) for exactly which bugs each one catches.

## Compiler warnings

```makefile
WARN = -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-compare \
       -Wformat=2 -Wformat-security -Wnull-dereference -Wstrict-prototypes \
       -Wwrite-strings -Wcast-qual
HARDEN = -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fstack-clash-protection
```

`-Wconversion` is noisy on existing code and finds real integer bugs on new code. Turn it on for new files if the volume is too high to fix at once.

## Static analysis

```bash
gcc -fanalyzer -c src/*.c          # GCC's built-in, very good on malloc paths
clang-tidy src/*.c --              # broad, configurable
cppcheck --enable=all src/         # different blind spots to the other two
```

These are complementary, not redundant — run more than one. `-fanalyzer` in particular is excellent at the unchecked-`malloc` class of bug that generated C produces most.

## Fuzzing

If your code parses bytes from outside — a file format, a protocol, a config — fuzz it. Fifteen lines, and it finds inputs nobody would think to write a test for.

```c
int LLVMFuzzerTestOneInput(const uint8_t *d, size_t n) { parse(d, n); return 0; }
```

```bash
clang -g -O1 -fsanitize=fuzzer,address,undefined fuzz.c src/parser.c -o fuzz
./fuzz -max_total_time=300 corpus/
```

Five minutes of this on a generated parser is worth an hour of reading it.

## Build system

**Make** for anything small. It is universal, everyone can read it, and a 30-line Makefile is more maintainable than a 30-line anything else.

**CMake** once you need cross-platform builds, multiple targets or dependency management — not because it is pleasant, but because it is what the ecosystem uses.

**Meson** if you get to choose freely. Faster, much more readable, and it works well.

Add `bear` to generate `compile_commands.json` so `clangd` and `clang-tidy` understand your project:

```bash
bear -- make
```

## Editor

`clangd` gives you real completion, go-to-definition and inline diagnostics in any editor that speaks LSP. This is free and it is most of what an IDE was for.

:::promo jetbrains
:::

CLion's case is the debugger and the memory view. When generated C is subtly wrong, watching the actual bytes beats reading the code.

## Testing

`Unity` or `Criterion` for unit tests; both are small and easy to vendor. The important thing is not which — it is that there is a `make test` an agent can run in under five seconds.

## Learning

:::promo manning
:::

C is a language where the books genuinely beat the tutorials, because the things that matter — memory model, undefined behaviour, the standard's actual guarantees — need more space than a web page gives them.

:::promo educative
:::

For filling a specific gap quickly rather than working through a whole book.

## Where to run it

:::promo hetzner
:::

For a build box, a CI runner or anything compute-heavy, dedicated hardware at Hetzner prices is hard to argue with.

## What to skip

- **Rolling your own string library.** Use `snprintf` and bounded functions. Every hand-rolled string API has the same bug in it.
- **`-Ofast` or `-ffast-math`** unless you know precisely which guarantees you are giving up.
- **Ignoring warnings.** `-Werror` in CI, not locally — locally it interrupts flow; in CI it holds the line.
- **A custom allocator, early.** Profile first. It is almost never the problem.

## Common questions

### Do sanitizers slow things down too much to use?

About 2x for ASan, which is irrelevant during development and in CI. You do not ship a sanitized binary; you develop with one. The trade is finding a heap overflow at your desk instead of in production.

### ASan or Valgrind?

ASan is much faster and gives better reports, so it is the default. Valgrind still finds things ASan does not — uninitialised memory reads in particular, where MemorySanitizer is the ASan-family answer but is harder to set up. Running Valgrind occasionally is worthwhile.

### Should I use C at all for new projects?

If you have a choice and no constraint forcing C, memory safety by construction is a strong argument for Rust or Zig — and it gets stronger as the volume of generated code rises. Most C exists because of a constraint, though, and for that code the tooling on this page is what makes it survivable.
