Go 语言中的变量究竟是分配在栈上还是分配在堆上?
Posted 知其黑、受其白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go 语言中的变量究竟是分配在栈上还是分配在堆上?相关的知识,希望对你有一定的参考价值。
阅读目录
Go 语言中的变量究竟是分配在栈上、还是分配在堆上?
函数内部的变量是在栈上,带指针的变量是在堆上。
func sum(a int, b int) *int
var c = a + b
return &c
golang函数是值拷贝,函数内部的变量 c
对应的内存就会被回收,如果返回的是指针 (&c)
,那么 c
对应的内存就不会被回收,因为go编译器知道要是回收了,那么返回的指针就获取不到指向的值了,于是就会把 c
分配到堆上。
那么编译器是如何检测的呢,答案是通过逃逸分析。
什么是逃逸分析?
逃逸分析:
通过指针的动态范围决定一个变量究竟是分配在栈上还是应该分配在堆上。
我们知道栈区是可以自动清理的,所以栈区的效率很高,但是不可能把所有的对象都申请在栈上面,而且栈空间也是有限的。
但如果所有的对象都分配在堆区的话,堆又不像栈那样可以自动清理,因此会频繁造成垃圾回收,从而降低运行效率。
所以在go中,会通过逃逸分析,把那些一次性的对象分配到栈区,如果后续还有变量指向,那么就放到堆区。
逃逸分析的标准
首先可以肯定的是,如果函数里面的变量返回了一个地址,那么这个变量肯定会发生逃逸。
go编译器会判断变量的生命周期,如果编译器认为函数结束后,这个变量不再被外部的引用了,会分配到栈,否则分配到堆。
package main
import "fmt"
func sum(a int, b int) *int
var c = a + b
var d = 1
var e = new(int)
fmt.Println(&d)
fmt.Println(&e)
return &c
- 比如这里的变量
d
, 尽管通过&d
获取了它的地址, 但是这仅仅是打印。 - 而
e
虽然调用了new
方法,但这并不能成为分配到堆区的理由。因为 d 和 e 并没有被外部引用, 所以不好意思, sum 函数执行结束, 这两位老铁必须"见上帝"。 - 但是对于c, 我们返回了它的指针, 既然返回了指针, 那么就代表这个变量对应的内存可以被外部访问, 所以会逃逸到堆。
*因此有两个结论
- 如果一个函数结束之后外部没有引用,那么优先分配到栈中(如果申请的内存过大,栈区存不下,会分配到堆)。
- 如果一个函数结束之后外部还有引用,那么必定分配到堆中。
逃逸分析演示
package main
import "fmt"
func sum(a int, b int) *int
var c = a + b
return &c
func main()
var p = sum(1, 2)
fmt.Println(*p)
通过命令:go run -gcflags "-m -l" .\\main.go
观察 golang 是如何进行逃逸分析的。
PS E:\\TEXT\\test_go\\test> go run -gcflags "-m -l" .\\main.go
# command-line-arguments
.\\main.go:6:6: moved to heap: c
.\\main.go:12:13: ... argument does not escape
.\\main.go:12:14: *p escapes to heap
3
PS E:\\TEXT\\test_go\\test>
我们看到变量 c
发生了逃逸,这和我们想的一样,但是为什么 main函数里面的 p 居然也逃逸了。
因为编译期间不确定变量类型的话,那么也会发生逃逸。除此之外,还可以通过反汇编命令来查看:
PS E:\\TEXT\\test_go\\test> go tool compile -S .\\main.go
main.sum STEXT size=95 args=0x10 locals=0x18 funcid=0x0 align=0x0
0x0000 00000 (.\\main.go:5) TEXT main.sum(SB), ABIInternal, $24-16
0x0000 00000 (.\\main.go:5) CMPQ SP, 16(R14)
0x0004 00004 (.\\main.go:5) PCDATA $0, $-2
0x0004 00004 (.\\main.go:5) JLS 68
0x0006 00006 (.\\main.go:5) PCDATA $0, $-1
0x0006 00006 (.\\main.go:5) SUBQ $24, SP
0x000a 00010 (.\\main.go:5) MOVQ BP, 16(SP)
0x000f 00015 (.\\main.go:5) LEAQ 16(SP), BP
0x0014 00020 (.\\main.go:5) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0014 00020 (.\\main.go:5) FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0014 00020 (.\\main.go:5) FUNCDATA $5, main.sum.arginfo1(SB)
0x0014 00020 (.\\main.go:5) FUNCDATA $6, main.sum.argliveinfo(SB)
0x0014 00020 (.\\main.go:5) PCDATA $3, $1
0x0014 00020 (.\\main.go:5) MOVQ AX, main.a+32(SP)
0x0019 00025 (.\\main.go:5) MOVQ BX, main.b+40(SP)
0x001e 00030 (.\\main.go:5) PCDATA $3, $-1
0x001e 00030 (.\\main.go:6) LEAQ type.int(SB), AX
0x0025 00037 (.\\main.go:6) PCDATA $1, $0
0x0025 00037 (.\\main.go:6) CALL runtime.newobject(SB)
0x002a 00042 (.\\main.go:6) MOVQ main.b+40(SP), CX
0x002f 00047 (.\\main.go:6) MOVQ main.a+32(SP), DX
0x0034 00052 (.\\main.go:6) ADDQ DX, CX
0x0037 00055 (.\\main.go:6) MOVQ CX, (AX)
0x003a 00058 (.\\main.go:7) MOVQ 16(SP), BP
0x003f 00063 (.\\main.go:7) ADDQ $24, SP
0x0043 00067 (.\\main.go:7) RET
0x0044 00068 (.\\main.go:7) NOP
0x0044 00068 (.\\main.go:5) PCDATA $1, $-1
0x0044 00068 (.\\main.go:5) PCDATA $0, $-2
0x0044 00068 (.\\main.go:5) MOVQ AX, 8(SP)
0x0049 00073 (.\\main.go:5) MOVQ BX, 16(SP)
0x004e 00078 (.\\main.go:5) CALL runtime.morestack_noctxt(SB)
0x0053 00083 (.\\main.go:5) MOVQ 8(SP), AX
0x0058 00088 (.\\main.go:5) MOVQ 16(SP), BX
0x005d 00093 (.\\main.go:5) PCDATA $0, $-1
0x005d 00093 (.\\main.go:5) JMP 0
0x0000 49 3b 66 10 76 3e 48 83 ec 18 48 89 6c 24 10 48 I;f.v>H...H.l$.H
0x0010 8d 6c 24 10 48 89 44 24 20 48 89 5c 24 28 48 8d .l$.H.D$ H.\\$(H.
0x0020 05 00 00 00 00 e8 00 00 00 00 48 8b 4c 24 28 48 ..........H.L$(H
0x0030 8b 54 24 20 48 01 d1 48 89 08 48 8b 6c 24 10 48 .T$ H..H..H.l$.H
0x0040 83 c4 18 c3 48 89 44 24 08 48 89 5c 24 10 e8 00 ....H.D$.H.\\$...
0x0050 00 00 00 48 8b 44 24 08 48 8b 5c 24 10 eb a1 ...H.D$.H.\\$...
rel 33+4 t=14 type.int+0
rel 38+4 t=7 runtime.newobject+0
rel 79+4 t=7 runtime.morestack_noctxt+0
main.main STEXT size=111 args=0x0 locals=0x48 funcid=0x0 align=0x0
0x0000 00000 (.\\main.go:10) TEXT main.main(SB), ABIInternal, $72-0
0x0000 00000 (.\\main.go:10) CMPQ SP, 16(R14)
0x0004 00004 (.\\main.go:10) PCDATA $0, $-2
0x0004 00004 (.\\main.go:10) JLS 104
0x0006 00006 (.\\main.go:10) PCDATA $0, $-1
0x0006 00006 (.\\main.go:10) SUBQ $72, SP
0x000a 00010 (.\\main.go:10) MOVQ BP, 64(SP)
0x000f 00015 (.\\main.go:10) LEAQ 64(SP), BP
0x0014 00020 (.\\main.go:10) FUNCDATA $0, gclocals·J5F+7Qw7O7ve2QcWC7DpeQ==(SB)
0x0014 00020 (.\\main.go:10) FUNCDATA $1, gclocals·5aa34RaZcmo0NkRpBHp2fg==(SB)
0x0014 00020 (.\\main.go:10) FUNCDATA $2, main.main.stkobj(SB)
0x0014 00020 (<unknown line number>) NOP
0x0014 00020 (.\\main.go:11) MOVQ $3, main.c+40(SP)
0x001d 00029 (.\\main.go:12) MOVUPS X15, main..autotmp_13+48(SP)
0x0023 00035 (.\\main.go:12) MOVQ main.c+40(SP), AX
0x0028 00040 (.\\main.go:12) PCDATA $1, $1
0x0028 00040 (.\\main.go:12) CALL runtime.convT64(SB)
0x002d 00045 (.\\main.go:12) LEAQ type.int(SB), CX
0x0034 00052 (.\\main.go:12) MOVQ CX, main..autotmp_13+48(SP)
0x0039 00057 (.\\main.go:12) MOVQ AX, main..autotmp_13+56(SP)
0x003e 00062 (<unknown line number>) NOP
0x003e 00062 ($GOROOT\\src\\fmt\\print.go:294) MOVQ os.Stdout(SB), BX
0x0045 00069 ($GOROOT\\src\\fmt\\print.go:294) LEAQ go.itab.*os.File,io.Writer(SB), AX
0x004c 00076 ($GOROOT\\src\\fmt\\print.go:294) LEAQ main..autotmp_13+48(SP), CX
0x0051 00081 ($GOROOT\\src\\fmt\\print.go:294) MOVL $1, DI
0x0056 00086 ($GOROOT\\src\\fmt\\print.go:294) MOVQ DI, SI
0x0059 00089 ($GOROOT\\src\\fmt\\print.go:294) PCDATA $1, $0
0x0059 00089 ($GOROOT\\src\\fmt\\print.go:294) CALL fmt.Fprintln(SB)
0x005e 00094 (.\\main.go:13) MOVQ 64(SP), BP
0x0063 00099 (.\\main.go:13) ADDQ $72, SP
0x0067 00103 (.\\main.go:13) RET
0x0068 00104 (.\\main.go:13) NOP
0x0068 00104 (.\\main.go:10) PCDATA $1, $-1
0x0068 00104 (.\\main.go:10) PCDATA $0, $-2
0x0068 00104 (.\\main.go:10) CALL runtime.morestack_noctxt(SB)
0x006d 00109 (.\\main.go:10) PCDATA $0, $-1
0x006d 00109 (.\\main.go:10) JMP 0
0x0000 49 3b 66 10 76 62 48 83 ec 48 48 89 6c 24 40 48 I;f.vbH..HH.l$@H
0x0010 8d 6c 24 40 48 c7 44 24 28 03 00 00 00 44 0f 11 .l$@H.D$(....D..
0x0020 7c 24 30 48 8b 44 24 28 e8 00 00 00 00 48 8d 0d |$0H.D$(.....H..
0x0030 00 00 00 00 48 89 4c 24 30 48 89 44 24 38 48 8b ....H.L$0H.D$8H.
0x0040 1d 00 00 00 00 48 8d 05 00 00 00 00 48 8d 4c 24 .....H......H.L$
0x0050 30 bf 01 00 00 00 48 89 fe e8 00 00 00 00 48 8b 0.....H.......H.
0x0060 6c 24 40 48 83 c4 48 c3 e8 00 00 00 00 eb 91 l$@H..H........
rel 2+0 t=23 type.int+0
rel 2+0 t=23 type.*os.File+0
rel 41+4 t=7 runtime.convT64+0
rel 48+4 t=14 type.int+0
rel 65+4 t=14 os.Stdout+0
rel 72+4 t=14 go.itab.*os.File,io.Writer+0
rel 90+4 t=7 fmt.Fprintln+0
rel 105+4 t=7 runtime.morestack_noctxt+0
go.cuinfo.producer.<unlinkable> SDWARFCUINFO dupok size=0
0x0000 72 65 67 61 62 69 regabi
go.cuinfo.packagename.以上是关于Go 语言中的变量究竟是分配在栈上还是分配在堆上?的主要内容,如果未能解决你的问题,请参考以下文章