英文:
Go memory allocation - new objects, pointers and escape analysis
问题
我读到了Golang语言以智能的方式管理内存。通过逃逸分析,当调用new时,Go语言可能不会分配内存,反之亦然。Golang是否可以使用var bob *Person = &Person{2, 3}
这样的表示法来分配内存,还是指针总是指向堆栈?
英文:
I read that Golang language manages memory in a smart way. Using escape analysis, go may not allocate memory when calling new, and vice versa. Can golang allocate memory with such a notation var bob * Person = & Person {2, 3}
. Or always the pointer will point to the stack
答案1
得分: 5
指针可能会逃逸到堆上,也可能不会,这取决于你的使用情况。编译器非常聪明。例如,给定以下代码:
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-24
MOVQ (TLS), CX
CMPQ SP, 16(CX)
PCDATA $0, $-2
JLS foo_pc115
PCDATA $0, $-1
SUBQ $24, SP
MOVQ BP, 16(SP)
LEAQ 16(SP), BP
LEAQ type."".Person(SB), AX
MOVQ AX, (SP)
PCDATA $1, $0
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 $0, $-2
CMPL runtime.writeBarrier(SB), $0
JNE foo_pc101
MOVQ AX, "".globalBob(SB)
foo_pc83:
PCDATA $0, $-1
MOVQ (AX), AX
MOVQ AX, "".~r2+48(SP)
MOVQ 16(SP), BP
ADDQ $24, SP
RET
正如你所看到的,它调用了newobject
函数。
这些反汇编列表是由https://godbolt.org/生成的,适用于amd64架构上的Go 1.16版本。
英文:
The pointer may escape to the heap, or it may not, depends on your use case. The compiler is pretty smart. E.g. given:
type Person struct {
b, c int
}
func foo(b, c int) int {
bob := &Person{b, c}
return bob.b
}
The function foo
will be compiled into:
TEXT "".foo(SB)
MOVQ "".b+8(SP), AX
MOVQ AX, "".~r2+24(SP)
RET
It's all on the stack here, because even though bob
is a pointer, it doesn't escape this function's scope.
However, if we consider a slight (albeit artificial) modification:
var globalBob *Person
func foo(b, c int) int {
bob := &Person{b, c}
globalBob = bob
return bob.b
}
Then bob
escapes, and foo
will be compiled to:
TEXT "".foo(SB), ABIInternal, $24-24
MOVQ (TLS), CX
CMPQ SP, 16(CX)
PCDATA $0, $-2
JLS foo_pc115
PCDATA $0, $-1
SUBQ $24, SP
MOVQ BP, 16(SP)
LEAQ 16(SP), BP
LEAQ type."".Person(SB), AX
MOVQ AX, (SP)
PCDATA $1, $0
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 $0, $-2
CMPL runtime.writeBarrier(SB), $0
JNE foo_pc101
MOVQ AX, "".globalBob(SB)
foo_pc83:
PCDATA $0, $-1
MOVQ (AX), AX
MOVQ AX, "".~r2+48(SP)
MOVQ 16(SP), BP
ADDQ $24, SP
RET
Which, as you can see, invokes newobject
.
These disassembly listings were generated by https://godbolt.org/, and are for go 1.16 on amd64
答案2
得分: 2
无论内存是在堆栈上分配还是“逃逸”到堆上,完全取决于你如何使用内存,而不是你如何声明变量。
如果你在C语言中返回指向堆栈分配变量的指针,当你尝试使用它时,指针的值将无效。在Go语言中,这是不可能的,因为你不能明确告诉Go语言将变量放在哪里。它非常擅长选择正确的位置,如果它发现对一块内存的引用可能超出堆栈帧的生命周期,它将确保在堆上进行分配。
> Go语言是否可以使用这样的表示法分配内存
>
> var bob *Person = &Person{2, 3}
>
> 或者指针总是指向堆栈
不能说这行代码总是指向堆栈,但有时可能指向堆栈,所以是的,它可能会分配内存(在堆上)。
再次强调,问题不在于这行代码,而在于它之后的代码。如果返回bob
的值(Person
对象的地址),那么它不能在堆栈上分配,因为返回的地址将指向已回收的内存。
英文:
Whether memory is allocated on the stack or "escapes" to the heap is entirely dependent on how you use the memory, not on how you declare the variable.
If you return a pointer to a stack-allocated variable in, say, C, the value your pointer will be invalid by the time you attempt to use it. This isn't possible in Go, because you cannot explicitly tell Go where to place a variable. It does a very good job of choosing the correct place, and if it sees that references to a blob of memory may live beyond the stack frame, it will ensure that allocation happens on the heap instead.
> Can golang allocate memory with such a notation
>
> var bob * Person = & Person {2, 3}
>
> Or always the pointer will point to the stack
That line of code cannot be said "always" point to the stack, but it might sometimes, so yes, it may allocate memory (on the heap).
Again, it's not about that line of code, it's about what comes after it. If value of bob
is returned (the address of the Person
object) then it cannot be allocated on the stack because the returned address would point to reclaimed memory.
答案3
得分: 2
简单来说,如果编译器能够证明该值可以安全地在栈上创建,它就会(可能)在栈上创建。否则,它将在堆上分配。
编译器用于进行这些证明的工具相当不错,但并不总是完全正确。不过,大多数情况下,担心这个问题的成本与收益并不真正有益。
英文:
Put simply, if the compiler can prove that the value can safely be created on the stack, it will (probably) be created on the stack. Otherwise, it will be allocated on the heap.
The tools that the compiler has to do these proofs are pretty good, but it doesn't get it right all the time. Most of the time, though, cost vs benefit of worrying about it is not really benefitial.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论