What is the stack?

Definitionally, the stack is a region of memory that your program uses to store local variables - the variables you declare inside functions. When your operating system runs your program, it gives you a chunk of memory (usually a few megabytes) and says "here's your stack, use it for your function variables."

Why does it exist? The stack provides automatic memory management. When you declare a variable, memory is allocated. When the function returns, that memory is automatically freed. You don't have to think about it - it just works. This makes the stack fast and safe for temporary data that only needs to exist while a function is running.

Think of the stack like a simple two-column spreadsheet. When you write:

AddressValue
0x7fff10010
0x7fff0fc20
0x7fff0f80x7fff100

That's it. The stack is just:

  • Addresses (memory locations) in the left column
  • Values (what's stored there) in the right column

If only it were that easy.

This is where it get's a little annoying. Let's use a slightly different representation to illustrate how the stack works:

TypeVariable NameAddressValue
intx0x7fff10010
inty0x7fff0fc20
int*ptr_x0x7fff0f80x7fff100

Right now, this analogy of the stack is not technically correct when looking through the lens of the call stack. That's because the stack follows a principle called LIFO (Last In, First Out). This means that the last item added to the stack is the first one to be removed. Since the stack grows downward in memory, newer variables get lower addresses, but should appear at the top of our visualization.

You can think of the stack (LIFO) like a stack of books; you add a book to the top of the stack, and when you need to remove a book, you can only take from the top. You cannot access or remove anything from the middle - only the top book is accessible at any given time.

So, what does the corrected table look like in relation to the code snippet above? The newest item (ptr_x) should be at the top, with the lowest address:

TypeVariable NameAddressValue
int*ptr_x0x7fff0f80x7fff100
inty0x7fff0fc20
intx0x7fff10010

Stack Frames

A stack frame is a chunk of the stack that belongs to a single function call. When you call a function, all of its local variables get grouped together into one frame. When the function returns, that entire frame gets 'popped' off the stack.

Think of it like this: each function gets its own "section" of the spreadsheet, and when the function is done, that entire section disappears.

Before calling double_it(), the stack looks like:

TypeVariable NameAddressValueStack Frame
intnum0x7fff1005main()

While double_it() is running, the stack looks like:

TypeVariable NameAddressValueStack Frame
intresult0x7fff0f410double_it()
intx0x7fff0f85double_it()
return addr(hidden)0x7fff0fc0x400150double_it()
intnum0x7fff1005main()

After double_it() returns, the stack looks like:

TypeVariable NameAddressValueStack Frame
intanswer0x7fff0fc10main()
intnum0x7fff1005main()

Notice how the entire double_it() frame (x, result, NOT the return address) disappeared when the function returned. While double_it() was running, the first thing that gets added to the stack was the return address (the value is 0x400150 because it's pointing to the next instruction in main()). That stack location (0x7fff0fc) eventually becomes the address where answer is stored in main() after the function returns.

The running stack isn't hugely important in this context, but it becomes relevant when trying to understand recursion, stack overflows, memory errors, and other stuff I'll cover some other time.

Which function has the biggest stack frame?

In this example, double_it() has the biggest stack frame with 3 items (return address, x, result), while main() has 2 variables (num, answer).

How can you identify a stack frame?

A stack frame contains:

  • Return address (pushed first - tells the CPU where to jump back to after the function finishes)
  • Function parameters (x in double_it())
  • All local variables declared in that function (result, num, answer)

The boundary? When a function returns, everything from the top of the stack down to where that function started gets removed. That's the frame. The return address is used to know where to continue execution, then the entire frame is popped off. So in this example, there were 2 stack frames:

  • main() frame: num, answer
  • double_it() frame: return address, x, result