how to know golang allocated variable on the heap or the stack?

3.2k Views Asked by At

i read the golang FAQ:https://go.dev/doc/faq#stack_or_heap,i want to know when golang allocate variable on stack or heap. so i write code like below :

package main

import (
    "fmt"
)


type Object struct {
    Field int
}

func main() {
    A := Object{1}
    B := Object{2}
    fmt.Println(A,B)
    //fmt.Printf("A:%p;B:%p\n",&A,&B)
    //m := testStackOrHeap()
    //C:=m[A]
    //D:=m[B]
    //fmt.Printf("C:%p;D:%p\n",&C,&D)
}
//go:noinline
func testStackOrHeap() map[Object]Object {
    one:=1
    two:=2
    A := Object{one}
    B := Object{two}
    C:= Object{one}
    D := Object{two}
    fmt.Println(C,D)
    fmt.Printf("A:%p;B:%p\n",&A,&B)
    m := map[Object]Object{A: A, B: B}
    return m
}



then see how the compiler allocate the memory .the cmd is go tool compile "-m" main.go the output is below :

main.go:15:13: inlining call to fmt.Println
main.go:30:13: inlining call to fmt.Println
main.go:31:12: inlining call to fmt.Printf
main.go:15:13: A escapes to heap
main.go:15:13: B escapes to heap
main.go:15:13: []interface {} literal does not escape
main.go:26:2: moved to heap: A
main.go:27:2: moved to heap: B
main.go:30:13: C escapes to heap
main.go:30:13: D escapes to heap
main.go:30:13: []interface {} literal does not escape
main.go:31:12: []interface {} literal does not escape
main.go:32:24: map[Object]Object literal escapes to heap
<autogenerated>:1: .this does not escape

my question is: why not golang allocate variable A B in testStackOrHeap() to the stack ,they can not escape to stackframe ,if it allocate to heap , the gcworker need to collect it,but if it allocate in stack, it will release when function return.

1

There are 1 best solutions below

2
Aasmund Eldhuset On

As @Volker pointed out in a comment, the heap/stack distinction is an implementation detail, and the rules for escape analysis are defined by the compiler, not by the language. Correctness is the most important trait of a compiler, so a compiler's rules will frequently favor simplicity and performance over absolute "optimalness".

In this case, it's quite likely that the compiler doesn't know what fmt.Printf() will do with the pointers it receives. Therefore, it has to assume that the pointers might be stored somewhere on the heap by that function and that the references to those two objects might thus survive the call to testStackOrHeap(). Therefore, it errs on the side of caution and promotes those two variables to the heap.

(Note that your conclusion that they do not escape was presumably based on an assumption that fmt.Printf() won't store the pointers. Did you actually read the source code of that function to learn that it doesn't? If not, you can't actually be sure that it doesn't - just like the compiler isn't sure. And even if the current version of that function doesn't, future versions might.)