Go 内存分配——新对象、指针和逃逸分析

Go memory allocation - new objects, pointers and escape analysis

我读到 Golang 语言以一种聪明的方式管理内存。使用逃逸分析,go在调用new时可能不会分配内存,反之亦然。 golang可以用var bob * Person = & Person {2, 3}这样的写法来分配内存吗?或者总是指针将指向堆栈

指针可能会逃逸到堆中,也可能不会,这取决于您的用例。编译器非常聪明。例如。给定:

type Person struct {
    b, c int
}


func foo(b, c int) int {
    bob := &Person{b, c}
    return bob.b
}

函数foo将被编译成:

    TEXT    "".foo(SB)
    MOVQ    "".b+8(SP), AX
    MOVQ    AX, "".~r2+24(SP)
    RET

这里都在栈上,因为即使bob是一个指针,它也没有脱离这个函数的作用域。

然而,如果我们考虑一个轻微的(尽管是人为的)修改:

var globalBob *Person

func foo(b, c int) int {
    bob := &Person{b, c}
    globalBob = bob
    return bob.b
}

然后bob转义,foo会被编译为:

    TEXT    "".foo(SB), ABIInternal, -24
    MOVQ    (TLS), CX
    CMPQ    SP, 16(CX)
    PCDATA  [=13=], $-2
    JLS     foo_pc115
    PCDATA  [=13=], $-1
    SUBQ    , SP
    MOVQ    BP, 16(SP)
    LEAQ    16(SP), BP
    LEAQ    type."".Person(SB), AX
    MOVQ    AX, (SP)
    PCDATA  , [=13=]
    CALL    runtime.newobject(SB)
    MOVQ    8(SP), AX
    MOVQ    "".b+32(SP), CX
    MOVQ    CX, (AX)
    MOVQ    "".c+40(SP), CX
    MOVQ    CX, 8(AX)
    PCDATA  [=13=], $-2
    CMPL    runtime.writeBarrier(SB), [=13=]
    JNE     foo_pc101
    MOVQ    AX, "".globalBob(SB)
 foo_pc83:
    PCDATA  [=13=], $-1
    MOVQ    (AX), AX
    MOVQ    AX, "".~r2+48(SP)
    MOVQ    16(SP), BP
    ADDQ    , SP
    RET

如您所见,调用 newobject.


这些反汇编清单由 https://godbolt.org/ 生成,适用于 amd64 上的 go 1.16

内存是在栈上分配还是“逃逸”到堆上完全取决于您如何使用内存,而不取决于您如何声明变量。

如果您 return 一个指向堆栈分配变量的指针,例如 C,您的指针的值将在您尝试使用它时无效。这在 Go 中是不可能的,因为你不能明确地告诉 Go 在哪里放置变量。它在选择正确位置方面做得很好,如果它发现对内存块的引用 可能 超出堆栈帧,它将确保分配发生在堆上.

Can golang allocate memory with such a notation

var bob * Person = & Person {2, 3}

Or always the pointer will point to the stack

那行代码不能说“总是”指向堆栈,但有时可能,所以是的,它可能分配内存(在堆上)。

再说一次,重点不在于那行代码,而在于它后面的内容。如果 bob 的值是 returned(Person 对象的地址),那么它不能在堆栈上分配,因为 returned 地址将指向回收的内存。

简单地说,如果编译器可以证明可以在堆栈上安全地创建该值,那么它将(可能)在堆栈上创建。否则会分配到堆上。

编译器用来做这些证明的工具非常好,但它并不总是正确的。但是,大多数时候,担心它的成本与收益并不是真正有益的。