值得注意的是,在Go语言规范中并没有出现"stack"和"heap"这些词。您的问题中使用了"...is declared on the stack,"和"...declared on the heap,"这样的措辞,但要注意,Go语言的声明语法并没有涉及堆栈或堆内存。
这在技术上使得所有问题的答案都依赖于实现。当然,在实际情况下,每个goroutine都有自己的堆栈和堆,有些东西放在堆栈上,有些则放在堆上。在某些情况下,编译器会遵循严格的规则(例如"
new
总是在堆上分配内存"),而在其他情况下,编译器会执行"逃逸分析"来决定对象是否可以存在于堆栈上,或者必须在堆上分配。
在您的第二个示例中,逃逸分析将显示指向结构体的指针逃逸,因此编译器必须分配该结构体。但我认为在这种情况下,Go的当前实现遵循了一条严格的规则,即如果对结构体的任何部分取地址,则该结构体将被放到堆上。
对于第三个问题,我们可能会混淆术语。在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的对象。
new
命令是否总是在堆上分配内存? - YenForYangruntime.new
有什么区别?此外,如果消除runtime.new
,有什么好处? - starriet