-2

我阅读了 golang 常见问题解答:https ://go.dev/doc/faq#stack_or_heap,我想知道 golang 何时在堆栈或堆上分配变量。所以我编写如下代码:

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
}



然后看看编译器是如何分配内存的。 cmd 是go tool compile "-m" main.go 输出如下:

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

我的问题是:为什么golang不将变量AB分配testStackOrHeap()到堆栈中,它们无法逃逸到堆栈帧,如果分配到堆,gcworker需要收集它,但如果它分配在堆栈中,它将在函数返回时释放。

4

1 回答 1

0

正如@Volker 在评论中指出的那样,堆/堆栈的区别是一个实现细节,转义分析的规则是由编译器定义的,而不是由语言定义的。正确性是编译器最重要的特征,因此编译器的规则通常更倾向于简单性和性能,而不是绝对的“最优性”。

在这种情况下,编译器很可能不知道如何fmt.Printf()处理它接收到的指针。因此,它必须假设指针可能被该函数存储在堆上的某个位置,并且对这两个对象的引用可能会在调用testStackOrHeap(). 因此,它会谨慎行事并将这两个变量提升到堆中。

(请注意,认为它们不会转义的结论可能是基于fmt.Printf()不会存储指针的假设。您是否真的阅读了该函数的源代码以了解它没有?如果没有,您实际上不能确保它没有 - 就像编译器不确定一样。即使该函数的当前版本没有,未来的版本也可能。)

于 2021-11-30T13:46:24.037 回答