Appearance
[中文版本](/posts/c教程/zh-CN/06-编译与构建/051-From Source to Executable The Compilation Pipeline)
Every time you run
gcc hello.c -o helloyou’re launching a complex, multi-stage process that transforms human-readable C code into a machine-executable binary. Understanding this compilation pipeline is the heart of becoming a real systems programmer.
Let’s unpack what happens between your.c file and the final executable.
Step 1. The Four Stages
A C compiler (likegcc orclang) performs four major stages internally:
| Stage | Tool | Input | Output | Description |
|---|---|---|---|---|
| 1. Preprocessing | cpp | hello.c | expanded source | Handles#include,#define, and macros |
| 2. Compilation | cc1 | expanded source | hello.s | Translates C into assembly |
| 3. Assembly | as | hello.s | hello.o | Converts assembly into machine code |
| 4. Linking | ld | .o+ libraries | hello | Combines everything into a runnable program |
You can stop at any step with compiler flags to see what’s happening.
Tiny Code: Observe Each Stage
Let’s use a simple program:
// hello.c
#include <stdio.h>
int main(void) {
printf("Hello, C!\n");
return 0;
}Run these commands to inspect each stage:
# 1. Preprocessing
gcc -E hello.c -o hello.i
# 2. Compilation to Assembly
gcc -S hello.i -o hello.s
# 3. Assembly to Object File
gcc -c hello.s -o hello.o
# 4. Linking
gcc hello.o -o helloNow check what each file looks like:
hello.i: C code with headers expandedhello.s: Human-readable assembly instructionshello.o: Machine code (binary object)hello: Final executable
Run:
./helloOutput:
Hello, C!Step 2. Preprocessing (#include, #define, #if)
The preprocessor handles all lines starting with#. It’s purely textual, no code execution yet.
gcc -E hello.c -o hello.iOpenhello.i and you’ll see thousands of lines fromstdio.h inserted into your code. It also replaces macros and removes comments.
This is the stage where your headers, macros, and conditional compilation come to life.
Step 3. Compilation (to Assembly)
Next, the compiler translates your preprocessed C code into assembly language for your target CPU.
gcc -S hello.i -o hello.sOpenhello.s to peek at low-level instructions like:
mov edi, OFFSET FLAT:.LC0
call putsThese are CPU-specific, on x86, ARM, or RISC-V, they’ll differ. This stage also performs optimization, type checking, and error detection.
Step 4. Assembly (to Object File)
The assembler converts the.s file into raw machine instructions and data structures, producing a relocatable object file.
gcc -c hello.s -o hello.oYou can inspect it with:
objdump -d hello.oEach function in your code becomes a symbol in this object file.
Step 5. Linking
The linker (ld) combines object files and libraries into a single executable.
gcc hello.o -o helloIf your program uses external functions (likeprintf), the linker locates them in system libraries (e.g.,/usr/lib/libc.so) and records their addresses.
The result: one self-contained executable ready to run.
Step 6. Inspect the Final Binary
Use tools like:
file hello
nm hello | head
readelf -h helloThese reveal:
- File type (ELF, Mach-O, etc.)
- Defined and undefined symbols
- Sections like
.text,.data,.bss
You’re now seeing your C code at the machine level.
Step 7. Cleanup and Automation
While it’s educational to run each step manually, most of the time you’ll rely on:
gcc hello.c -o helloor aMakefile to orchestrate multiple files (we’ll cover that in Section 55).
Tiny Code: Multi-File Example
Let’s build a two-file program manually:
main.c
#include "greet.h"
int main(void) {
greet("C programmer");
return 0;
}greet.c
#include <stdio.h>
void greet(const char *name) {
printf("Hello, %s!\n", name);
}greet.h
void greet(const char *name);Compile and link manually:
gcc -c main.c
gcc -c greet.c
gcc main.o greet.o -o app
./appOutput:
Hello, C programmer!Why It Matters
Understanding the compilation pipeline helps you:
- Debug tricky build errors (
undefined reference,multiple definition, etc.) - Control optimization and debugging symbols
- Inspect intermediate stages for learning or tuning performance
- Build your own lightweight build systems or compilers
This is how source code becomes machine reality, step by step, precisely defined.
Try It Yourself
- Generate all intermediate files (
.i,.s,.o) for a few programs and inspect them. - Experiment with
gcc -O0,-O2, and-O3and observe how assembly changes. - Add
-gand explore the binary withgdb. - Build a program that spans multiple
.cfiles. - Use
nmandobjdumpto trace how symbols move through the stages.
Next, you’ll explore the preprocessor and macros, the engine behind includes, constants, and compile-time code generation.
