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: 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:
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
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).
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
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.
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).
3 as the function
Println runs, it acquires the memory that was freed up by the
inc function as shown in the below figure
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
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)
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.
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).
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)
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
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
Therefore, sharing up (returning pointers) typically escapes to the Heap.