Memory Management

Aside from file I/O, both the C library and POSIX provide different mechanisms for managing a program’s memory.

Memory Model

The conventional program memory model divides a program’s address space into different data segments that serve different purposes:

Code

Read-only executable code (.text). This segment contains actual instructions; read-only data such as small constants and even short strings are sometimes also embedded in the code segment as immediate operands to instructions.

Data

Initialized static variables (.data and .rodata). In C, this region corresponds to file-scope declarations and static variables declared inside functions. On many systems, a read-only data segment is used to store read-only data, such as string literals and constant values.

Block Starting Symbol (BSS)

Uninitialized static variables (.bss). In an actual program image, only the length of the .bss region is stored, and on POSIX systems it is zero-filled when the operating system loads the program into memory. In C, this region is used for zero-initialized static variables–recall that uninitialized statics are all zero-initialized by default.

Heap

The heap is a region of storage which is used to dynamically allocated storage which is too large to fit on the stack, needs to outlive a particular function, etc.

Dynamic Libraries

External code and data which the program depends on, and is loaded into memory by the operating system when the program is executed. For example, the C standard library is often implemented as a dynamic library. Typically consist of several sub-segments corresponding to self-contained analogues of the Code, Data, and BSS sections listed above.

Stack

The stack contains a growing sequence of function frames, which themselves consist of a return address, function arguments, and any function local variables. Typically the stack has a fixed maximum size, which can lead to program runtime errors when exceeding the limit due to large stack allocations and/or deeply nested function call sequences, such as runaway recursion.

Conventionally, the segments are located in memory in the order shown above, with the code, data, bss, and heap segments located at the lower end of the program’s memory space, and the stack located at its upper end. Thus, as the heap grows to contain more allocated data, it is said to grow upwards towards the stack, and as the stack “grows” to contain more function frames, it “grows” downwards towards the heap. Dynamic libraries are typically located between the heap and stack, somewhere in the middle of the program’s address space.

While some facilities exist to dynamically allocate objects on the stack–such as C’s controversial Variable Length Arrays (VLAs)–in general the stack only holds fixed sized objects, so that the location of each function local variable can be taken as a fixed offset from a global stack pointer value. Many other aspects of the system are much simpler when function frames all have fixed size determined at compile-time, and generally performance is better as well. On the other hand, the heap does not have this limitation, and can be used to store dynamically allocated data of arbitrarily large sizes.