The Most Important Picture
Every program runs in memory. Understanding what those areas of memory are for is a critical step for understanding and debugging complex programs. Some languages, such as C, C++, or Rust, require keeping this in mind during development. Other languages still use these areas in the same ways as manual-memory-managed languages. That means that this metaphor cuts across nearly every programming language and platform! It explains multithreading, system calls, object allocation, and more.
The Most Important Picture is the layout of a running program in its virtual memory. This virtual memory is the length and breadth of the cell the program is confined to. The program has access to (some of) this virtual memory and nothing else. All memory is indexed: every byte is addressable through its unique location in memory. It is divided into four (three and two halves) sections.
At the lowest area of virtual memory lives the code (or text) section. The program has to know what instruction to execute next; this is where all the instructions live. The instruction is loaded from this memory into the CPU and executed.
Unlike the other sections of memory, the code section has read-execute permissions. By default, other sections are read-write. This makes the code section a read-only area, which may not be modified during the run of the program. Constant strings or data may be put here by a compiler. Hence its alternative name, the text section. This also ensures that code can only be executed if it lives in the code section.
Data (two parts)
Just above the code section lives the data section.
This section is fixed in size, neither growing nor shrinking during the run of the program.
It holds any data that has static lifetime duration: globals, and items labeled
static in most languages.
Its fixed size means that the number of global items must be known at compile time.
Block Started by Symbol
The data section is split into two parts, the data section proper and the BSS. The data section sits below the BSS in virtual memory. This distinction matters almost not at all. Basically, any static data that a compiler determines to be initialized with a value of 0 might be in the BSS section. Rather than storing those explicit values of 0, the program on disk can just store how many bytes are needed. The BSS section can then be expanded at load time, saving a few dozen bytes of storage on the filesystem.
The stack is by far the most complex arrangement of memory. It forms a stack data structure, hence the name. When a function is invoked at runtime, space is created on the stack for that function's use, known as a stack frame. This space holds all local variables used by the function. It usually also holds the copy of each argument passed to the function. It also contains a pointer to where the function was invoked from—which will point to somewhere in the code section. This old instruction pointer tells the program where to continue from once the function is over.
When the function terminates, the memory used by the stack frame is reclaimed by the process for whatever the next function call may need. This reclaimed memory is not cleared, so in languages that support "uninitialized variables"—C and C++ come to mind—might end up having traces of those old local variables as their intitial values. The stack starts at the highest addresses in virtual memory, growing downward as functions frames are created, an shrinking upward in virtual memory as the functions exit or return.
The stack section is fairly limited on x64 architecture, with the largest operating systems topping out at about 64MiB or so. Many platforms have much smaller limits for stack space. This small size means that larger allocations should be put on the heap.
Finally, the heap memory is the largest section. Unlike the data or code sections, it grows or shrinks as the program requests it. Unlike the stack, heap memory persists between function calls. Thus, the vast majority of memory use in most programs happens in the heap.
Memory on the heap is generally allocated on demand, with the empty space being managed by a data structure known as a heap.
This allocation is done via the
malloc function in C, or the
new keyword/constructor in most other languages.
This puts the object in the heap, and returns a pointer to the object locally (in the stack frame).
Some languages, like Python, allocate space in the heap for every object, automatically!
This Most Important Picture is a metaphor. It does not explain all behaviors (like how code might be loaded at runtime, or how the system break is manipulated). It does provide a useful jumping-off point for nearly any discussion about a process' image.
A executable program sitting on disk just needs to know what its instructions are, and what static-lifetime data is initialized to. Thus, it contains the code and data sections, which are just copied into memory at load time! Then space is allocated for the BSS, a stack is set up, and the program runs.
If a program needs anything outside of this picture, such as data from the filesystem, or a network socket, or a database connection, then it needs to request it from the operating system via a system call. The executing program can only understand memory, however, so the operating system will write to or read from this most important picture (memory) that the program has access to.
A multithreaded program has multiple Stacks, and all other sections are shared between them. This means local variables (held on the Stack) are independent of each other, but Heap memory objects (or global Data ones) need synchronization to avoid race conditions.
If a program forks off multiple processes, they each have their own Most Important Picture. Since these pictures cover all of a process's virtual memory, two processes cannot interact except through system calls! This might be to share virtual memory, or some other communication mechanism (socket, database, file, message queue, etc.).