C Notes
Understand the complete memory layout of a C program including text segment, data segment, BSS, heap, and stack with diagrams, examples, and interview questions.
Understanding how a C program is organized in memory is crucial for writing efficient code, debugging memory issues, and performing well in technical interviews. When your program runs, the operating system allocates memory divided into distinct segments, each serving a specific purpose.
In this article, we'll explore every segment of a C program's memory layout, see where different variables live, and understand how the stack and heap grow during execution.
Overview of Memory Segments
When a C program is loaded into memory, it's divided into five main segments:
Text Segment (Code Segment)
The text segment contains the compiled machine code instructions of your program. It's placed in a read-only area of memory to prevent accidental modification of instructions.
Key characteristics:
- Read-only – attempting to write here causes a segmentation fault
- Sharable – multiple instances of the same program share one copy
- Fixed size – determined at compile time
#include <stdio.h>
// This function's code resides in the text segment
int add(int a, int b) {
return a + b;
}
int main() {
printf("Address of main(): %p\n", (void*)main);
printf("Address of add(): %p\n", (void*)add);
return 0;
}Address of main(): 0x401150 Address of add(): 0x401136
Data Segment (Initialized Data)
The data segment stores global and static variables that are explicitly initialized to a non-zero value. This segment is further divided into read-only and read-write areas.
#include <stdio.h>
// These live in the initialized data segment
int global_var = 42;
static int static_var = 100;
char *greeting = "Hello"; // pointer in data, string in read-only data
int main() {
static int local_static = 55; // also in data segment
printf("global_var address: %p\n", (void*)&global_var);
printf("static_var address: %p\n", (void*)&static_var);
printf("local_static addr: %p\n", (void*)&local_static);
return 0;
}global_var address: 0x404030 static_var address: 0x404034 local_static addr: 0x404038
Notice how all three addresses are close together – they're in the same segment.
BSS Segment (Uninitialized Data)
BSS stands for "Block Started by Symbol." It holds global and static variables that are either uninitialized or initialized to zero. The OS initializes this entire segment to zero before program execution begins.
The BSS segment doesn't actually occupy space in the executable file – only its size is recorded. This saves disk space.
#include <stdio.h>
// BSS segment - uninitialized or zero-initialized
int uninitialized_global;
static int uninitialized_static;
int zero_global = 0;
// Data segment - initialized to non-zero
int initialized_global = 10;
int main() {
printf("uninitialized_global = %d (address: %p)\n",
uninitialized_global, (void*)&uninitialized_global);
printf("zero_global = %d (address: %p)\n",
zero_global, (void*)&zero_global);
printf("initialized_global = %d (address: %p)\n",
initialized_global, (void*)&initialized_global);
return 0;
}uninitialized_global = 0 (address: 0x404044) zero_global = 0 (address: 0x404048) initialized_global = 10 (address: 0x404030)
Heap Segment
The heap is used for dynamic memory allocation. It grows upward (toward higher addresses) and is managed by functions like malloc(), calloc(), realloc(), and free().
Key characteristics:
- Programmer-managed – you allocate and free manually
- Grows upward – toward higher addresses
- Fragmentation – can occur over time
- Slower – allocation involves system calls
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p1 = (int*)malloc(sizeof(int));
int *p2 = (int*)malloc(sizeof(int));
int *p3 = (int*)malloc(100 * sizeof(int));
printf("First allocation: %p\n", (void*)p1);
printf("Second allocation: %p\n", (void*)p2);
printf("Third allocation: %p\n", (void*)p3);
// Notice addresses increase (heap grows upward)
free(p1);
free(p2);
free(p3);
return 0;
}First allocation: 0x1e48010 Second allocation: 0x1e48030 Third allocation: 0x1e48050
Stack Segment
The stack stores local variables, function parameters, return addresses, and manages function calls. It grows downward (toward lower addresses) and operates in a Last-In-First-Out manner.
Key characteristics:
- Automatic management – variables are allocated/freed with scope
- Grows downward – toward lower addresses
- Limited size – typically 1-8 MB (causes stack overflow if exceeded)
- Very fast – just moves a pointer
#include <stdio.h>
void function_b(int param) {
int local_b = 30;
printf("function_b - param: %p, local_b: %p\n",
(void*)¶m, (void*)&local_b);
}
void function_a() {
int local_a = 20;
printf("function_a - local_a: %p\n", (void*)&local_a);
function_b(99);
}
int main() {
int local_main = 10;
printf("main - local_main: %p\n", (void*)&local_main);
function_a();
return 0;
}main - local_main: 0x7ffd3a2c4e2c function_a - local_a: 0x7ffd3a2c4e0c function_b - param: 0x7ffd3a2c4dec, local_b: 0x7ffd3a2c4de8
Notice addresses decrease as we go deeper into function calls – the stack grows downward.
Complete Example: Where Variables Live
#include <stdio.h>
#include <stdlib.h>
int global_init = 100; // Data segment
int global_uninit; // BSS segment
static int static_init = 50; // Data segment
static int static_uninit; // BSS segment
void demo() {
int local = 10; // Stack
static int static_local = 25; // Data segment
int *heap_ptr = malloc(sizeof(int)); // Pointer on stack, data on heap
printf("=== Variable Locations ===\n");
printf("Text: main() = %p\n", (void*)main);
printf("Data: global_init = %p\n", (void*)&global_init);
printf("Data: static_init = %p\n", (void*)&static_init);
printf("Data: static_local = %p\n", (void*)&static_local);
printf("BSS: global_uninit = %p\n", (void*)&global_uninit);
printf("BSS: static_uninit = %p\n", (void*)&static_uninit);
printf("Heap: *heap_ptr = %p\n", (void*)heap_ptr);
printf("Stack: local = %p\n", (void*)&local);
printf("Stack: heap_ptr var = %p\n", (void*)&heap_ptr);
free(heap_ptr);
}
int main() {
demo();
return 0;
}=== Variable Locations === Text: main() = 0x4011a0 Data: global_init = 0x404030 Data: static_init = 0x404034 Data: static_local = 0x404038 BSS: global_uninit = 0x404044 BSS: static_uninit = 0x404048 Heap: *heap_ptr = 0x1a8d010 Stack: local = 0x7ffd8e3c1abc Stack: heap_ptr var = 0x7ffd8e3c1ac0
Segment Comparison Table
| Segment | Contains | Lifetime | Growth | Access |
|---|---|---|---|---|
| Text | Machine code | Entire program | Fixed | Read-only |
| Data | Initialized globals/statics | Entire program | Fixed | Read-Write |
| BSS | Uninitialized globals/statics | Entire program | Fixed | Read-Write |
| Heap | Dynamic allocations | Until free() | Upward | Read-Write |
| Stack | Local variables, parameters | Function scope | Downward | Read-Write |
Stack Overflow vs Heap Overflow
Stack Overflow occurs when the stack exceeds its limit, usually from infinite recursion:
Heap Exhaustion occurs when you keep allocating without freeing:
while (1) {
malloc(1024 * 1024); // 1MB each time, never freed
// Eventually malloc returns NULL
}Checking Memory Layout with size Command
On Linux, you can use the size command to see segment sizes:
// Compile: gcc -o program program.c
// Run: size programtext data bss dec hex filename 1516 600 8 2124 84c program
Interview Questions on Memory Layout
Q1: Where are string literals stored? String literals are stored in the read-only portion of the data segment (sometimes called .rodata). Modifying them causes undefined behavior.
Q2: What's the difference between BSS and Data segments? Data stores initialized non-zero globals/statics (takes space in executable). BSS stores uninitialized or zero-initialized ones (only size recorded in executable, initialized to zero at runtime).
Q3: Can the heap and stack collide? Theoretically yes, but modern systems use virtual memory with guard pages between them. In embedded systems without virtual memory, this is a real concern.
Q4: Where does a const global variable reside? In the read-only portion of the data segment. The compiler and linker place it in a protected memory page.
Q5: What happens when malloc() fails? It returns NULL. If you dereference NULL, it causes a segmentation fault. Always check malloc's return value.
Summary
The memory layout of a C program consists of five key segments: Text (code), Data (initialized globals), BSS (uninitialized globals), Heap (dynamic memory growing upward), and Stack (local variables growing downward). Understanding this layout helps you predict variable lifetimes, debug memory corruption, optimize memory usage, and ace technical interviews. Remember that the stack is fast but limited, while the heap is flexible but requires manual management.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Memory Layout of a C Program – Text, Data, BSS, Heap, Stack.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this C Programming topic.
Search Terms
c-programming, c programming, programming, advanced, memory, layout, memory layout of a c program – text, data, bss, heap, stack
Related C Programming Topics