Fundamentals 13 min read

Why Does Your C++ Program’s Memory Look Like This? From Punch Cards to Virtual Memory

This article traces the evolution of memory layout from early punched‑card computers through assembly‑level innovations, segment‑based designs, and virtual memory, then demonstrates a real 32‑bit Linux C++ program showing where globals, stack, heap, and mapped regions reside, and explains how this knowledge helps debug segmentation faults, stack overflows, and memory leaks.

Liangxu Linux
Liangxu Linux
Liangxu Linux
Why Does Your C++ Program’s Memory Look Like This? From Punch Cards to Virtual Memory

Introduction

Understanding where variables reside (e.g., int number = 42;) explains the difference between global and local storage, and why segmentation faults occur.

Early computers – the birth of memory management

From punched cards

In the 1940s‑50s programs were sequences of holes on paper tape. Memory was a few kilobytes, only one program ran at a time, and there was no operating system, so there was no concept of a structured memory layout.

Memory was extremely scarce

Only one program executed at a time

Programs directly controlled the hardware

Assembly‑era innovations

Late 1950s introduced symbolic names (e.g., STORE RESULT) and a simple three‑region model:

┌───────────────┐
│      Stack    │
├───────────────┤
│      Data     │
├───────────────┤
│   Program Code│
└───────────────┘

Problems remained: safety hazards, wasted space, and difficulty extending memory.

Segmentation

From conceptual separation to physical isolation

1960s hardware (e.g., IBM System/360, DEC PDP) added memory protection, allowing distinct permission regions.

Segmentation matures

By the late 1970s (Unix V7) memory was divided into functional zones:

┌───────────────┐
│      Stack    │ ← function calls, locals (grows down)
├───────────────┤
│   Memory‑Mapped│ ← shared libraries, files
├───────────────┤
│      Heap     │ ← dynamic allocation (grows up)
├───────────────┤
│      BSS      │ ← uninitialized globals
├───────────────┤
│   Data Segment│ ← initialized globals
├───────────────┤
│   Code Segment│ ← program instructions
└───────────────┘

The BSS story

BSS (“Block Started by Symbol”) originated as a pseudo‑instruction on IBM 704/709 to reserve space for uninitialized data. Unix inherited it, saving disk space because zero‑filled data is created at load time.

Benefits of segmentation

Code protection : read‑only code segment prevents accidental overwrites.

Resource saving : BSS occupies no space in the executable.

Functional separation : different data types are stored separately, easing management.

Virtual memory – the modern era

From physical limits to virtual freedom

Late 1970s introduced virtual memory, giving the illusion of abundant address space while mapping pages to physical RAM or disk.

Memory‑mapping: sharing and interaction

BSD Unix (early 1980s) used memory‑mapping for shared libraries, large file access, and fast inter‑process communication.

Classic 32‑bit Linux layout

High address (0xFFFFFFFF) ── Kernel space
│   Stack (grows down)
│   ↓
│   ── Memory‑mapped region
│   ↑
│   Heap (grows up)
│   BSS (uninitialized globals)
│   Data segment (initialized globals)
Low address (0x00000000) ── Code segment

The 4 GB address space is split: user space 0‑3 GB, kernel space 3‑4 GB.

Why does the stack grow down?

Historical inheritance from early CPUs (e.g., Intel 8086).

Memory utilization: stack down, heap up maximizes free middle space.

Register design: some instruction sets make downward growth more efficient.

Real‑world C++ memory layout on 32‑bit Linux

Sample code – revealing addresses

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>

// Global – data segment
int globalInitialized = 100;
// Global – BSS segment
int globalUninitialized;
const char* constString = "Hello World"; // read‑only data segment

void printAddresses() {
    int localVar = 42; // stack
    static int staticLocalVar = 999; // data segment
    int* heapVar = (int*)malloc(sizeof(int));
    *heapVar = 123;
    void* mappedMemory = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    strcpy((char*)mappedMemory, "这是内存映射区");
    printf("Code (function): %p
", (void*)printAddresses);
    printf("Code (string): %p
", (void*)constString);
    printf("Data (init global): %p
", (void*)&globalInitialized);
    printf("BSS (uninit global): %p
", (void*)&globalUninitialized);
    printf("Heap: %p
", (void*)heapVar);
    printf("Mapped: %p
", mappedMemory);
    printf("Stack (local): %p
", (void*)&localVar);
    printf("Data (static local): %p
", (void*)&staticLocalVar);
    free(heapVar);
    munmap(mappedMemory, 4096);
}

int main() {
    printAddresses();
    return 0;
}

Typical output (addresses vary):

Code (function): 0x5626c78681e9
Code (string): 0x5626c7869008
Data (init global): 0x5626c786b010
BSS (uninit global): 0x5626c786b024
Heap: 0x5626de6dd2a0
Mapped: 0x7f4d8f70c000
Stack (local): 0x7ffec9b4b8a4
Data (static local): 0x5626c786b014

BSS optimization value

Placing large uninitialized arrays in BSS saves disk space because the executable records only the required size; the loader zero‑fills the memory at runtime.

Applying memory‑layout knowledge

Debugging segmentation faults

int main(){
    int* p = NULL;
    *p = 100; // segfault – writing to address 0
    return 0;
}

Knowing the address space helps locate the offending region.

Understanding stack overflows

void recursive(){
    int bigArray[1000000]; // consumes stack
    recursive();
}

Excessive recursion or large stack allocations exceed the limited stack size (typically a few megabytes).

Preventing memory leaks

void potentialLeak(){
    int* p = new int[1000];
    // missing delete[] p leads to leak
}

Awareness that heap memory must be freed avoids leaks.

Conclusion

The evolution from punched‑card programs to virtual memory shows how each advancement solved real problems—efficiency, safety, and simplicity—while preserving useful ideas like BSS. When you write int x = 10;, remember the centuries of engineering that decide where that value lives in memory.

Virtual MemoryMemory LayoutC++segmentation faultBSS
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

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.