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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
