What You Will Learn
When a process runs, its memory is divided into distinct regions called segments. Each segment has a specific purpose and lifecycle. Knowing which variables go where is critical for debugging memory bugs, understanding stack overflows, and working with dynamic memory.
A Linux process’s virtual memory is organized into five logical segments. The diagram below shows them in order from high to low virtual addresses (as seen on x86-32/x86-64):
| 0xFFFFFFFF | |
| Kernel Space (not accessible to user program) |
|
| argv, environ command-line args & environment strings |
|
| ↓ grows down | Stack local variables, function args, return addresses |
| ↑ ↓ (unallocated space between stack & heap) | |
| ↑ grows up | Heap malloc/free — dynamic allocation |
| BSS (uninit data) global/static vars NOT initialized — zeroed at start |
|
| Initialized Data global/static vars WITH initial values |
|
| Text (code) read-only machine instructions |
|
| 0x00000000 |
| Segment | Contains | C Example | Permissions |
|---|---|---|---|
| Text | Compiled machine code | Function bodies | Read + Execute (no write) |
| Init Data | Initialized globals/statics | int x = 10; | Read + Write |
| BSS | Uninitialized globals/statics (zeroed) | int arr[1000]; | Read + Write |
| Heap | Dynamic memory (malloc) | malloc(1024) | Read + Write |
| Stack | Local vars, function args, return addresses | int y; (inside fn) | Read + Write |
The BSS segment is not stored in the program file on disk. Instead, the executable records the size needed. The kernel allocates and zero-fills this memory at load time. This means a program with a 10 MB uninitialized global array won’t be a 10 MB file on disk.
size ./your_program — prints the size of text, data (init), and bss segments.Code Examples
#include <stdio.h>
#include <stdlib.h>
/* ---------- GLOBAL (file scope) ---------- */
int counter = 42; /* Initialized data segment */
char name[256]; /* BSS segment (zeroed at startup) */
/* ---------- STATIC ---------- */
static double pi = 3.14159; /* Initialized data segment */
int main(void)
{
/* ---------- STACK (automatic, inside main) ---------- */
int local_x = 100; /* Stack */
char local_buf[64]; /* Stack */
/* ---------- HEAP (dynamic) ---------- */
int *heap_arr = malloc(10 * sizeof(int)); /* Heap */
printf("counter = %d (init data seg)\n", counter);
printf("name[0] = %d (BSS — should be 0)\n", name[0]);
printf("local_x = %d (stack)\n", local_x);
printf("heap_arr ptr = %p (heap)\n", (void*)heap_arr);
free(heap_arr);
return 0;
}
/* Run 'size ./a.out' after compiling to see segment sizes:
text data bss dec hex filename
1692 616 256 2564 a04 a.out */
/* FILE: prog_a.c — uses INITIALIZED large array */
#include <stdio.h>
int big_array[100000] = {1, 2, 3}; /* Initialized data — stored on disk */
int main(void) { printf("size: see below\n"); return 0; }
/* FILE: prog_b.c — uses UNINITIALIZED large array */
#include <stdio.h>
int big_array[100000]; /* BSS — NOT stored on disk */
int main(void) { printf("size: see below\n"); return 0; }
/* Compile and compare file sizes:
$ gcc prog_a.c -o prog_a
$ gcc prog_b.c -o prog_b
$ ls -lh prog_a prog_b
-rwxr-xr-x 400K prog_a ← large because array is in file
-rwxr-xr-x 16K prog_b ← small because BSS is not in file
$ size prog_a
text data bss dec
1692 400616 4 402312
$ size prog_b
text data bss dec
1692 616 400004 402312
↑ BSS is large but file is still small */
#include <stdio.h>
#include <stdlib.h>
int g_init = 99; /* Init data segment */
int g_uninit; /* BSS segment */
extern char etext, edata, end; /* Segment boundary symbols */
int main(void)
{
int local = 5; /* Stack */
int *heap = malloc(4); /* Heap */
printf("--- Segment boundary addresses ---\n");
printf("End of text (code) : %p\n", (void*)&etext);
printf("End of init data : %p\n", (void*)&edata);
printf("End of BSS : %p\n", (void*)&end);
printf("\n--- Variable addresses ---\n");
printf("g_init (init data) : %p\n", (void*)&g_init);
printf("g_uninit (BSS) : %p\n", (void*)&g_uninit);
printf("local (stack) : %p\n", (void*)&local);
printf("heap (heap) : %p\n", (void*)heap);
/* On x86-64 you will see:
text address < init data address
init data < BSS address
BSS address < heap address
heap address << stack address */
free(heap);
return 0;
}
Interview Preparation Questions
Text (code), Initialized Data, BSS (uninitialized data), Heap, and Stack. Each serves a different purpose and has different permissions, lifetime, and growth behavior.
To prevent a process from accidentally modifying its own instructions through a bad pointer. It also enables sharing — multiple processes running the same program share one physical copy of the text segment in RAM.
BSS holds global and static variables that are not explicitly initialized. Since they are always zeroed at startup, the executable only needs to record the required size — not actual zero bytes. The kernel zero-fills this memory at load time, saving disk space.
The program break is the current top (upper boundary) of the heap. malloc() internally calls brk() or sbrk() to increase the program break, effectively growing the heap upward to allocate more dynamic memory.
Technically different segments: `int x;` goes to BSS (uninitialized), while `int x = 0;` goes to the initialized data segment. However, both end up with value 0 at runtime. Some compilers may optimize `int x = 0;` into BSS as well.
`size ./prog` shows the byte sizes of the text, initialized data (.data), and uninitialized data (.bss) segments, plus total. Example: `text=1692 data=616 bss=4 dec=2312`. It reads these values from the ELF section headers, not from runtime memory.
