Toolchain & First Program
Choosing a Compiler
You need exactly one C compiler. The three mainstream options all implement the ISO C standard; pick whichever matches your environment.
| Compiler | Where it shines | Install (Windows) | Install (Linux/macOS) |
|---|---|---|---|
| GCC | Default on Linux; best ecosystem compatibility | MSYS2: pacman -S mingw-w64-ucrt-x86_64-gcc | sudo apt install gcc / brew install gcc |
| Clang | Fast, precise diagnostics, great IDE support | winget install LLVM | sudo apt install clang / brew install llvm |
| MSVC | Native Windows toolchain, Visual Studio | Visual Studio Build Tools (Desktop C++ workload) | — |
The Compile–Lint–Run Cycle
Compiling is a pipeline: the preprocessor expands #include and macros, the compiler translates to assembly, the assembler produces object code, and the linker combines objects with libraries into an executable. You drive all of it with one command:
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror hello.c -o hello
./hello # run on Linux/macOS
./hello.exe # run on Windows (Git Bash)
-std=c11— demand the C11 standard (C17 and C23 also exist; C11 is supported everywhere).-Wall -Wextra -Wpedantic— enable the warning families. A warning in C is the compiler confessing it noticed a trap.-Werror— promote every warning to an error. Painful at first, lifesaving later.-o hello— name the output executable; without it you geta.out.
Compile Errors vs Link Errors
Bad syntax or a type misuse makes the compiler refuse (compile error). Valid code that refers to a function the linker cannot find — a missing library or a misspelled function name — makes the linker refuse (link error). Read the first line of any diagnostic: it names file, line, and severity. Fix errors top-down; one error at the top often cascades into fake errors below it.
Your First Program
Every C program has exactly one main function — the entry point. This version is honest and complete: it announces itself, uses a variable, and returns an exit status. Read the comments; they explain why, not just what.
// hello.c — first program, compiled with warnings-as-errors.
#include <stdio.h> // declares printf(): formatted console output
int main(void) { // "void": main takes no arguments; returns int
printf("Hello, C!\n"); // \n moves the cursor to a new line
int answer = 42; // C requires a declared type for every name
printf("The answer is %d\n", answer); // %d prints an int value
return 0; // exit status 0 = success for the shell
}
Compile it with the loud flags above and run it. Then break it on purpose: delete the return, change %d to %s, remove <stdio.h>. Each break teaches you a diagnostic you will meet again for real.
Sanitizers — C's Debugging Superpower
Modern compilers can instrument your program at runtime and catch the memory bugs that compile-time warnings cannot: out-of-bounds access, use-after-free, leaks, and signed overflow. Two sanitizers matter most:
gcc -std=c11 -Wall -Wextra -fsanitize=address,undefined hello.c -o hello
./hello # now: any memory error aborts with a precise stack trace
-fsanitize=address— AddressSanitizer: catches buffer overflows, use-after-free, and double-free, and reports the exact allocation site.-fsanitize=undefined— UndefinedBehaviorSanitizer: catches signed overflow, misaligned access, and other UB with pinpoint messages.
Make this your default compile recipe for every exercise in this roadmap. Debugging with sanitizers is faster and more precise than staring at crashes — this is the single best habit you can learn here.
A Minimal Build Script
For one-file programs the shell command is enough. When a project grows, capture the recipe in a tiny script so every future compile uses identical, safe flags. Save this as build.sh and run bash build.sh:
#!/usr/bin/env bash
# build.sh — compile every source file in the current directory with safe flags
# then run the resulting executable with sanitizers active.
set -euo pipefail # fail fast on any error
CC="${CC:-gcc}" # respect an overridden CC
FLAGS="-std=c11 -Wall -Wextra -Wpedantic -Werror"
FLAGS="$FLAGS -fsanitize=address,undefined" # catch memory bugs at runtime
for src in *.c; do # compile each source file
echo "compiling $src"
"$CC" $FLAGS "$src" -o "${src%.c}"
done
echo "build ok — run: ./hello"
Now every example in this roadmap compiles with the same discipline. Next, learn the lexical structure of the language: punctuation, keywords, and how a statement is assembled.