Understanding the memory layout of a C program is essential for writing efficient, secure, and reliable code. Unlike higher-level languages that abstract memory management, C provides direct control over memory, making it powerful but error-prone. This article explores the structure of a C program’s memory, its segments, common pitfalls, and best practices.
Overview of Memory Segments
A C program’s memory is divided into distinct segments, each serving a unique purpose. These segments are organized as follows in the virtual address space (from low to high addresses):
- Text Segment
- Data Segment
- BSS Segment
- Heap
- Stack
High Address
+------------------------+ ← Stack (function calls, local vars)
| Stack |
|------------------------|
| Heap | ← malloc(), calloc(), realloc()
|------------------------|
| BSS Segment (uninit) | ← uninitialized global/static vars
|------------------------|
| Data Segment (init) | ← initialized global/static vars
|------------------------|
| Text | ← code (machine instructions)
+------------------------+
Low Address
High Memory Addresses
┌─────────────────────────────┐
│ Stack │ ← grows downward
│ - Local variables │
│ - Function call frames │
│ - Return addresses │
├─────────────────────────────┤
│ Heap │ ← grows upward
│ - malloc/calloc/realloc │
│ - Managed via brk()/mmap() │
├─────────────────────────────┤
│ BSS Segment (Zero-inited) │
│ - Uninitialized globals │
│ - static int x; │
├─────────────────────────────┤
│ Data Segment (Init data) │
│ - Initialized globals │
│ - static int x = 5; │
├─────────────────────────────┤
│ Text Segment (Code) │
│ - Machine instructions │
│ - const string literals │
└─────────────────────────────┘
Low Memory Addresses
1️⃣ Text Segment (Code)
Purpose:
Stores executable machine instructions (compiled code).
Characteristics:
- Read-only: Prevents accidental code modification.
- Attempting to write causes segmentation fault.
- Shared: Reused across processes running the same binary to save memory.
- Contains string literals (e.g., “Hello World!”).
Example:
printf("Hello"); // String "Hello" resides in the text segment.
2️⃣ Data Segment
Purpose:
Holds initialized
global and static variables.
Characteristics:
- Read-write: Variables can be modified during execution.
- Persistent: Exists for the program's entire lifecycle.
Example:
int globalVar = 42; // Stored in the data segment
static int staticVar = 5; // Also in the data segment
3️⃣ BSS Segment (Block Started by Symbol)
Purpose:
Store uninitialized global and static variables.
Characteristics:
- Zero-initialized: Set to
0
by the OS before program execution. - Reduces executable file size by not storing explicit zeroes.
Example:
int uninitGlobal; // BSS segment
static int uninitStatic; // BSS segment
4️⃣ Heap
Purpose:
Dynamically allocated memory (via malloc
, calloc
, realloc
).
Characteristics:
- Manual management: Allocated/freed by the programmer.
- Grows upwards: Exands toward higher addresses.
- Fragmentation risk: Inefficient use can lead to wasted memory.
- Memory must be freed manuallly.
Example:
int *arr = malloc(10 * sizeof(int)); // Heap allocation
free(arr); // Explicit deallocation required
Heap is allocated by the OS using:
brk()
– old methodmmap()
– more common now
5️⃣ Stack
Purpose:
Manages local variables, function parameters, and return addresses.
Characteristics:
- Automatic management: Allocated on function entry, freed on exit.
- Grows downwards: Expands toward lower addresses.
- Fixed size: Overflow cause stack exhaustion (e.g., infinite recursion).
Example:
void func() {
int localVar = 10; // Stack-allocated
} // Memory for localVar is freed automatically
6️⃣ Additional Memory Regions
- Command-Line Arguments & Environment Variables: Stored at the top of the stack or a dedicated region.
- Memory-Mapped Regions: Used for shared libraries, memory-mapped files, or dynamic linking.
Real Example in Code
Code:
#include <stdio.h>
#include <stdlib.h>
int global_data = 10;
static int static_data = 20;
static int static_uninit;
int main() {
int stack_var = 5;
char *heap_var = (char*)malloc(100);
printf("Text (main): %p\n", (void *)main);
printf("Global (data): %p\n", (void *)&global_data);
printf("Static (data): %p\n", (void *)&static_data);
printf("Static (bss): %p\n", (void *)&static_uninit);
printf("Stack: %p\n", (void *)&stack_var);
printf("Heap: %p\n", (void *)heap_var);
free(heap_var);
return 0;
}
Output:
Text (main): 0x1
Global (data): 0x6b0
Static (data): 0x6b4
Static (bss): 0x750
Stack: 0x500df8
Heap: 0x500e28