Golang uses two types of memory: stack and heap.
new vs makenew: 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:
mainsquareOnce 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:
mainsquareThe 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