与C++/C相比,Go语言的内存布局

21
在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中,是否有具体的讨论?


7
它将被分配在堆上。 Go编译器可以检测到对象是否会存在于堆栈之外,并自动将其分配在堆上。如果您使用 go build -gcflags'-m' 进行编译以查看优化决策,就可以看到它。 - siritinga
2
@python: 你的直觉是对的,你不必担心它。实际上,它确实会被分配在堆上,但 Go 的内存模型非常简单,你实际上不必考虑堆栈。你只需考虑变量。如果你取某个东西的地址,在有指针指向它的情况下,Go 将保证该地址始终有效。作为一个程序员,这样的事对你来说并不重要。(显然,思考它的实现方式可能很有趣,也有一些很酷的东西发生了) - joshlf
感谢大家的回答。非常感谢你们所有人的答复。 - python
如果你获取某个东西的地址,Go语言将保证只要你有指向它的指针,那么该地址始终有效...除了对于某些复合字面量的指针,例如 x := &((2 * 3) + 4):https://dev59.com/2l8e5IYBdhLWcg3wlbFR#25601963 - VonC
是的,但Go不会盲目地让你这样做并产生运行时错误 - 它不会让你编译它。 - joshlf
1个回答

32

组合字面量部分提到:

获取复合字面量的地址(§Address operators)会生成指向该字面量值实例的唯一指针。

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

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

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

正如在"Go中结构体的堆栈分配与堆分配及其与垃圾回收的关系"中所提到的:

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


博客文章“Go中的逃逸分析”详细介绍了发生了什么,并提到了FAQ

如果可能,Go编译器将在该函数的堆栈帧中分配局部变量。
但是,如果编译器无法证明变量在函数返回后不被引用,则编译器必须在垃圾收集的堆上分配变量以避免悬空指针错误。
另外,如果本地变量非常大,则将其存储在堆上而不是堆栈上可能更有意义。

博客文章还补充说:

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.

请参见"五个使Go快的方法":
与C不同,它通过malloc强制您选择值是存储在堆上还是声明在函数作用域内存储在栈上,Go实现了一种称为逃逸分析的优化。
Go的优化默认始终启用。
您可以使用-gcflags=-m开关查看编译器的逃逸分析和内联决策。
由于逃逸分析是在编译时而不是运行时执行的,因此无论您的垃圾回收器效率如何,堆栈分配将始终比堆分配更快。

1
即使规范没有提到,编译器在其逃逸分析中也会提及:./test.go:11: new(int) escapes to heap - siritinga
@siritinga 确实。我已经编辑了答案,并提供了更多有关逃逸分析的详细信息,来源于https://dev59.com/5GYr5IYBdhLWcg3wYpQg#46WeEYcBWogLw_1bpRPK - VonC
这只是一条注释。最后,避免使用堆栈术语并不那么容易 :) - siritinga
1
@siritinga 是的,但是http://dave.cheney.net/2014/06/07/five-things-that-make-go-fast表明goroutine堆栈与普通堆栈非常不同(非常小,没有守卫页,...) - VonC
2
@VonC 自我提醒,那是我第1000个优秀答案。 - VonC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接