Ever wondered what really happens when
you hit "compile"?
Your C code doesn't just magically become
a program.
It goes on a fascinating four-stage journey
from human-readable text to machine-
executable instructions.
Let's break it down.
1. Preprocessing: The Prep Stage
Before the real work begins, the
preprocessor cleans up your code. It handles
all directives starting with a #:
#include: Inlines header files directly into
your code.
#define: Expands macros and symbolic
constants.
#if, #ifdef: Manages conditional
compilation.
It also strips out all your comments. The
output is a single, expanded .i file, ready for
the compiler.
Command: gcc -E main.c -o main.i
2. Compilation: The Translation
Next, the compiler takes the preprocessed code
and translates it into assembly language,a low-
level language specific to your target's
architecture (e.g., x86, ARM).
This is a critical step where the system performs
syntax and semantic checks, catching errors in
your logic.
It's also where powerful optimizations like
constant folding and loop unrolling happen to
make your code more efficient.
The result is a .s file.
Command: gcc -S main.i -o main.s
3. Assembly: To Machine Code
The assembler converts the human-
readable assembly instructions into pure
machine code (binary).
This output, known as an object file (.o),
contains the raw instructions the CPU can
understand.
However, it's not yet a runnable program.
Each object file is a modular piece of your
final application, containing not just code
but also a symbol table and relocation
information for the next stage.
Command: gcc -c main.s -o main.o
4. Linking: The Final Assembly
The linker is the final piece of the puzzle.
Its job is to take one or more object files and
combine them with any necessary libraries (like
printf from the C standard library) into a single,
executable file.
It resolves symbol references, matching
function calls to their definitions and assigns
final memory addresses.
The output is your finished program (.elf, .exe,
etc.), ready to run!
Command: gcc main.o -o main
Advanced Considerations
For those working in embedded systems or
performance-critical applications, mastering the
compilation process is key.
Linker Scripts: Manually define the memory layout
to place code (.text) and data (.data, .bss) in specific
memory regions like Flash or RAM.
Optimization Flags: Use flags like -O1, -O2, -O3 to
control the level of compiler optimization, or -Os to
optimize for binary size in memory-constrained
devices.
Debugging Tools: Get familiar with objdump to
disassemble your binary, nm to inspect symbol
tables, and readelf to analyze the structure of your
executable.
Respost to share with the
embedded community
Like and Follow for more
embedded systems related
content