CLOSE

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

  1. Text Segment
  2. Data Segment
  3. BSS Segment
  4. Heap
  5. 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 method
  • mmap() – 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