Toolchain & First Program

C source code is only text; a compiler turns it into machine code. This page installs your toolchain, explains the compile-lint-run cycle, and shows a first program compiled with the habits that keep C safe: loud warnings and sanitizers.

Choosing a Compiler

You need exactly one C compiler. The three mainstream options all implement the ISO C standard; pick whichever matches your environment.

CompilerWhere it shinesInstall (Windows)Install (Linux/macOS)
GCCDefault on Linux; best ecosystem compatibilityMSYS2: pacman -S mingw-w64-ucrt-x86_64-gccsudo apt install gcc / brew install gcc
ClangFast, precise diagnostics, great IDE supportwinget install LLVMsudo apt install clang / brew install llvm
MSVCNative Windows toolchain, Visual StudioVisual 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 get a.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.