我是Go的新手,我在C风格的基于堆栈的编程之间经历了一些不协调的错误,其中自动变量存在于堆栈上,并且在堆上分配了内存,以及基于Python的基于堆栈的编程,其中只有堆栈上的东西才是堆上对象的引用/指针 .
据我所知,以下两个函数提供相同的输出:
func myFunction() (*MyStructType, error) {
var chunk *MyStructType = new(HeaderChunk)
...
return chunk, nil
}
func myFunction() (*MyStructType, error) {
var chunk MyStructType
...
return &chunk, nil
}
即分配一个新的结构并返回它 .
如果我在C中编写它,第一个将把一个对象放在堆上,第二个将它放在堆栈上 . 第一个将返回一个指向堆的指针,第二个将返回一个指向堆栈的指针,该指针在函数返回时会蒸发,这将是一件坏事 .
如果我用Python(或除C#之外的许多其他现代语言)编写它,那么示例2是不可能的 .
我得到Go垃圾收集两个值,所以上述两种形式都没问题 .
报价:
注意,与C不同,返回局部变量的地址是完全可以的 . 与函数关联的存储在函数返回后仍然存在 . 实际上,获取复合文字的地址在每次评估时都会分配一个新实例,因此我们可以将这两行结合起来 . http://golang.org/doc/effective_go.html#functions
但它提出了几个问题 .
1 - 在示例1中,结构在堆上声明 . 例2怎么样?在堆栈中声明的方式与在C中的方式相同,还是在堆上也是如此?
2 - 如果在堆栈上声明了示例2,那么在函数返回后它是如何保持可用的?
3 - 如果实际上在堆上声明了示例2,那么结构是如何通过值而不是通过引用传递的?在这种情况下,指针有什么意义?
4 回答
值得注意的是,“堆栈”和“堆”这两个词在语言规范中没有出现 . 您的问题的措辞是“...在堆栈上声明”和“...在堆上声明”,但请注意,Go声明语法对堆栈或堆没有任何说明 .
从技术上讲,这可以解决所有依赖于实现的问题 . 当然,实际上有一个堆栈(每个goroutine!)和一个堆,一些东西在堆栈上,一些在堆上 . 在某些情况下,编译器遵循严格的规则(例如“
new
总是在堆上分配") and in others the compiler does "转义分析”来确定对象是否可以存在于堆栈中,或者是否必须在堆上分配 .在您的示例2中,转义分析将显示指向转义结构的指针,因此编译器必须分配结构 . 我认为Go的当前实现在这种情况下遵循严格的规则,即如果地址取自结构的任何部分,则结构在堆上 .
对于问题3,我们可能会对术语感到困惑 . Go中的所有内容都按值传递,没有通过引用传递 . 在这里,您将返回指针值 . 指针有什么意义?考虑以下对您的示例的修改:
我修改了myFunction2以返回结构而不是结构的地址 . 现在比较myFunction1和myFunction2的汇编输出,
不要担心这里的myFunction1输出与peterSO(优秀)答案中的不同 . 我们显然运行不同的编译器 . 否则,请参阅我修改myFunction2以返回myStructType而不是* myStructType . 对runtime.new的调用已经消失,在某些情况下这将是一件好事 . 等等,这是myFunction3,
仍然没有调用runtime.new,是的,它确实可以按值返回8MB对象 . 它有效,但你通常不想这样做 . 这里指针的目的是避免推动8MB对象 .
在这两种情况下,Go的当前实现将在堆上为
MyStructType
类型的struct
分配内存并返回其地址 . 功能相同;编译器asm源是一样的 .所有功能和返回参数按值传递 . 类型为
*MyStructType
的返回参数值是一个地址 .根据Go's FAQ:
资料来源:http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#stack_heap_vars