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:
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
.
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 a
)
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.
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.
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).
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
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.
Top comments (0)