Compiling C/C++
How source code becomes an executable — the preprocess, compile, assemble, and link pipeline, libraries, memory layout, and make.
This week is about the toolchain: how a .c or .cpp file becomes a running program. We
trace each stage of the build by hand with gcc/g++, look at what the linker produces, and
then let make automate the whole thing.
Anatomy of a program
A C/C++ source file mixes three kinds of things: preprocessor directives (#include,
#define), declarations (what exists — e.g. a function prototype in a header), and
definitions (the actual implementation). Declarations usually live in headers (.h) and
definitions in source files (.c / .cpp); the build stitches them together.
The four stages
Compilation is a pipeline. Each stage has its own tool and its own output file:
hello.c ──▶ hello.i ──▶ hello.s ──▶ hello.o ──▶ hello
preprocess compile assemble link
(cpp) (cc1) (as) (ld)You can stop after any stage with the right gcc flag:
gcc -E hello.c -o hello.i # 1. preprocess: expand #include / #define / macros
gcc -S hello.i -o hello.s # 2. compile: C → assembly
gcc -c hello.s -o hello.o # 3. assemble: assembly → object code (ELF .o)
gcc hello.o -o hello # 4. link: object files + libraries → executableIn practice you run gcc hello.c -o hello and the driver performs all four steps for you.
Inspecting the artifacts
file hello.o tells you what a file is, nm hello.o lists its symbols, and
objdump -d hello.o disassembles it back to assembly — handy for seeing what the compiler
produced.
Linking and libraries
The linker resolves the symbols your code refers to (like printf) against libraries.
There are two ways to link them:
- Static (
.aarchives) — the code is copied into your executable at link time. Bigger binary, no runtime dependency. Build one withar, link with-static. - Dynamic / shared (
.so) — the executable only records which library it needs; the code is loaded at run time and shared between programs. Compile the objects with-fPIC.
ldd hello shows which shared libraries an executable will load.
Memory layout
When the program runs, the loader maps it into an address space with distinct segments:
high ┌────────────────────┐
│ stack (grows ↓) │ local variables, call frames
│ ... │
│ heap (grows ↑) │ malloc / new
│ bss │ uninitialised globals
│ data │ initialised globals
low │ text │ the machine code (read-only)
└────────────────────┘Automating with make
Typing the commands by hand gets old fast. A Makefile describes targets, their
dependencies, and the recipe to build them — and make rebuilds only what changed:
CC = gcc
CFLAGS = -Wall -O2
all: hello
hello: hello.o
$(CC) hello.o -o hello
hello.o: hello.c
$(CC) $(CFLAGS) -c hello.c
clean:
rm -f *.o hellomake builds the all target; make clean removes the build output.
Go deeper
The Compilation and build process guide walks through every stage on a real program, and the Using Makefile sample shows a multi-file build end to end.
Practice / lab
- Run the four
gcccommands above on a Hello World and inspect each artifact (.i,.s,.o) withlessandfile. - List symbols with
nm hello.oand disassemble withobjdump -d hello.o. - Write a Makefile with
allandcleantargets; watchmakeskip unchanged files. - Compare
gcc hello.c -o hellowithgcc -static hello.c -o helloand check both withldd.
Homework
- ENV-2: Complex numbers and sorting — a multi-file C++ program built with a Makefile.
References & resources
Required
- GCC documentation
- GNU Make manual
- Compilation process in C (GeeksforGeeks)
- Understanding the C/C++ compilation process (video)
Optional / enrichment
- Learn Makefiles by example
- Memory layout of a C program
- 9 essential GNU binutils tools
- C++: препроцессор, компилятор, компоновщик (RUS, video)
Self-check
- What does the preprocessor do, and what is its output?
- Which stage produces the
.ofile, and what does it contain? - How does static linking differ from dynamic linking?
- Which tool lists the symbols inside an object file?
- Why are Makefiles useful once a project has more than one source file?