Golang uses two types of memory: stack and heap.
new
vs make
new
: Allocates a single memory block, returns a pointer to newly allocated zeroed memory. Used mostly for struct types.make
: Used to create slices, maps, and channels, and allocates memory blocks.package main
func main() {
n := 4
n2 := square(n)
println(n2)
}
func square(n int) int {
return n * n
}
func println(_ int) {
}
There will be:
main
square
Once the square calculation is done, Go does not clear the stack frame. The value of n
is still part of the square
stack frame. It tracks valid and invalid stack frames, marking the square
frame as invalid.
Then, the println
stack frame replaces the square
stack frame, like a self-cleaning process.
package main
func main() {
n := 4
square(&n)
println(n)
}
func square(n *int) int {
return *n * *n
}
func println(_ int) {
}
Escape analysis output:
go build -gcflags="-m -l" test.go
# command-line-arguments
./test.go:9:13: n does not escape
In this case, two stack frames will be created:
main
square
The square
function has a pointer n
which points to the main
stack frame’s value of n
. When using pointers, it stays on the stack itself. This is called “passing pointer down on function typically stays on the stack”.
package main
func main() {
n := 4
n1 := square(&n)
println(*n1)
}
func square(x *int) *int {
y := *x * *x
return &y
}
func println(_ int) {
}
Escape analysis output:
go build -gcflags="-m -l" test.go
# command-line-arguments
./test.go:9:13: x does not escape
./test.go:10:2: moved to heap: y
When the square
function returns a pointer, it returns a pointer from the square
stack frame, which becomes invalid as per stack cleaning. Then how does println
access n1
? The Go compiler understands that y
cannot be part of the stack frame once square
returns, so y
will be part of the heap. This is called “sharing up (returning pointer) typically escapes to heap.”
Only the compiler knows whether memory will be on the heap or stack. Below are a few reasons why values are constructed on the heap instead of the stack:
This explains why the IO reader is written this way:
type Reader interface {
Read(b []byte) (n int, err error)
}
Instead of:
type Reader interface {
Read(n int) (b []byte, err error)
}
With the second approach, there would be many heap allocations, which would be inefficient.
Understanding Allocations: the Stack and the Heap - GopherCon SG 2019