When a program crashes, logs may mention stack overflow, segmentation fault, out of memory, or heap corruption. These are all memory-related, but they are not the same kind of failure.
Stack, heap, globals, text, and mmap regions are not merely “different memory blocks.” They serve different lifetimes, access patterns, and runtime constraints.
A useful first model is: text stores instructions, data stores global state, the stack stores function calls and local execution state, the heap stores dynamically allocated objects, and mmap regions store file mappings, shared memory, large anonymous mappings, and dynamic libraries.
On systems with virtual memory, these regions usually belong to one process address space:
high addresses
stack / thread stacks
mmap area / dynamic libraries / file mappings
heap
BSS / data
text / rodata
low addresses
The exact layout depends on architecture, kernel, compiler options, ASLR, and runtime behavior, but the division of responsibilities is broadly similar.
Text and Read-Only Data
The text segment usually stores program instructions and is commonly mapped readable and executable, but not writable. Read-only data stores string literals, constant tables, and similar content.
This has two benefits.
First, the program cannot easily corrupt its own instructions or constants at runtime.
Second, when multiple processes run the same program, code pages and read-only pages can be shared to reduce memory use.
So modifying a string literal or writing near a function address is not “changing an ordinary array.” It may touch a read-only or non-writable mapping. The result may be a segmentation fault, or on systems without protection, a more hidden memory corruption.
Globals Hold Long-Lived State
Global variables and static variables usually live in data or BSS.
Explicitly initialized globals go into the data segment. Uninitialized or zero-initialized globals usually go into BSS. BSS does not necessarily occupy full space in the executable file; it is zeroed when loaded.
These objects usually live for the whole process or firmware lifetime.
That has two engineering consequences:
- global state is easy to access concurrently from threads, tasks, or interrupt paths
- memory is occupied from startup and is not automatically released when a function returns
In embedded systems, BSS, data, heap, and stacks often share limited RAM. A large array that looks harmless as a global variable can directly change static RAM pressure.
The Stack Stores Call State
The stack mainly serves function calls.
A function call usually needs space for return addresses, some registers, local variables, argument spill space, and alignment padding. When the function returns, that stack space is automatically reclaimed.
main
-> foo
-> bar
-> baz
Deeper calls use more stack frames. Large local arrays make individual frames larger. Recursion, deep call chains, large locals, and interrupt nesting all increase stack pressure.
The stack is fast and has clear lifetime rules. Its drawback is limited capacity.
In a Linux user process, each thread has its own thread stack. The main thread and newly created threads may have different stack sizes, affected by ulimit, thread attributes, and runtime settings.
In an RTOS, each task usually has its own stack. If a task stack is too small, stack overflow follows; if it is too large, scarce RAM is wasted.
Why Stack Overflow Is Dangerous
Stack overflow is not always a polite “the stack is full” notification.
On systems with an MMU, there may be a guard page near the thread stack. An out-of-bounds access triggers a page fault, and the kernel turns it into a segmentation fault or stack overflow signal.
But on many MCUs or small RTOSes, the memory next to a stack may be another task’s stack, global data, heap, or a control block. A stack that grows too far may not crash immediately. It may corrupt another structure and fail much later as a seemingly random bug.
Common triggers include:
- large local arrays
- recursion without a clear bound
- large formatting buffers
- deep interrupt or callback nesting
- task stacks sized by guesswork
For these bugs, stack high-water marks, guard patterns, MPU protection, and compiler stack-usage reports are often more useful than only looking at the crash point.
The Heap Stores Dynamic-Lifetime Objects
The heap supports runtime allocation, such as malloc/free, new/delete, and object allocation in many language runtimes.
The heap is suitable for objects whose:
- size is unknown at compile time
- lifetime outlives the current function
- count changes at runtime
- ownership must be passed between modules
The cost is real: the allocator maintains metadata, finds suitable free blocks, handles release and coalescing, and in multithreaded environments protects concurrent access.
So heap allocation is much more complex than stack allocation.
Many memory bugs happen on the heap:
- memory leaks
- use-after-free
- double free
- out-of-bounds writes corrupting allocator metadata
- fragmentation after long runtimes
- unhandled allocation failure
In server programs, heap problems may appear as steady memory growth. In embedded devices, they may appear as allocation failures or resets after days of operation.
Fragmentation Is Not Just Low Total Memory
Heap fragmentation is painful because total free memory may be large enough, while no single contiguous block is large enough.
For example:
used free used free used free
8K 2K 8K 3K 8K 2K
There is 7K free in total, but a 6K allocation may still fail depending on the allocator and layout.
Long-running devices are especially exposed to this. A short test passing does not prove the heap will remain healthy after a month.
Common mitigations include:
- using memory pools for fixed-size objects
- avoiding repeated allocate/free in hot paths
- making ownership and release points explicit
- allocating long-lived objects during startup
- writing clear fallback paths for allocation failure
Real-time systems also care about allocation latency. The heap is not only a capacity issue; it is also a predictability issue.
mmap Regions Serve Special Mappings
On Linux-like systems, many mappings outside the heap come from mmap.
Common examples include:
- dynamic libraries
- file mappings
- shared memory
- large anonymous memory regions
- thread stacks
- device memory or DMA buffer mappings
That is why process memory cannot be understood only by asking “how big is the heap?” RSS, VSS, anonymous pages, file-backed pages, shared pages, and page-cache-related mappings may all matter.
The key idea of mmap is establishing a relationship between a virtual address range and some backing object. The backing object may be a file, device, shared memory object, or simply demand-allocated anonymous pages.
So mmap is not “another malloc.” It is closer to creating a mapping inside the address space.
Linux and RTOS Layouts Differ
In a Linux process with an MMU, each process has its own virtual address space. Stack, heap, dynamic libraries, and mmap regions are part of that process’s view. The same address value in two processes does not mean the same physical memory.
In many RTOS or bare-metal systems, all tasks share one physical address view. Each task has its own stack, but heap, globals, driver buffers, and DMA buffers often live in the same system address space.
This creates important differences:
- a Linux process crash usually does not directly corrupt other processes
- an RTOS task writing out of bounds may corrupt the whole system
- a Linux user pointer cannot be handed directly to DMA hardware
- on bare metal or many RTOSes, pointers are closer to real addresses, but protection is weaker
- RTOS task stack sizes usually must be planned at task creation
So any discussion of stack, heap, and address layout has to start with the runtime environment.
Debug Memory Bugs by Asking Where the Object Lives
When debugging a memory crash, do not look only at the pointer value. More useful questions are:
- is the object on the stack, heap, global area, mmap region, or driver/DMA buffer
- who owns its lifetime
- can it be used after a function returns
- can multiple threads, tasks, or interrupt paths access it concurrently
- can an out-of-bounds write corrupt a neighboring object
- is allocation failure handled
- does the system have an MMU, MPU, or guard page
The region where an object lives determines many failure modes.
Stack objects are vulnerable to use-after-return, stack overflow, and large locals. Heap objects are vulnerable to ownership bugs, use-after-free, fragmentation, and leaks. Global objects are vulnerable to concurrent access and hidden state coupling. mmap regions add mapping lifetime, synchronization, and backing-store consistency concerns.
What Matters in Practice
Stack, heap, globals, text, and mmap regions are not different names for memory. They express different lifetimes and access semantics.
The stack is for short-lived call state: fast, but limited. The heap is for dynamic-lifetime objects: flexible, but vulnerable to leaks, fragmentation, and corruption. Globals are for long-lived state, but they create implicit coupling. Text and read-only regions rely on permission protection. mmap regions map files, shared memory, devices, or large anonymous pages into an address space.
The value of understanding memory layout is not memorizing a fixed address diagram. It is being able to look at an object and ask: where does it live, who owns it, when is it released, who may access it concurrently, and what will be corrupted if it overflows?