Managing memory using stack and heap: all you need to know

Ehab Alsharif
Geek Culture
Published in
5 min readApr 11, 2022

Both the stack and heap are dedicated memory regions that are used for allocating and deallocating data dynamically — there are other memory regions that could be used for allocating static data upon the initialization of a process.

In this article we are going to understand the difference between the stack and the heap, and how they are often used by the different programming languages both interpreted and compiled. We are also going to see which data typically gets stored in the stack, and which data typically gets stored in the heap, and why.

The stack

A visual representation of a stack

A stack is a LIFO (Last in First out) memory region typically used for storing information necessary for executing functions, this includes: local variables, return addresses, parameters passed for callees, and whatever an implementer might decide.

Calling a function causes the stack to grow in size by a layer that is called a stack frame, this layer will have the necessary information to execute this specific function, and to return the execution to the caller function.

Because operating on the stack happens in a LIFO fashion, it means that allocating and deallocating is just a matter of incrementing/decrementing an index by a specific length — modern cpu architectures provide dedicated registers and instruction sets for manipulating the stack(s).

If you inspect the previous statement you will see that it essentially means the following:

  • You can only allocate data that you know its size beforehand — this theoretically works for dynamic data, for example the interpreter/compiler might allocate an array initially filled with one element on the stack if the interpreter/compiler can see that through the entire life time of a function no more elements than four can be added
  • Allocating and deallocating is pretty straightforward and fast, it’s just a matter of changing indexes
  • Reading and modifying information in the stack is also straightforward and fast, it’s a matter of index accessing

Essentially these three points are the defining characteristics of a stack when viewed from a programming language point of view, and what it means is that data of fixed size for example: integers, strings are typically stored on the stack.

Whether or not a programming language might decide to only put fixed sized data or dynamic sized data is an implementation detail, but in general many programming languages will decide to only put fixed sized data on the stack, because it is not easy/feasible to determine the max size of a dynamic sized data.

Look at the following function that is written in an imaginative language — that just looks similar to javascript:

function mySum() {  const arr = []  arr.push(1, 2);  return arr[0] + arr[1]}

The array arr here is of a dynamic size but the compiler/interpreter might decide to see that it is has a max size of 2, it is up to the implementation to allocate the array on the stack or the heap, but our functions are typically much more complex so think of this example:

function myMultiplication() {  const arr = [1,2]  return multi(arr)}

Since the array has dynamic size we can not know what multi will do with it. Essentially while allocating on the stack is all about knowing the max size of a certain data, in practice differentiating between initial size and max size is hard, so typically only fixed initial sized data is allocated on the stack. Of course there might always be some exceptions, for example Variable length arrays in C are allocated on the stack even though they are dynamic sized data.

The stack is completely different from the call stack, the call stack is language dependent and what it does and how it does it vary widely between different languages. The stack(s) is a LIFO memory region, it is suitable for implementing certain call stacks functionality , but it can also be used for whatever the implementer might see fit, as for example reversing a string.

A call stack is often associated with the stack, because it makes sense to back the functionality of a call stack using a stack, but that is not always the case and a stack alone might not be enough.

Let’s check this javascript code as an example:

function outer() {
const z = 3;
function inner() {
console.log(z)
}
setTimeout(inner, 10)
}

Now if you run outer, what should happen? When inner runs, outer has already finished its execution, should inner throw an error or just log 3?

If the Javascript memory model is to allocate the z variable on the stack then when outer finishes execution z will be deallocated, and inner should throw an error. However inner will actually log 3, essentially this is an example of a closure, and closures are a feature that are not supported directly by stacks; something more than the stack should be used to implement it, or even implement it completely without the stack.

The heap

If the stack is typically used for allocating fixed size data, then the heap is typically used for allocating dynamic sized data. Unlike the stack, allocating and deallocating requires much more than just incrementing/decrementing an index.

Both the heap and the stack are contiguous regions in memory, typically they grow in opposite directions of each other, and are placed next to each other.

Stack and heap representation in memory
An example of stack and heap layout in a memory region

This means you can easily visualize both of them as an array. Let’s say as an example that our heap is an array of 100 slots from 0 to 99. Let’s say the process requests 10 slots, then 20, then 30, and then the process requests that the first 10 slots are deallocated. So we will have something like this [10 empty slots, 20 used slots, 30 used slots, 40 empty slots], now imagine that the process needs 50 linear slots, our heap has 50 empty slots (the first 10 and the last 40), but now while the heap has 50 empty slots it can not grant them to the process.

What i have just described is an issue known as memory fragmentation, this issue must be solved to make heap allocation efficient, and solving it requires overhead when both allocating and deallocating, and this is the main reason why allocating and deallocating in the stack is much more efficient — of course other than that allocating and deallocating in the stack might have hardware support.

Because of this overhead, modern programming languages try to dynamically allocate and deallocate whatever is easy/feasible on the stack, and everything else on the heap.

Was this article useful for you? Clap 👏 it, and help others find this article.

--

--