Your-paragraph-text.png

Stack Frames Explained from First Principles

A high-level program with large functions that call many other functions often executes instantaneously. But underneath, every time you call a function, the program quietly performs a series of operations. It allocates memory, stores the function’s parameters, remembers where to return after the function execution, and maintains the information needed to resume execution correctly. The underlying layer responsible for this is the stack frame.

In this article, we will take a deep dive into the fundamentals of computer organisation to understand the concept of a stack frame. We will explore how your computer jumps between function calls without losing its place, mixing up variables, or losing track of how the program should execute after a function returns.

Why is a stack frame needed?

Let’s understand it using a simple analogy.

Imagine you are cooking pasta by following a recipe from a book. Step 2 says “Make garlic sauce” (see page 22). Before moving to page 22, you put a sticky note on step 2 so that you don’t lose track of the main recipe. On page 22, step 3 says “Mix garlic and tamarind as prescribed” (see page 45). Again, you put a sticky note on page 22 and jump to page 45. After mixing the garlic and tamarind, you return to page 22, remove the sticky note, prepare the garlic sauce, and then return to Step 3 of the main recipe.

Now, coming back, the stack frame is just like the sticky note in your computer brain. When a program calls a function, the computer needs a way to remember where it was before it got interrupted and what temporary variable it needs for the current task.

Memory topography inside a computer.

A program’s virtual memory space is divided into several segments, namely text (code), data, BSS, heap, and stack.

To execute a program, the computer uses a combination of CPU hardware and virtual memory. The RAM contains the text segment that stores only the read-only machine code instructions. The data segment stores the pre-initialized global/static variables, whereas the BSS Segment stores uninitialized global/static variables. 

The stack and heap face towards each other with unallocated memory in the middle. Here, the purpose of the stack is to store short-term function memory ( such as return addresses, frame anchors, local variables), and the heap stores dynamic, manually allocated memory.

The CPU side holds registers that are tiny memory slots used as pointers to the virtual memory. It has three registers, namely RIP, RSP, and RBP.

Diagram comparing CPU registers on the left with RAM virtual memory topology on the right. It shows RIP pointing to the Text Segment, RSP pointing to the top of the Stack, and RBP pointing to the Stack frame base anchor. It highlights the Stack growing downward and the Heap growing upward toward each other.
Interaction between hardware CPU registers (RIP, RSP, RBP) and the segments of virtual memory in RAM.

 

RIP is the instruction pointer and points directly to the current line of machine code inside the Text segment that the CPU is currently executing. RSP points to the top element in the current stack frame, followed by RBP, which points to a fixed base anchor inside the active function frame in RAM, making it easy to locate local variables.

Every time a function is invoked, the computer carves a dedicated workspace on the stack called a stack frame. Let us now understand how it works in a live execution environment.
Consider the program below.

				
					#include <stdio.h>
void inner_function(int value) {
    printf("Executed with value: %d\n", value);
}
void outer_function(void) {
    inner_function(10);
}
int main(void) {
    outer_function();
    return 0;
}
				
			

When execution reaches inside inner_function(10), the stack frames pile from High Memory down to Low Memory as shown below.

Stack frame diagram showing memory layout from High Memory to Low Memory during nested function calls (main calling outer_function, calling inner_function). It details saved return addresses (Saved RIP), saved frame pointers (Saved RBP), passed arguments, and active register positions for RBP and RSP.
The complete stack frame structure in RAM during nested function execution, showing how Saved RIP and Saved RBP preserve execution state across calls.

 

The above program has multiple function calls. It all begins when the operating system starts your program. The C runtime then calls main(). Before main() begins executing, the return address (stored in the RIP register) is pushed onto the stack, telling the CPU where to resume execution after main() returns. This is followed by the saved frame pointer (RBP), which stores the caller’s stack frame so it can be restored when main() finishes.

Then main() jumps to outer_function, after dropping a Return Address (Saved RIP) onto the stack pointing back into main(). Once inside outer_function(), the function saves main’s Base Pointer (Saved RBP) so it remembers main()’s anchor point, then sets up its own frame space directly below it.

Next, outer_function() calls inner_function() and hands it the value 10. Just like before, the computer drops two safety notes onto the stack.

  • Return Address (Saved RIP): A bookmark pointing back to where execution paused inside outer_function().
  • Saved Frame Pointer (outer’s RBP): A note remembering outer_function()’s anchor point.

Now, inner_function() is actively running at the very bottom of the stack. The RBP (Base Pointer) locks onto inner_function()’s anchor point, while the RSP (Stack Pointer) marks the current edge of the stack, where new data is pushed and removed during execution.

When inner_function() finishes, the computer simply follows its saved bookmarks in reverse. First, it restores the saved RBP (Base Pointer), moving RBP back to outer_function()’s stack frame. This re-establishes the caller’s frame as the active one. Next, it restores the saved RIP (Return Instruction Pointer), causing the CPU to jump back to the exact instruction in outer_function() immediately after the call to inner_function().

With the stack frame for inner_function() removed, control seamlessly returns to the caller, and the program continues executing as if nothing had interrupted it.