CD Notes
Guide to program memory sections, layout organization, and storage classes
Introduction to Program Memory Layout
When you compile a C program and run it, the operating system does not just dump everything into a random area of memory. Instead, the program's memory is carefully organized into distinct sections, each serving a specific purpose. Think of it like a well-organized office building — the ground floor has the reception (code), the first floor has permanent records (initialized data), the second floor has empty filing cabinets ready for use (uninitialized data), the upper floors are an ever-growing warehouse (heap), and the roof is a workspace where temporary tasks happen (stack).
Understanding storage organization is essential for compiler design because the compiler decides which section each piece of data belongs to. A global integer goes in .data or .bss, a local variable goes on the stack, and dynamically allocated memory goes on the heap. These decisions directly affect program performance, memory usage, and correctness.
The Five Memory Sections
The .text Section (Code Segment)
The .text section contains the machine instructions — the actual compiled code of your program. It has special properties:
When you have two terminal windows running the same program, the operating system keeps only one copy of the .text section in physical memory and maps it into both processes' virtual address spaces. This saves significant memory for commonly used programs.
The .data Section (Initialized Data)
The .data section stores global and static variables that have explicit initial values:
int global_count = 42; // Goes in .data
static float pi = 3.14159; // Goes in .data
char greeting[] = "Hello"; // Goes in .data (5 bytes + null)
const int MAX = 100; // May go in .rodata (read-only data)The initial values are stored directly in the executable file. When the program loads, the OS copies this section from disk into memory. This means the executable file size increases with every initialized global variable.
The .bss Section (Block Started by Symbol)
The .bss section stores global and static variables that are either uninitialized or initialized to zero:
The critical insight about .bss is that it takes no space in the executable file. The file only records the section's total size; at runtime, the OS allocates the memory and fills it with zeros. This is why static int arr[1000000] does not make your executable file 4 MB larger — the array lives in .bss.
The Heap
The heap is the region for dynamic memory allocation. It starts immediately after the .bss section and grows upward (toward higher addresses) as the program allocates memory:
The heap is managed by a memory allocator (like ptmalloc in glibc or jemalloc in Firefox). The compiler generates calls to allocation functions, but the actual management is a runtime responsibility.
The Stack
The stack occupies the high end of the address space and grows downward (toward lower addresses). It manages function calls, local variables, and temporary computations:
Complete Memory Layout Diagram
Here is the typical layout on a 64-bit Linux system:
The gap between heap and stack is essential — both grow toward each other. If they ever collide, the program is out of memory.
Storage Classes in C
The C language provides storage class specifiers that tell the compiler where and how long to store a variable:
| Storage Class | Keyword | Scope | Lifetime | Memory Section |
|---|---|---|---|---|
| Automatic | auto (default) | Local to block | Function execution | Stack |
| Static | static | Local or file | Entire program | .data or .bss |
| External | extern | Global (all files) | Entire program | .data or .bss |
| Register | register | Local to block | Function execution | CPU register (hint) |
Let us see how the compiler maps each to memory:
The static keyword inside a function is particularly interesting — the variable has local scope (only accessible inside the function) but static lifetime (persists between function calls). The compiler places it in .data or .bss, not on the stack.
How the Linker Organizes Sections
When you compile multiple source files, each produces its own .o (object) file with its own sections. The linker merges corresponding sections from all object files into the final executable:
| file1.o | [.text₁] [.data₁] [.bss₁] |
| file2.o | [.text₂] [.data₂] [.bss₂] |
| file3.o | [.text₃] [.data₃] [.bss₃] |
| executable | [.text₁+₂+₃] [.data₁+₂+₃] [.bss₁+₂+₃] |
The linker also resolves external references — when file1.c calls a function defined in file2.c, the linker patches the call instruction to point to the correct address in the merged .text section.
Examining Memory Layout in Practice
You can inspect a program's section sizes using the size command:
$ size my_program
text data bss dec hex filename
12584 1024 200000 213608 34268 my_programThis tells you the code is 12 KB, initialized data is 1 KB, and uninitialized data is 200 KB. Notice how the .bss section can be very large without increasing the executable file size.
For more detail, use objdump or readelf:
Key Takeaways
Storage organization determines how the compiler, linker, and operating system collaborate to place different kinds of data in appropriate memory regions. Code goes in read-only .text, initialized globals in .data, zero-initialized globals in .bss (saving disk space), dynamic data on the heap, and temporary function data on the stack. Understanding this organization helps you predict program behavior, optimize memory usage, and debug issues like segmentation faults that arise from accessing the wrong memory region.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Storage Organization: Memory Sections and Layout.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Compiler Design topic.
Search Terms
compiler-design, compiler design, compiler, design, runtime, environment, storage, organization
Related Compiler Design Topics