C Program Compilation Steps (Deep Dive)

When we run a C program, we are not directly executing the .c file. A C source file goes through multiple stages before it becomes an executable.

Preprocessing → Compilation → Assembling → Linking → Loading/Runtime


Overall Pipeline

main.c
  |
  |  (1) Preprocessor
  v
main.i
  |
  |  (2) Compiler
  v
main.s
  |
  |  (3) Assembler
  v
main.o
  |
  |  (4) Linker (+ libraries)
  v
main (executable)
  |
  |  (5) OS Loader
  v
Running Process (in memory)

C Program Build + Run Pipeline Diagram

C Program Build + Run Pipeline

Detailed compilation pipeline diagram showing preprocessing, compilation, assembling, linking, static vs dynamic linking.


0) Source Code (.c)

Example program:

#include <stdio.h>

int x = 10;

int main() {
    printf("Hello %d\n", x);
    return 0;
}

1) Preprocessing (.c → .i)

Preprocessor performs text-based transformations before compilation.

What happens?

Command

gcc -E main.c -o main.i

Why useful?

If you suspect macro/header related issues, inspect main.i.


2) Compilation (.i → .s)

Compilation converts preprocessed C code into assembly.

Internal stages inside the compiler

  1. Lexical analysis (tokenization)
    Converts raw text to tokens like int, main, {, }

  2. Parsing
    Builds AST (Abstract Syntax Tree) using grammar rules

  3. Semantic analysis
    Type checking, prototype matching, conversions

  4. Optimization
    Examples:
    • constant folding
    • dead code elimination
    • common subexpression elimination
    • function inlining (optional)
  5. Code generation
    Converts intermediate representation (IR) to assembly

Command

gcc -S main.i -o main.s

Output file:


3) Assembling (.s → .o)

Assembler converts assembly into machine code.

Command

gcc -c main.s -o main.o

Output:

Object file contains

Useful tool:

nm main.o

4) Linking (.o + libs → executable)

Linker combines your object files and required libraries to produce final executable.

Why linking is needed?

Your code may call external functions like printf(). printf() is not inside your main.o — it is in the C standard library.

So linker resolves:

Command

gcc main.o -o main

Static vs Dynamic Linking

Dynamic linking (default)

Check:

ldd ./main

Static linking

gcc -static main.c -o main

5) OS Loader Stage (Runtime)

When you run:

./main

The OS loader:

Then startup code calls main().

Check entry point:

readelf -h main | grep Entry

Useful gcc option: keep intermediate files

gcc -save-temps main.c -o main

This generates .i, .s, .o files automatically.


Quick Interview Summary