Skip to content
  • The Compilation Model · C

The Compilation Model

LearnViaNotes @learnvianotes· 3 min read

Turning main.c into an executable is four separate stages: preprocess → compile → assemble → link. Knowing them makes compiler and linker errors easy to read.

main.c → Preprocessor → main.i → Compiler → main.s → Assembler → main.o → Linker → a.out

The four stages

  1. Preprocessing (cpp)

    Text substitution: expands #include (pastes header files in), #define macros, #if conditionals, strips comments. Output: one big translation unit (-E).

  2. Compilation (cc1)

    Parses the C, type-checks it, optimises it and emits assembly for the target CPU (-S). Syntax and type errors happen here.

  3. Assembly (as)

    Converts assembly into machine code in an object file (.o) with a symbol table — unresolved references to functions defined elsewhere are left as holes (-c).

  4. Linking (ld)

    Combines object files and libraries (libc, -lm…), resolves every symbol to an address, and writes the executable. “undefined reference” errors happen here.

Terminal
$ gcc -E main.c -o main.i      # stop after preprocessing
$ gcc -S main.c -o main.s      # stop after compiling (assembly)
$ gcc -c main.c -o main.o      # stop after assembling (object file)
$ gcc main.o util.o -o app     # link object files into an executable
$ gcc -save-temps main.c       # keep main.i, main.s, main.o all at once
$ nm main.o                    # list symbols (U = undefined, T = defined text)

Essential gcc / clang flags

FlagMeaning
-o nameName of the output file (default a.out).
-std=c17Language standard (c89, c99, c11, c17, c2x/c23, or gnu17 with extensions).
-Wall -WextraTurn on the important warnings. Always use them.
-WerrorTreat warnings as errors (good in CI).
-pedanticWarn about non-standard extensions.
-gInclude debug info (for gdb / lldb).
-O0 -O1 -O2 -O3 -OsOptimisation level (none → aggressive; -Os for size).
-I dir / -L dir / -lnameAdd a header search path / library search path / link libname.
-D NAME=valDefine a macro from the command line (like #define NAME val).
-fsanitize=address,undefinedRuntime checks for memory errors and undefined behaviour (ASan / UBSan).

Warnings are free bug reports

Every sample in these notes is compiled with -Wall -Wextra -Werror. This one is rejected at compile time because the format string does not match the argument:

C
#include <stdio.h>

int main(void) {
    double price = 9.99;
    printf("%d\n", price);   /* %d expects int, got double */
    return 0;
}
Compiler
main.c:5:20: error: format specifies type 'int' but the argument has type 'double' [-Werror,-Wformat]
    5 |     printf("%d\n", price);   /* %d expects int, got double */
      |             ~~     ^~~~~
      |             %f

Compile-time vs link-time errors

ErrorStageTypical cause
expected ';'CompileSyntax error
implicit declaration of functionCompileMissing #include or prototype
undefined reference to 'foo'LinkFunction declared but never defined / .o or -l library not linked
multiple definition of 'x'LinkGlobal defined in a header included by two .c files
undefined reference to 'sqrt'LinkForgot -lm (math library) on some Linux toolchains

What is a translation unit?

What is the difference between a compiler error and a linker error?

Static vs dynamic linking?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report