Joe*_*Joe 146 heap-memory go stack-memory
我是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
}
Run Code Online (Sandbox Code Playgroud)
即分配一个新结构并返回它.
如果我在C中编写它,第一个将把一个对象放在堆上,第二个将它放在堆栈上.第一个将返回一个指向堆的指针,第二个将返回一个指向堆栈的指针,该指针在函数返回时会蒸发,这将是一件坏事.
如果我用Python(或除C#之外的许多其他现代语言)编写它,那么示例2是不可能的.
我得到Go垃圾收集两个值,所以上述两种形式都没问题.
报价:
请注意,与C不同,返回局部变量的地址是完全可以的.与函数关联的存储在函数返回后仍然存在.实际上,获取复合文字的地址在每次评估时都会分配一个新实例,因此我们可以将这两行结合起来.
但它提出了几个问题.
1 - 在示例1中,结构在堆上声明.例2怎么样?在堆栈中声明的方式与在C中的方式相同,还是在堆上也是如此?
2 - 如果在堆栈上声明了示例2,那么在函数返回后它是如何保持可用的?
3 - 如果实际上在堆上声明了示例2,那么结构是如何通过值而不是通过引用传递的?在这种情况下,指针有什么意义?
Son*_*nia 151
值得注意的是,"堆栈"和"堆"这两个词在语言规范中没有出现.您的问题的措辞是"...在堆栈上声明"和"...在堆上声明",但请注意,Go声明语法对堆栈或堆没有任何说明.
从技术上讲,这可以解决所有依赖于实现的问题.当然,实际上有一个堆栈(每个goroutine!)和一个堆,一些东西在堆栈上,一些在堆上.在某些情况下,编译器遵循严格的规则(例如" new总是在堆上分配"),而在其他情况下,编译器会执行"转义分析"以确定对象是否可以存在于堆栈中,或者是否必须在堆上分配.
在您的示例2中,转义分析将显示指向转义结构的指针,因此编译器必须分配结构.我认为Go的当前实现在这种情况下遵循严格的规则,即如果地址取自结构的任何部分,则结构在堆上.
对于问题3,我们可能会对术语感到困惑.Go中的所有内容都按值传递,没有通过引用传递.在这里,您将返回指针值.指针有什么意义?考虑以下对您的示例的修改:
type MyStructType struct{}
func myFunction1() (*MyStructType, error) {
var chunk *MyStructType = new(MyStructType)
// ...
return chunk, nil
}
func myFunction2() (MyStructType, error) {
var chunk MyStructType
// ...
return chunk, nil
}
type bigStruct struct {
lots [1e6]float64
}
func myFunction3() (bigStruct, error) {
var chunk bigStruct
// ...
return chunk, nil
}
Run Code Online (Sandbox Code Playgroud)
我修改了myFunction2以返回结构而不是结构的地址.现在比较myFunction1和myFunction2的汇编输出,
--- prog list "myFunction1" ---
0000 (s.go:5) TEXT myFunction1+0(SB),$16-24
0001 (s.go:6) MOVQ $type."".MyStructType+0(SB),(SP)
0002 (s.go:6) CALL ,runtime.new+0(SB)
0003 (s.go:6) MOVQ 8(SP),AX
0004 (s.go:8) MOVQ AX,.noname+0(FP)
0005 (s.go:8) MOVQ $0,.noname+8(FP)
0006 (s.go:8) MOVQ $0,.noname+16(FP)
0007 (s.go:8) RET ,
--- prog list "myFunction2" ---
0008 (s.go:11) TEXT myFunction2+0(SB),$0-16
0009 (s.go:12) LEAQ chunk+0(SP),DI
0010 (s.go:12) MOVQ $0,AX
0011 (s.go:14) LEAQ .noname+0(FP),BX
0012 (s.go:14) LEAQ chunk+0(SP),BX
0013 (s.go:14) MOVQ $0,.noname+0(FP)
0014 (s.go:14) MOVQ $0,.noname+8(FP)
0015 (s.go:14) RET ,
Run Code Online (Sandbox Code Playgroud)
不要担心这里的myFunction1输出与peterSO(优秀)答案中的不同.我们显然运行不同的编译器.否则,请参阅我修改myFunction2以返回myStructType而不是*myStructType.对runtime.new的调用已经消失,在某些情况下这将是一件好事.等等,这是myFunction3,
--- prog list "myFunction3" ---
0016 (s.go:21) TEXT myFunction3+0(SB),$8000000-8000016
0017 (s.go:22) LEAQ chunk+-8000000(SP),DI
0018 (s.go:22) MOVQ $0,AX
0019 (s.go:22) MOVQ $1000000,CX
0020 (s.go:22) REP ,
0021 (s.go:22) STOSQ ,
0022 (s.go:24) LEAQ chunk+-8000000(SP),SI
0023 (s.go:24) LEAQ .noname+0(FP),DI
0024 (s.go:24) MOVQ $1000000,CX
0025 (s.go:24) REP ,
0026 (s.go:24) MOVSQ ,
0027 (s.go:24) MOVQ $0,.noname+8000000(FP)
0028 (s.go:24) MOVQ $0,.noname+8000008(FP)
0029 (s.go:24) RET ,
Run Code Online (Sandbox Code Playgroud)
仍然没有调用runtime.new,是的,它确实可以按值返回8MB对象.它有效,但你通常不想这样做.这里指针的目的是避免推动8MB对象.
pet*_*rSO 55
type MyStructType struct{}
func myFunction1() (*MyStructType, error) {
var chunk *MyStructType = new(MyStructType)
// ...
return chunk, nil
}
func myFunction2() (*MyStructType, error) {
var chunk MyStructType
// ...
return &chunk, nil
}
Run Code Online (Sandbox Code Playgroud)
在这两种情况下,Go的当前实现将为堆上struct的类型分配内存MyStructType并返回其地址.功能相同; 编译器asm源是一样的.
--- prog list "myFunction1" ---
0000 (temp.go:9) TEXT myFunction1+0(SB),$8-12
0001 (temp.go:10) MOVL $type."".MyStructType+0(SB),(SP)
0002 (temp.go:10) CALL ,runtime.new+0(SB)
0003 (temp.go:10) MOVL 4(SP),BX
0004 (temp.go:12) MOVL BX,.noname+0(FP)
0005 (temp.go:12) MOVL $0,AX
0006 (temp.go:12) LEAL .noname+4(FP),DI
0007 (temp.go:12) STOSL ,
0008 (temp.go:12) STOSL ,
0009 (temp.go:12) RET ,
--- prog list "myFunction2" ---
0010 (temp.go:15) TEXT myFunction2+0(SB),$8-12
0011 (temp.go:16) MOVL $type."".MyStructType+0(SB),(SP)
0012 (temp.go:16) CALL ,runtime.new+0(SB)
0013 (temp.go:16) MOVL 4(SP),BX
0014 (temp.go:18) MOVL BX,.noname+0(FP)
0015 (temp.go:18) MOVL $0,AX
0016 (temp.go:18) LEAL .noname+4(FP),DI
0017 (temp.go:18) STOSL ,
0018 (temp.go:18) STOSL ,
0019 (temp.go:18) RET ,
Run Code Online (Sandbox Code Playgroud)
在函数调用中,函数值和参数按通常顺序计算.在评估它们之后,调用的参数通过值传递给函数,并且被调用的函数开始执行.当函数返回时,函数的返回参数通过值传递回调用函数.
所有函数和返回参数都按值传递.带类型的返回参数值*MyStructType是一个地址.
您并不总是知道您的变量是在堆栈还是堆上分配的.
...
如果您需要知道变量的分配位置,请将"-m"gc标志传递给"go build"或"go run"(例如go run -gcflags -m app.go).
资料来源:http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/index.html#stack_heap_vars
| 归档时间: |
|
| 查看次数: |
31470 次 |
| 最近记录: |