Introduction
Heap memory, also known simply as the “heap”, is a critical region of a computer’s memory that provides dynamic memory allocation. This is particularly useful when creating complex data structures, such as linked lists or hashmaps, as it is often not possible to determine the sizes of these structures at compile time.
Program Execution
When a program is compiled, the resulting binary file typically consists of several sections that serve different purposes. The exact sections and their characteristics can vary depending on the compiler and platform, but there are several common sections found in compiled files.
.text section: This section contains the executable code of the program. It is loaded into memory (usually at the low memory addresses) when the program starts and remains in memory for the duration of the program’s execution. This memory region is also marked read-only to prevent the program from accidentally modifying its own instructions.
.data and .bss sections: The
.data
section contains initialized global variables (i.e., variables that have been given a value before the program starts), while the.bss
section contains uninitialized global variables. Because these variables haven’t been given a value yet, the.bss
section just needs to keep track of how much space will be needed for them when the program starts. Both of these sections are usually placed after the.text
secion.
When a program is launched, the operating system creates a process for it. This process is given a block of virtual memory that is divided into several segments. This includes the sections mentioned above, as well as the stack and heap sections.
Let’s walk through the life cycle of a program, from its initial launch to its eventual termination.
Program Start: When the program starts, it begins to execute instructions contained in the
.text
segment. The stack, at this point, is empty or contains minimal data, and the heap is uninitialized.Stack Memory: As functions are called within the program, a new stack frame is pushed onto the stack for each call. This frame contains function parameters, local variables, and return addresses. The stack grows and shrinks automatically (as it is managed by the operating system) as functions are called and returned. Each thread in a process has its own stack.
Heap Memory: The heap, in contrast, is used for dynamic memory allocation. It starts out empty and grows as memory is allocated. When a program requests a block of memory via a function like
malloc()
in C ornew
in C++, a block of heap memory of the requested size is allocated and a pointer to this memory block is returned. This memory is not automatically freed and it’s up to the programmer to manually deallocate this memory when it’s no longer needed. A memory leak occurs when a program allocates memory on the heap but fails to deallocate it, resulting in a gradual reduction of available memory. Over time, this can lead to significant performance degradation or even program failure if the system runs out of memory.Program Execution: As the program continues to run, it may allocate and deallocate memory on the heap as needed. The stack will continue to grow and shrink as functions are called and returned. If a function or a group of nested functions require more stack space than is available, a stack overflow error will occur. Similarly, if the program attempts to allocate more memory on the heap than is available, a memory allocation failure will occur.
Program Termination: When the program finishes execution, all memory associated with the process, including stack and heap memory, is reclaimed by the operating system. While it’s good practice to clean up (i.e., deallocate) all dynamically allocated memory, modern operating systems will clean up any memory that was not deallocated when a process terminates.