我是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
请注意,与C语言不同,完全可以返回局部变量的地址。函数返回后,与变量关联的存储将保留。实际上,每次获取复合文字的地址时,它都会分配一个新实例,因此我们可以将最后两行结合在一起。
http://golang.org/doc/effective_go.html#functions
但这引起了两个问题。
1-在示例1中,该结构在堆上声明。例子2呢?是在堆栈中声明的方式是否与在C中声明的方式相同,还是在堆栈中使用?
2-如果在堆栈上声明了示例2,则函数返回后如何保持可用状态?
3-如果实际上在堆上声明了示例2,那么如何通过值而不是通过引用传递结构?在这种情况下,指针的意义是什么?
值得注意的是,在语言规范中,“ stack”和“ heap”一词没有出现。您的问题用“ …在堆栈上声明”和“ …在堆栈上声明”来表述,但请注意,Go声明语法对堆栈或堆没有任何说明。
从技术上讲,这使您对所有问题的答案都依赖于实现。当然,实际上,有一个堆栈(每个goroutine!)和一个堆,有些东西在堆栈上,有些东西在堆栈上。在某些情况下,编译器遵循严格的规则(例如“ new始终在堆上分配”),而在其他情况下,编译器执行“转义分析”来确定对象是否可以存在于堆栈中,或者是否必须在堆中进行分配。
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 }
我修改了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 ,
不必担心这里的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 ,
仍然没有调用runtime.new,是的,它确实可以按值返回8MB对象。它有效,但是您通常不希望这样做。这里的指针指向是避免推送约8MB的对象。