Go内存布局与C++/C相比如何?

huangapple go评论73阅读模式
英文:

Go memory layout compared to C++/C

问题

在Go语言中,似乎没有构造函数的概念,但建议使用一个函数来分配一个结构体类型的对象,通常命名为"New" + TypeName,例如:

func NewRect(x, y, width, height float) *Rect {
    return &Rect{x, y, width, height}
}

然而,我不确定Go语言的内存布局。在C/C++中,这种代码意味着你返回一个指向临时对象的指针,因为变量是在栈上分配的,函数返回后变量可能是一些垃圾值。在Go语言中,我需要担心这种情况吗?因为似乎没有标准说明哪种数据将被分配在栈上,哪种数据将被分配在堆上。

在Java中,有一个明确指出基本类型(如int、float)将被分配在栈上,其他派生自对象的对象将被分配在堆上。在Go语言中,是否有类似的说明?

英文:

In Go, it seems there are no constructors, but it is suggested that you allocate an object of a struct type using a function, usually named by "New" + TypeName, for example

func NewRect(x,y, width, height float) *Rect {
     return &Rect(x,y,width, height)
}

However, I am not sure about the memory layout of Go. In C/C++, this kind of code means you return a pointer, which point to a temporary object because the variable is allocated on the stack, and the variable may be some trash after the function return. In Go, do I have to worry such kind of thing? Because It seems no standard shows that what kind of data will be allocated on the stack vs what kind of data will be allocated on the heap.

As in Java, there seems to have a specific point out that the basic type such as int, float will be allocated on the stack, other object derived from the object will be allocated on the heap. In Go, is there a specific talk about this?

答案1

得分: 32

复合字面量部分提到:

> 获取复合字面量的地址(§地址运算符)会生成一个指向字面量值实例的唯一指针。

这意味着New函数返回的指针将是有效的(在堆栈上分配)。
调用

> 在函数调用中,函数值和参数按照通常的顺序进行评估。
在它们被评估之后,调用的参数按值传递给函数,并且被调用的函数开始执行。
函数的返回参数在函数返回时通过值传递回调用函数

你可以在这个答案这个线程中了解更多信息。

正如在“Go中的堆栈与堆分配以及它们与垃圾回收的关系”中提到的:

> 值得注意的是,“堆栈”和“堆”这两个词在语言规范中并没有出现。


博客文章“Go中的逃逸分析”详细介绍了发生的情况,提到了常见问题

> 在可能的情况下,Go编译器将在函数的堆栈帧中分配局部变量。
然而,如果编译器无法证明变量在函数返回后不被引用,那么编译器必须将变量分配在垃圾回收的堆上,以避免悬空指针错误。
此外,如果一个局部变量非常大,将其存储在堆上而不是堆栈上可能更合理。

博客文章补充道:

> 执行“逃逸分析”的代码位于**src/cmd/gc/esc.c**中。
从概念上讲,它试图确定一个局部变量是否逃逸了当前作用域;唯一两种发生逃逸的情况是当变量的地址被返回时,以及当其地址被分配给外部作用域中的变量时。
如果一个变量逃逸了,它必须在堆上分配;否则,将其放在堆栈上是安全的。

> 有趣的是,这也适用于new(T)分配。
如果它们不逃逸,它们最终将被分配在堆栈上。下面是一个例子来阐明这个问题:
>
> var intPointerGlobal *int = nil
>
> func Foo() *int {
> anInt0 := 0
> anInt1 := new(int)
>
> anInt2 := 42
> intPointerGlobal = &anInt2
>
> anInt3 := 5
>
> return &anInt3
> }
>
> 在上面的例子中,anInt0anInt1不逃逸,所以它们在堆栈上分配;
anInt2anInt3逃逸,并在堆上分配。


另请参阅“使Go快速的五个因素”:

> 与C不同,C通过malloc强制你选择一个值是存储在堆上还是通过在函数作用域内声明它存储在堆栈上,Go实现了一种称为逃逸分析的优化。

> Go的优化默认始终启用。
你可以使用**-gcflags=-m**开关查看编译器的逃逸分析和内联决策。

> 由于逃逸分析是在编译时而不是运行时执行的,所以堆栈分配将始终比堆分配更快,无论你的垃圾回收器有多高效。

英文:

The Composite Literal section mentions:

> Taking the address of a composite literal (§Address operators) generates a unique pointer to an instance of the literal's value.

That means the pointer returned by the New function will be a valid one (allocated on the stack).
Calls:

>In a function call, the function value and arguments are evaluated in the usual order.
After they are evaluated, the parameters of the call are passed by value to the function and the called function begins execution.
The return parameters of the function are passed by value back to the calling function when the function returns.

You can see more in this answer and this thread.

As mentioned in "Stack vs heap allocation of structs in Go, and how they relate to garbage collection":

> It's worth noting that the words "stack" and "heap" do not appear anywhere in the language spec.


The blog post "Escape Analysis in Go" details what happens, mentioning the FAQ:

> When possible, the Go compilers will allocate variables that are local to a function in that function's stack frame.
However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors.
Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack.

The blog post adds:

> The code that does the “escape analysis” lives in src/cmd/gc/esc.c.
Conceptually, it tries to determine if a local variable escapes the current scope; the only two cases where this happens are when a variable’s address is returned, and when its address is assigned to a variable in an outer scope.
If a variable escapes, it has to be allocated on the heap; otherwise, it’s safe to put it on the stack.

> Interestingly, this applies to new(T) allocations as well.
If they don’t escape, they’ll end up being allocated on the stack. Here’s an example to clarify matters:
>
> var intPointerGlobal *int = nil
>
> func Foo() *int {
> anInt0 := 0
> anInt1 := new(int)
>
> anInt2 := 42
> intPointerGlobal = &anInt2
>
> anInt3 := 5
>
> return &anInt3
> }
>
> Above, anInt0 and anInt1 do not escape, so they are allocated on the stack;
anInt2 and anInt3 escape, and are allocated on the heap.


See also "Five things that make Go fast":

> Unlike C, which forces you to choose if a value will be stored on the heap, via malloc, or on the stack, by declaring it inside the scope of the function, Go implements an optimisation called escape analysis.

> Go’s optimisations are always enabled by default.
You can see the compiler’s escape analysis and inlining decisions with the -gcflags=-m switch.

> Because escape analysis is performed at compile time, not run time, stack allocation will always be faster than heap allocation, no matter how efficient your garbage collector is.

huangapple
  • 本文由 发表于 2014年9月4日 14:51:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/25658998.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定