madhadron

What are the stack and the heap?

by Fred Ross Last updated: January 2, 2021

This evening someone asked, “what are the stack and the heap?” This isn’t the first time I’ve taught someone this, and I think I’ve found a decent way to teach it, so it’s probably time to write it down.

Let’s go all the way back to the begining. We’re going to do this in something vaguely like really old BASIC. Consider the following program:

10 PRINT "HELLO"
20 GOTO 10

If you haven’t seen old BASIC before, this is read and run one line at a time. The number at the beginning of the line is a way of labelling the lines (called, imaginatively, the “line number”). The exact numbers don’t matter so long as the number of each line is greater than the number of the line before it. The GOTO N statement jumps so that the program runs line N next and continues from line N. This program prints HELLO over and over until we stop it.

In modern languages we can write functions that we can call over and over again. Let’s try to put together something like that in this minimal substrate. First, let’s write a hunk of code that we can jump to, run the lines in it, and jump back to where we came from. We can put it at a fixed line number, but we need to know where to jump back, so we will define a variable RETURN_ADDRESS where we will store the line number to jump back to.

10 DIM RETURN_ADDRESS

100 LET RETURN_ADDRESS = 120
110 GOTO 1000
120 PRINT " ALICE"
130 NEWLINE
140 LET RETURN_ADDRESS = 160
150 GOTO 1000
160 PRINT " BOB"
170 NEWLINE
180 EXIT

1000 PRINT "HELLO"
1010 GOTO RETURN_ADDRESS

The statment DIM just declares a variable with the given name. This program run lines 10, 100, 110, then jumps to 1000. At 1000, it prints HELLO, then at 1010 it looks up the number in RETURN_ADDRESS (set at line 100 to 120) and jumps to that line. At 120 it prints ALICE and then a newline. Then does the same thing, but this time having line 1010 jump back to line 160 to print HELLO BOB then exits.

We can reuse the hunk of code at line 1000, but what happens if we want to call another hunk from that hunk? If both hunks rely on GOTO RETURN_ADDRESS, then we will have something like

10 DIM RETURN_ADDRESS

100 LET RETURN_ADDRESS = 120
110 GOTO 1000
120 EXIT

1000 RETURN_ADDRESS = 1010
1010 GOTO RETURN_ADDRESS

2000 PRINT "HELLO"
2010 GOTO RETURN_ADDRESS

Which ends up infinitely looping at line 1010. We could have separate variable for the return address for each hunk, but we would like to be able to write recursive hunks eventually, and we can’t use separate variables for that. Instead, we’re going to introduce a stack.

Note. If you don’t know what a stack is, think of it like a stack of cards. You can write a number on a card and put it on the top of the stack, or take a card off the top of the stack and read the number on it. Putting a card on top is calling pushing onto the stack and taking a card off is called popping it from the stack.

With a stack for return addresses, our previous program will work.

10 DIM RETURN_STACK

100 LET RETURN_STACK = STACK()
110 RETURN_STACK.PUSH(130)
120 GOTO 1000
130 EXIT

1000 PRINT "HELLO "
1010 RETURN_STACK.PUSH(1030)
1020 GOTO 2000
1030 GOTO RETURN_STACK.POP()

2000 PRINT "WORLD"
2010 NEWLINE
2020 GOTO RETURN_STACK.POP()

Again, we run 10, then 100, then at 120 we jump to 1000 and print HELLO. We push another address onto the RETURN_STACK, and jump to 2000. At 2000 we print WORLD, and then pop the top address off RETURN_STACK and jump to it (in this case 1030). At 1030 we do the same thing, but this time we get 130. At 130 we exit.

Next we’re going to pass behavior to hunks.

10 DIM RETURN_STACK
20 DIM FIRST_NAME
30 DIM LAST_NAME
40 DIM FULL_NAME

100 LET RETURN_STACK = STACK()
110 LET FIRST_NAME = "ALICE"
120 LET LAST_NAME = "SMITH"
130 LET FULL_NAME = "EDDIE THE BABOON"
140 RETURN_STACK.PUSH(160)
150 GOTO 1000
160 PRINT "HELLO " + FULL_NAME
170 NEWLINE
180 EXIT

1000 LET FULL_NAME = FIRST_NAME + " " + LAST_NAME
1010 PRINT FULL_NAME
1020 NEWLINE
1030 GOTO RETURN_STACK.POP()

We would like this program to print

HELLO ALICE SMITH
HELLO EDDIE THE BABOON

But FULL_NAME gets clobbered on line 1000. This isn’t exactly wrong, but it is certainly very inconvenient, especially when programs get large. We need a way to create a scratch pad for each hunk we invoke so we don’t get this kind of collision. We could explore possibilities, but having already gone through the exercise of arriving at a stack for return values, let’s just do the same thing here. This time we will make a stack of dictionaries (or maps or objects)…a set of key-value pairs.

10 DIM RETURN_STACK
20 DIM DATA_STACK
30 DIM THIS_STATE
40 DIM FIRST_NAME
50 DIM LAST_NAME
60 DIM FULL_NAME

100 LET RETURN_STACK = STACK()
110 LET DATA_STACK = STACK()
120 LET FIRST_NAME = "ALICE"
130 LET LAST_NAME = "SMITH"
140 LET FULL_NAME = "EDDIE THE BABOON"

200 PRINT "HELLO "
210 RETURN_STACK.PUSH(230)
220 GOTO 1000
230 PRINT "HELLO " + FULL_NAME
240 NEWLINE
250 EXIT

1000 DATA_STACK.PUSH(THIS_STATE)
1010 LET THIS_STATE = {}
1020 LET DATA_STACK["FULL_NAME"] = FIRST_NAME + " " + LAST_NAME
1030 PRINT FULL_NAME
1040 NEWLINE
1050 LET THIS_STATE = DATA_STACK.POP()
1060 GOTO RETURN_STACK.POP()

Now we get the output we expect. If we make every hunk of code push whatever THIS_STATE was to DATA_STACK and then set it to an empty dictionary, only set values in that dictionary, then restore whatever it was from the stack at the end, we will not clobber anything outside. This stack of states is what people mean when they speak of allocating memory on “the stack.”

If that is the stack, then what is the heap? The heap appears when we need to work with data that is not lost when we return from a hunk of code. For example, say we want to append messages to various logs as our program runs. We need a place to write those values so we can find them. Here is a program that has three hunks of code to create a log under a given name, append to it, and finally print and delete it.

10 DIM RETURN_STACK
20 DIM DATA_STACK
30 DIM HEAP
40 DIM LOG_NAME
50 DIM LOG_LINE

60 LET RETURN_STACK = STACK()
70 LET DATA_STACK = STACK()
80 LET HEAP = {}
100 LOG_NAME = "DEBUG"
110 RETURN_STACK.PUSH(130)
120 GOTO 1000
130 LOG_NAME = "AUDIT"
140 RETURN_STACK.PUSH(160)
150 GOTO 1000
160 LOG_NAME = "DEBUG"
170 LOG_LINE = "DOING SOMETHING 1"
180 RETURN_STACK.PUSH(200)
190 GOTO 2000
200 LOG_LINE = "DOING SOMETHING 2"
210 RETURN_STACK.PUSH(230)
220 GOTO 2000
230 LOG_NAME = "AUDIT"
240 LOG_LINE = "ACCESS RECORDED"
250 RETURN_STACK.PUSH(270)
260 GOTO 2000
270 RETURN_STACK.PUSH(290)
280 GOTO 3000
290 LOG_NAME = "DEBUG"
300 RETURN_STACK.PUSH(320)
310 GOTO 3000
320 EXIT

# Allocate a structure
1000 HEAP[LOG_NAME] = []
1010 GOTO RETURN_STACK.POP()

# Append to log
2000 DATA_STACK.PUSH(THIS_STATE)
2010 LET THIS_STATE = {}
2020 LET THIS_STATE["MESSAGE"] = TIMESTAMP() + " " + LOG_LINE
2030 HEAPS[LOG_NAME].APPEND(THIS_STATE["MESSAGE"])
2040 THIS_STATE = DATA_STACK.POP()
2050 GOTO RETURN_STACK.POP()

# Print and deallocate
3000 PRINT LOG_NAME
3010 NEWLINE
3020 PRINT HEAPS[LOG_NAME]
3030 DELETE HEAP[LOG_NAME]
3040 GOTO RETURN_STACK.POP()

This program first allocates two logs on the heap (the jumps to 1000), then appends several log lines (the jumps to 2000), and finally prints and deletes them (jumps to 3000).

It’s clear why we would refer to “the stack” by that name (it’s a stack of scratch pads). Why is “the heap” so called? It comes from how this variable we have called HEAP is actually implemented. In our program we have assumed that we have dictionaries, but that is already a very sophisticated data structure. In practice we actually begin with a big block of memory. When we allocate a variable on the heap, we carve a piece of the right size off the beginning of that block. As we allocate more, we pull more off the block. When we delete variables, we want to be able to reuse the space, so the free space is often tracked in a data structure called a heap, where the first block that is big enough for a variable will get reused. Modern allocators are often far more sophisticated, but we persist in calling the memory that is being managed this way “the heap.”

So: “the stack” refers to memory used by a hunk of code’s local variables, discarded as soon as we return. “The heap” refers to memory managed separately from jumping from and returning to code.