Program Life Cycle
How Code Becomes An Executable
Understanding how your C code becomes a working program involves a series of well-defined steps. These steps include preprocessing, compiling, assembling, linking, and loading. Each plays a crucial role in turning human-readable code into machine-executable binaries.
π οΈ 1. Preprocessing
The first step in the compilation pipeline is preprocessing. The C preprocessor handles directives that begin with #
, such as:
#include <stdio.h>
#define PI 3.14
Key responsibilities:
File inclusion (
#include
)Macro expansion (
#define
)Conditional compilation (
#ifdef
,#ifndef
)
Output: A βcleaned upβ .i
file (pure C source code with all includes and macros expanded).
π§Ύ 2. Compilation (To Assembly)
Next, the compiler takes the preprocessed code and translates it into assembly language specific to the target architecture (e.g., x86, ARM).
Responsibilities:
Syntax analysis
Semantic checks
Optimization
Translation to low-level assembly
Example:
gcc -S hello.i -o hello.s
Output: .s
file (human-readable assembly instructions).
βοΈ 3. Assembling (To Machine Code)
The assembler converts the .s
(assembly) file into an object file containing machine code and metadata.
Responsibilities:
Convert mnemonics to opcodes
Assign addresses to instructions
Generate symbols and relocation info
as hello.s -o hello.o
Output: .o
file (object file with machine code, not yet executable)
π 4. Linking
Linking is the stage that combines one or more .o
files and resolves external references like function calls (printf
, main
, etc.). It also pulls in libraries and produces a final executable.
Types of Linking:
Static linking: Libraries are included in the final binary (larger file).
Dynamic linking: Binary relies on external
.so
(Linux) or.dll
(Windows) files at runtime.
Responsibilities:
Resolve function and variable references across files
Patch addresses (relocations)
Combine sections (
.text
,.data
,.bss
) into a final format like ELF (Linux) or PE (Windows)
gcc hello.o -o hello
Output: hello
(executable file)
π 5. Loading & Execution
At runtime, the operating system's loader takes over:
Loads the binary into memory
Maps required libraries (shared objects)
Resolves dynamic symbols (if dynamically linked)
Sets up the stack, heap, and registers
Jumps to the
main()
function
π¦ Libraries: How They Fit In
Static Libraries (.a
)
.a
)Linked at compile time
Included in final binary
Dynamic Libraries (.so
, .dll
)
.so
, .dll
)Linked at runtime
Can be shared across processes
Reduces memory usage and binary size
# Link with a shared library
gcc -o myprog myprog.c -lm # Links with libm.so (math library)
π§° Tools Breakdown
cpp
Preprocessor
.i
gcc
Compiler (with -S
, -c
, or full)
.s
, .o
, exe
as
Assembler
.o
ld
Linker
Executable
objdump
Disassembler
Assembly view
nm
Symbol table viewer
Function map
strace
System call tracer
Runtime flow
π Life Cycle Recap
C source (.c)
β
Preprocessor β Preprocessed (.i)
β
Compiler β Assembly (.s)
β
Assembler β Object (.o)
β
Linker β Executable (ELF/PE)
β
Loader β Loaded into Memory
β
Execution begins at main()
β
Summary
The journey from C code to an executable is powered by a pipeline of tools and concepts:
Preprocessing simplifies the code
Compiling translates to assembly
Assembling creates machine code
Linking resolves symbols and joins everything
Loading and execution bring your program to life
Last updated