Memory Layout of a C Program
Every running C program lives inside a chunk of memory that the operating system hands it, and that memory is not one undifferentiated blob — it is organized into distinct regions, each with its own purpose, lifetime, and rules. Understanding this layout is the foundation for understanding pointers, dynamic memory allocation, and a whole category of bugs (stack overflows, segmentation faults, dangling pointers) that make no sense until you know what memory region a variable actually lives in.
A compiled C program's address space is typically divided into five segments. From lowest address to highest, they are usually laid out roughly like this:
Segment | Contents | Fixed or Variable Size? |
|---|---|---|
Text (Code) Segment | The compiled machine instructions of your program | Fixed at compile time; typically read-only |
Initialized Data Segment | Global and static variables that have an explicit initial value | Fixed size, determined at compile time |
Uninitialized Data Segment (BSS) | Global and static variables with no explicit initializer (zero-initialized) | Fixed size, determined at compile time |
Heap | Memory obtained dynamically at runtime via malloc/calloc/realloc | Grows upward as you allocate; shrinks as you free |
Stack | Local variables, function parameters, return addresses | Grows and shrinks automatically as functions are called and return |
The Text (Code) Segment
main() function and every other function in your program. It is typically marked read-only by the operating system, so that a stray pointer bug that tries to write into it causes an immediate crash (a segmentation fault) rather than silently corrupting your own program's instructions.Initialized Data Segment
static variables that you give an explicit initial value live here. Because the initial value is known at compile time, the compiler can bake it directly into the executable file.int global_counter = 10; // lives in the initialized data segment static double pi_approx = 3.14; // also here -- static + explicit initializer
Uninitialized Data Segment (BSS)
static variables that were not given an explicit initial value. The C standard guarantees these start out zeroed, so the compiler does not need to store any actual initial data for them in the executable file — it just reserves the right amount of zeroed space when the program starts, which keeps the binary smaller.int global_total; // no initializer -- BSS, guaranteed to start at 0 static char buffer[1024]; // no initializer -- BSS, guaranteed to start all-zero
The Heap
malloc(), calloc(), and realloc() (all covered in depth on the following pages). Conceptually, the heap starts near the end of the data/BSS segments and grows upward, toward higher addresses, as your program allocates more memory. Heap memory is not automatically reclaimed — it stays allocated until you call free() on it (or the program exits).The Stack
#include <stdio.h>
void inner(void) {
int x = 42; // x lives on the stack, inside inner()'s stack frame
printf("%d\n", x);
} // inner()'s stack frame (including x) is popped here, automatically
void outer(void) {
int y = 10; // y lives on the stack, inside outer()'s stack frame
inner(); // pushes a new stack frame on top of outer()'s
printf("%d\n", y); // still valid -- outer()'s frame is untouched
}
int main(void) {
outer();
return 0;
}Where a Variable Lives, at a Glance
Declaration | Segment |
|---|---|
| Initialized data segment |
| BSS (uninitialized data) |
| Initialized data segment |
| BSS (uninitialized data) |
| Stack |
| Heap |
String literal | Text/read-only data segment |
A C program's memory is divided into segments: text (code), initialized data, BSS (uninitialized data), heap, and stack
Global/static variables with an explicit initializer go in the initialized data segment; without one, they go in BSS and are guaranteed to start at zero
Local variables live on the stack and are automatically reclaimed when their function returns -- they are not zero-initialized by default
Memory obtained with malloc/calloc/realloc lives on the heap and stays allocated until you explicitly free it
The heap and stack conceptually grow toward each other; exhausting either causes a crash, but for different reasons