C Memory Layout
When a C program runs, the operating system assigns it a block of memory. This memory is not used randomly — it is organized into distinct regions, each with a specific purpose. Understanding the memory layout of a C program helps you write better code, avoid bugs like buffer overflows and memory leaks, and understand why certain variables behave the way they do.
The Five Segments of a C Program's Memory
High Address
+---------------------------+
| Stack | ← Local variables, function calls
| (grows DOWNWARD ↓) |
+---------------------------+
| ↕ | ← Free space between stack and heap
| Heap |
| (grows UPWARD ↑) | ← malloc(), calloc() — dynamic memory
+---------------------------+
| BSS Segment | ← Uninitialized global/static variables
+---------------------------+
| Data Segment | ← Initialized global/static variables
+---------------------------+
| Text (Code) Segment | ← Your program's compiled instructions
+---------------------------+
Low Address
1. Text Segment (Code Segment)
The text segment holds the compiled machine code of your program — the actual CPU instructions that execute when your program runs. This segment is read-only to prevent the program from accidentally overwriting its own instructions.
- Contains: compiled function code, program logic
- Read-only: yes (attempts to write here cause a segfault)
- Example: the body of
main(), your functions
int add(int a, int b) {
return a + b; // this instruction lives in the text segment
}
2. Data Segment (Initialized Data)
The data segment stores global and static variables that have an explicit initial value set by the programmer.
- Contains: initialized global variables, initialized static variables
- Lifetime: entire program execution
- Writable: yes
int count = 10; // stored in data segment (initialized global)
static float rate = 3.5; // stored in data segment (initialized static)
int main() {
// count and rate are accessible anywhere
return 0;
}
3. BSS Segment (Uninitialized Data)
The BSS segment (Block Started by Symbol) stores global and static variables that are declared but not given an initial value. The OS automatically sets them to zero at program start.
- Contains: uninitialized global variables, uninitialized static variables
- Default value: all zeros (set by OS before program starts)
- Lifetime: entire program execution
int score; // stored in BSS — automatically 0
static int level; // stored in BSS — automatically 0
char name[100]; // stored in BSS — all bytes are 0
int main() {
printf("%d\n", score); // prints 0 (BSS guarantees zero initialization)
return 0;
}
4. Heap Segment
The heap is the region for dynamic memory allocation. Memory on the heap is allocated and freed manually by the programmer using malloc(), calloc(), realloc(), and free().
- Contains: memory allocated at runtime with malloc/calloc
- Lifetime: from allocation until freed (or program ends)
- Grows: upward toward high addresses
- Risk: forgetting to call
free()causes memory leaks
#include <stdlib.h>
int main()
{
int *ptr = (int*) malloc(5 * sizeof(int)); // allocates 20 bytes on heap
ptr[0] = 100;
ptr[1] = 200;
// ... use the memory ...
free(ptr); // release heap memory — must always do this!
return 0;
}
5. Stack Segment
The stack stores local variables, function parameters, return addresses, and saved register values for each active function call. The stack works as a LIFO (Last In, First Out) structure — when a function is called, a stack frame is pushed; when it returns, the frame is popped and that memory is freed automatically.
- Contains: local variables, function arguments, return address
- Lifetime: from function entry to function exit
- Grows: downward toward low addresses
- Risk: too many nested calls or large local arrays cause stack overflow
void display(int n) // n is stored on the stack
{
int temp = n * 2; // temp is stored on the stack
printf("%d\n", temp);
// temp and n are destroyed when display() returns
}
int main()
{
int x = 10; // x is on the stack
display(x);
return 0;
}
Stack Frame Diagram
When main() calls display(10):
Stack (top = lower address):
+-------------------------+
| display's frame: |
| n = 10 |
| temp = 20 |
| return address |
+-------------------------+
| main's frame: |
| x = 10 |
| return address |
+-------------------------+
When display() returns:
display's frame is POPPED and that memory is freed immediately.
Stack vs Heap — Key Differences
| Feature | Stack | Heap |
|---|---|---|
| Management | Automatic (compiler manages) | Manual (programmer manages) |
| Speed | Very fast | Slower (OS involvement) |
| Size | Limited (typically 1–8 MB) | Large (limited by system RAM) |
| Lifetime | Until function returns | Until explicitly freed |
| Risk | Stack overflow (too many calls) | Memory leak (forgetting free) |
| Grows toward | Low addresses (downward) | High addresses (upward) |
Identifying Where Variables Live
#include <stdio.h>
#include <stdlib.h>
int globalInit = 5; // Data segment
int globalUninit; // BSS segment
void demo()
{
int localVar = 10; // Stack
static int staticVar = 20; // Data segment
int *heapVar = (int*)malloc(sizeof(int)); // Heap
*heapVar = 30;
printf("local (Stack): %p\n", (void*)&localVar);
printf("static (Data) : %p\n", (void*)&staticVar);
printf("heap (Heap) : %p\n", (void*)heapVar);
free(heapVar);
}
int main()
{
demo();
return 0;
}
Common Memory Problems
| Problem | Cause | Region |
|---|---|---|
| Stack Overflow | Infinite recursion or very large local arrays | Stack |
| Memory Leak | malloc without free | Heap |
| Dangling Pointer | Using a pointer after free() | Heap |
| Buffer Overflow | Writing beyond array bounds | Stack or Heap |
| Segmentation Fault | Writing to read-only memory or NULL pointer | Text / BSS |
Summary
A C program's memory is divided into five segments. The text segment holds compiled code and is read-only. The data segment holds initialized global and static variables. The BSS segment holds uninitialized global and static variables, all initialized to zero by the OS. The heap holds dynamically allocated memory managed manually by the programmer — always pair malloc with free. The stack holds local variables and function call information, managed automatically — each function call creates a stack frame that is destroyed on return. Understanding these segments prevents memory bugs and helps write safer, more efficient C programs.
