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:

  1. Loads the binary into memory

  2. Maps required libraries (shared objects)

  3. Resolves dynamic symbols (if dynamically linked)

  4. Sets up the stack, heap, and registers

  5. Jumps to the main() function


πŸ“¦ Libraries: How They Fit In

Static Libraries (.a)

  • Linked at compile time

  • Included in final binary

Dynamic Libraries (.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

Tool
Description
Output

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