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, 我们返回了它的指针, 既然返回了指针, 那么就代表这个变量对应的内存可以被外部访问, 所以会逃逸到堆。

*因此有两个结论

  1. 如果一个函数结束之后外部没有引用,那么优先分配到栈中(如果申请的内存过大,栈区存不下,会分配到堆)。
  2. 如果一个函数结束之后外部还有引用,那么必定分配到堆中。

逃逸分析演示

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 语言中的变量究竟是分配在栈上还是分配在堆上?的主要内容,如果未能解决你的问题,请参考以下文章

go的值类型和引用类型2——内存分配规则

java语言中,类的成员变量分配在哪个内存区?

什么是逃逸分析?

Golang之变量去哪儿

go lang中局部变量的内存分配

堆和栈的区别 还有啥建立在堆上 啥建立在栈上