DEV Community

Cover image for Memory allocations in Go
Karan Kumar
Karan Kumar

Posted on

Memory allocations in Go

To understand how memory allocations in Go works, we need to understand the types of memories in programming context, which are Stack and Heap.
If you are familiar with typical memory representation of C, you must already be aware of these two terms.

Stack vs Heap

Stack: The stack is a memory set aside as a scratch space for the execution of thread. When a function in called, a block is reserved on top of the stack for local variables and some bookkeeping data. This block of memory is referred to as a stack frame. Initial stack memory allocation is done by the OS when the program is compiled. When a function returns, the block becomes unused and can be used the next time any function is called (in the world of JS this is similar to function's execution context).
The stack is always reserved in LIFO (last in first out), the most recent block added will be freed(marked as unused) first. The size of the memory allocated to the function and it's variable on the stack is known to the compiler and as soon as the function call is over, the memory is de-allocated.

Heap: In heap there is no particular order to the way the items are placed. Heap allocation requires manual housekeeping of what memory is to be reserved and what is to be cleaned. The heap memory is allocated at the run time. Sometimes the memory allocator will perform maintenance tasks such as defragmenting allocated memory (fragmenting: when small free blocks are scattered, but when request for large memory allocation comes, there is no free memory left in heap even though small free blocks combined may be large. this is bad!) OR garbage collecting (identifying at runtime when memory is no longer in scope and deallocating it).

What makes one faster?

The stack is faster because all free memory is always contiguous. No list needs to be maintained of all the segments of free memory, just a single pointer to the current top of the stack.

In case of goroutines (kind of simultaneously executing go functions) we have multiple stack memories for each go routines as shown in the below figure:

image


Variable allocations

But how do we know which memory will the variable be assigned in the go program out of Stack and Heap?

NOTE: Th term free (in the diagrams) refer to the memory that is acquired by a stack frame (valid memory). And unused means the invalid memory in the stack.

1 Let us consider the following code. The function main has its local variables n and n2. main function is assigned a stack frame in the stack. Same goes for the function square.

image

2 Now as soon as the function square returns the value, the variable n2 will become 16 and the memory function square is NOT cleaned up, it will still be there and marked as invalid (unused).

image

3 Now when the Println function is called, the same unused memory on stack left behind by the square function is consumed by the Println function. (take a look at the memory address for a)

image


With Pointers

1 Let us do the same thing with pointers. Here we have a main function which passes the reference to the variable to a function that increments the value.

image

2 Now as function inc dereferences the pointer and increments the value that it points to, and does its work, the stack frame of inc is again unused/invalid (freed for other functions allocation).

image

3 as the function Println runs, it acquires the memory that was freed up by the inc function as shown in the below figure

image

This is where Go Compiler kicks in 🚩
Sharing down of the variables (passing references) typically stays on the stack. Notice the word typically. This is because, the GO Compiler takes the decision whether a referenced variable needs to stay on the stack or on the heap.

But when would the referenced variable be put on Heap?

Let us understand that.

Let us consider an example where we are returning pointers.

1 So we have a function main that has a variable n who's value is assigned by a function answer which returns a pointer to it's local variable. This is how the stack frame allocation is done initially for both the functions

image

2 Now when the function answer executes and returns the pointer, the address for x is assigned to the variable n in the main function and the answer function's stack frame gets freed up (unused)

image

Here's the catch 🚩

We have a problem here, we have a pointer pointing down to the unused (invalid memory) in the stack. But how is that a problem?

Let us see what happens when the Println function is called.

3 Println function takes the space freed up by the answer function (notice the memory address) and takes reference to the returned value and divides it by 2 (making it 21).

image

This is the problem here, the value which n was pointing to (which was originally 42) has been overwritten by the Println call which made it 21. And since Println took over the memory address of answer function (after answer function freed up the space), now that memory has the value 21 (instead of 42)


What is the solution?

Thanks to Go compiler, we do not have to worry about this. Go compiler is smart enough to handle this.

This is what really happens:

Compiler knows it was NOT safe to leave the pointer variable on the stack. So what it does is, it declares x (from the answer function) somewhere on the Heap

image

This means when the Println function is called, which changes the value to half, it will not clobber the value of x. This is called Escaping To The Heap which is done during the compile time.

Therefore, sharing up (returning pointers) typically escapes to the Heap.

Discussion (0)