This episode digs into what happens behind the scenes: the four-stage compilation process, the C memory model that divides the stack, heap, static data, and text segments, and the ABI and calling convention. You will also see the structure of a C program and the toolchain flow.

A C program does not become an executable directly. An entire pipeline works behind the scenes, from preprocessing to linking. Understanding this pipeline lets you diagnose compiler errors, read linker messages, and optimize binary size.
Episode 2 dissects the internal architecture of the C language: the four stages of compilation, the memory model that divides the address space into different segments, the ABI and calling convention that connect functions, and the program structure and toolchain that put it all together.
After this episode, you will no longer see C code as plain text, but as a blueprint that runs through a precise transformation engine.
The first stage reads directives that begin with the # sign. The preprocessor replaces #include with the header contents, expands #define macros, and filters out #ifdef sections. The result is a file with all includes already expanded. Run this stage yourself with:
gcc -E program.c -o program.i
wc -l program.iThe command gcc -E program.c -o program.i produces a temporary file much longer than the original source, because it contains the entire contents of the included headers.
The second stage translates C into architecture-specific assembly, then the third stage translates the assembly into an object file of machine code. Object files cannot be run yet because references to functions from other files are still unresolved:
gcc -S program.c
gcc -c program.c -o program.o
file program.oThe -S flag produces a .s file containing assembly, while -c produces a .o object file. To see the symbols defined inside an object file, use nm:
nm program.oThe output of nm program.o shows symbols such as main and library functions that are still undefined.
The final stage is linking: the linker combines all object files, resolves references between functions, and attaches runtime libraries such as libc. The result is an executable:
gcc program.o -o program
readelf -h programreadelf -h program displays the ELF header, including the architecture, file type, and entry point. Of the four stages above, only the first two are usually visible to programmers; errors that appear at the linking stage typically read undefined reference.
When an executable is loaded, the operating system divides the process memory into several segments:
malloc, managed manually by the programmer.Global variables live for the whole program, local variables live only while their function is called, and heap data lives until free is called.
size programThe command size program shows the text, data, and bss segment sizes at a glance. This is a quick way to inspect a program's memory footprint.
The ABI (Application Binary Interface) is a binary-level contract: data type sizes, struct layout, and how functions are called. The calling convention is the part of the ABI that determines how arguments are passed to functions — through which registers, and how values are returned.
On x86_64 Linux, the System V convention places the first argument in the rdi register, the second in rsi, and so on. Programs that follow the same ABI can interoperate even if written in different files. This is what allows the printf function from libc to be called from your code.
Understanding the ABI helps you read errors like undefined reference, understand why structs need alignment (episode 17), and why binary libraries must be built with a compatible ABI. ABI mismatch is a classic cause of crashes when linking libraries compiled with different compilers.
A healthy C project consists of several components: header files that declare the interface, source files that provide the implementation, and a single main function as the entry point. Preprocessor directives like #include and #define are in charge, while the linker brings everything together with static or shared libraries.
The basic toolchain works in one flow: edit in your editor, compile with gcc or clang, link, then run. If a crash occurs, a debugger like gdb reads the core file and stack to find the cause. Episode 11 will cover debugging in depth, but the flow always starts with understanding the pipeline above.
Tip
A good habit: compile with gcc -Wall -Wextra -g. The first two flags turn on warnings, and -g stores the debug information needed by gdb and valgrind.
Key takeaways:
-E, -S, -c, and full linking separate each pipeline stage.undefined reference errors appear at the linking stage, not compilation.In the next episode 3 we will discuss C's basic syntax and program structure — variable declarations and basic data types, the main function, statements, code blocks, comments, and putting together and running a simple program.