6.3 Memory Layout of a Process

6.3 Memory Layout of a Process
Linux System Programming — Chapter 6
5
Memory Segments
3
Code Examples
6
Interview Questions

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.

The Five Memory Segments

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):

Process Virtual Memory Layout (High → Low Address)
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 Details — What Goes Where?
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

Why BSS Saves Disk Space

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.

Try it: size ./your_program — prints the size of text, data (init), and bss segments.

Code Examples

Example 1: Where Do My Variables Live?
#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   */
Example 2: BSS vs Initialized Data — Size Difference
/* 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 */
Example 3: Print Addresses to See Segment Layout
#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

Q1. What are the five memory segments of a Linux process?

Text (code), Initialized Data, BSS (uninitialized data), Heap, and Stack. Each serves a different purpose and has different permissions, lifetime, and growth behavior.

Q2. Why is the text segment read-only?

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.

Q3. What is the BSS segment and why doesn’t it take space on disk?

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.

Q4. What is the “program break” on the heap?

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.

Q5. A global variable declared as `int x;` vs `int x = 0;` — same or different segment?

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.

Q6. What does the `size` command show? Write its typical output.

`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.

Leave a Reply

Your email address will not be published. Required fields are marked *