Fundamentals 13 min read

Understanding C/C++ Process Memory Layout: Code, Global, Stack, and Heap

This article explains how an operating system divides a C/C++ program's virtual address space into four distinct regions—code, global, stack, and heap—detailing their lifetimes, permissions, management rules, and common pitfalls such as memory leaks, dangling pointers, and stack overflow.

Deepin Linux
Deepin Linux
Deepin Linux
Understanding C/C++ Process Memory Layout: Code, Global, Stack, and Heap

Many developers write code for years yet still misunderstand the underlying memory layout of a process. This article explains why local variables disappear after a function returns, why new/malloc objects must be freed manually, and why global variables stay resident.

Process from File to Execution

When a C/C++ program is compiled, an executable file is produced. Running the program triggers the OS loader to read the file into memory and divide the virtual address space into four regions: code (text), global (data + BSS), heap, and stack. The classic order from high to low addresses is code → global → heap → stack (stack grows downward, heap grows upward).

Code Segment (Text)

The code segment stores compiled binary instructions. It is read‑only, shared among processes, lives for the entire program execution, and contains no variable data.

Read‑only, cannot be written at runtime.

Shared across processes to save memory.

Longest lifetime: created at program start, released after program exit.

No variable data, only executable instructions.

int add(int a, int b) {
    if (a > 0) {
        return a + b; // arithmetic instruction → code segment
    }
    return 0;
}

void loop() {
    for (int i = 0; i < 10; i++) { // loop logic → code segment
        // instruction sequence → code segment
    }
}

Global Segment (Data + BSS)

The global segment, also called the static segment, holds global variables and variables declared with static. It is split into two sub‑segments: the initialized data segment (Data) for variables with explicit initial values, and the uninitialized segment (BSS) for variables without initial values, which the OS zero‑fills.

Program‑wide lifetime: allocated at start, released at exit.

Initialized variables → Data; uninitialized → BSS.

Automatically zero‑initialized in BSS, avoiding wild values.

Read‑write during execution.

// Initialized global variable → Data segment
int g_num = 10;
char g_str[] = "hello";

// Uninitialized global variable → BSS segment
int g_val;

void func() {
    // Local static initialized variable → Data segment
    static int s_a = 100;
    // Local static uninitialized variable → BSS segment
    static int s_b;
}

Stack

The stack is automatically allocated and reclaimed memory for local variables, function parameters, temporaries, and return addresses. It grows downward, follows a LIFO order, and is very fast but limited to a few megabytes.

Automatic lifecycle: allocated on function entry, reclaimed on exit.

Very small space (typically a few MB).

Continuous addresses, read‑write, highest access speed.

Follows stack data‑structure rules (push/pop).

void test() {
    // Local variable, function parameter, temporary variable all stored in stack
    int a = 10;
    double b = 3.14;
    char buf[32];
}

Classic issue – stack overflow: limited stack size means deep recursion, excessively large local arrays, or deep nesting can quickly exhaust the stack, causing a crash.

// Example 1: deep recursion causing stack overflow
void recurse() {
    recurse();
}

// Example 2: large local array exhausting stack space
void bigArr() {
    int arr[1000000];
}

Heap

The heap is manually managed memory obtained via new / malloc and released with delete / free. It can grow to gigabytes, but developers must match each allocation with a corresponding deallocation to avoid leaks, dangling pointers, and fragmentation.

Manual lifecycle: new/malloc to allocate, delete/free to release.

Very large space (GB‑level), suitable for big data and long‑lived objects.

Non‑continuous addresses; fragmentation may occur.

Lifetime independent of function scope; persists until freed or program exits.

#include <stdlib.h>
void test_heap() {
    // Manual heap allocation
    int* p = (int*)malloc(sizeof(int) * 10);
    // Use heap memory
    p[0] = 100;
    // Must free manually; omission causes memory leak
    free(p);
}

Typical heap bugs:

Memory leak – allocated memory never freed.

Dangling pointer – accessing memory after it has been freed.

Out‑of‑bounds access – reading/writing beyond the allocated range.

#include <stdlib.h>
void heap_err() {
    // Allocate 10 ints (valid indices 0‑9)
    int* ptr = (int*)malloc(sizeof(int) * 10);
    ptr[10] = 10; // out‑of‑bounds access
    free(ptr);
    ptr[0] = 20;  // dangling pointer: use after free
    // Missing free(ptr) would cause a leak
}

Comparison of the Four Regions

Code : OS‑managed; lifetime program start‑to‑exit; small (only instructions); read‑only; issues – instruction tampering.

Global : OS‑managed; lifetime program start‑to‑exit; medium size; read‑write; issues – unnecessary resident memory.

Stack : OS‑managed; lives during function calls; very small (MB); read‑write; issues – overflow, invalid returns.

Heap : Developer‑managed; from allocation to manual free; very large (GB); read‑write; issues – leaks, dangling pointers, fragmentation.

Putting It All Together

The following program demonstrates which variables reside in each region and shows proper heap allocation and deallocation.

#include <iostream>
using namespace std;

// Global variable: stored in Global Data segment
int g_num = 100;
// Uninitialized global variable: stored in Global BSS segment
int g_empty;

void test() {
    // Local variable: stored in Stack
    int local_num = 10;
    // Static local variable: moved from Stack to Global segment
    static int static_num = 20;
    // Dynamic memory: stored in Heap
    int* heap_ptr = new int(30);

    cout << "Stack local variable: " << local_num << endl;
    cout << "Global static variable: " << static_num << endl;
    cout << "Heap dynamic variable: " << *heap_ptr << endl;

    // Manual heap deallocation
    delete heap_ptr;
}

int main() {
    test();
    return 0; // Program ends, Stack and Global memory are released
}

Understanding the four memory regions is essential for debugging memory errors, optimizing performance, and writing correct C/C++ code. Mastery of their rules enables developers to avoid wild pointers, memory leaks, and stack overflows.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Memory managementC#stackheapprocess memoryglobal variablescode segment
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.