如何判断golang变量是分配在栈(stack)上还是堆(heap)上? 2024-01-27 默认分类 暂无评论 530 次阅读 Go这门语言抛弃了C/C++中的开发者管理内存的方式:主动申请与主动释放,增加了逃逸分析和GC,将开发者从内存管理中释放出来,让开发者有更多的精力去关注软件设计,而不是底层的内存问题。这是Go语言成为高生产力语言的原因之一。 一、如何判断变量是分配在栈(stack)上还是堆(heap)上? Golang 中的变量只要被引用就一直会存活,存储在堆上还是栈上由内部实现决定而和具体的语法没有关系。 通常情况下: 栈上:函数调用的参数、返回值以及小类型局部变量大都会被分配到栈上, 这部分内存会由编译器进行管理。 无需 GC 的标记。 堆上:大对象、逃逸的变量会被分配到堆上,分配到堆上的对象。 Go 的运行时 GC 就会在 后台将对应的内存进行标记从而能够在垃圾回收的时候将对应的内存回收,进而增加了开销。 ```Go 例1 func main() { a := make([]int, 10000) b := make([]int, 1000) } ``` ```Shell go run -gcflags "-m -l" main.go (-m打印逃逸分析信息,-l禁止内联编译) command-line-arguments ./main.go:22:11: make([]int, 10000) escapes to heap //无法被一个执行栈装下,即便没有返回,也会直接在堆上分配; ./main.go:23:11: main make([]int, 1000) does not escape //对象能够被一个执行栈装下,变量没有返回到栈外,进而没有发生逃逸。 ``` 二、指针逃逸分析 局部变量以指针的方式从方法中传出或被全局变量引用,这种现象被称为指针逃逸(Escape)。我们来分析几种情况: 情况一(最基本):在某个函数中new或字面量创建出的变量,将其指针作为函数返回值,则该变量一定发生逃逸。 ```Go func main() { c := call2() } func call2() *int { x := 2 return &x } ``` ```Shell go run -gcflags "-m -l" main.go command-line-arguments ./main.go:25:2: moved to heap: x ./main.go:22:2: c declared and not used ``` 情况二:指针作为函数调用参数,则该变量如果没有被逃逸的变量的或者全局变量引用,指针不会逃逸。 ```Go func main() { a := make([]int, 5) call(&a) } func call(a *[]int) { (*a)[0] = 1 } ``` ```Shell go run -gcflags "-m -l" main.go command-line-arguments ./main.go:26:11: call a does not escape ./main.go:22:11: main make([]int, 5) does not escape ``` 情况三:仅仅在函数内对变量做取址操作,而未将指针传出,指针不会逃逸 ```Go package main func main() { a := 2 b := &a } ``` ```Shell go run -gcflags "-m -l" main.go command-line-arguments ./main.go:5:2: b declared and not used ``` 情况四:指针作为函数调用参数,则该变量如被逃逸的变量的或者全局变量引用,指针会逃逸。 ```Go package main var g *int func main() { a := 2 g = &a } ``` ```Shell go run -gcflags "-m -l" main.go command-line-arguments ./main.go:6:2: moved to heap: a ``` 情况五:被指针类型的slice、map和chan引用的指针一定发生逃逸 ```Go func main() { a := make([]*int, 1) b := 12 a[0] = &b c := make(map[string]*int) d := 14 c["aaa"] = &d e := make(chan *int, 1) f := 15 e <- &f } ``` ```Shell go run -gcflags "-m -l" main.go command-line-arguments ./main.go:5:2: moved to heap: b ./main.go:9:2: moved to heap: d ./main.go:13:2: moved to heap: f ./main.go:4:11: main make([]*int, 1) does not escape ./main.go:8:11: main make(map[string]*int) does not escape ``` 三、总结 golang 变量存储在堆上还是栈上由内部实现决定而和具体的语法没有关系。 通常情况下: > 栈上:函数调用的参数、返回值以及小类型局部变量大都会被分配到栈上, 这部分内存会由编译器进行管理。 无需 GC 的标记。 > >堆上:大对象、逃逸的变量会被分配到堆上,分配到堆上的对象。 非指针小对象通常保存在栈上,大对象保存在堆上。至于指针保存在堆上还是栈上, 要进行逃逸分析: 我们得出了指针必然发生逃逸的三种情况: 1. 在某个函数中new或字面量创建出的变量,将其指针作为函数返回值, 则该变量一定发生逃逸(构造函数返回的指针变量一定逃逸); 2. 被已经逃逸的变量引用的指针 3. 被指针类型的slice、map和chan引用的指针 同时我们也得出一些必然不会逃逸的情况: 1. 指针被未发生逃逸的变量引用; 2. 仅仅在函数内对变量做取址操作,而未将指针传出; [转载自: 一、如何判断变量是分配在栈(stack)上还是堆(heap)上?](https://zhuanlan.zhihu.com/p/523195006 "一、如何判断变量是分配在栈(stack)上还是堆(heap)上?") 文章目录 一、如何判断变量是分配在栈(stack)上还是堆(heap)上? command-line-arguments 二、指针逃逸分析 情况一(最基本):在某个函数中new或字面量创建出的变量,将其指针作为函数返回值,则该变量一定发生逃逸。 command-line-arguments 情况二:指针作为函数调用参数,则该变量如果没有被逃逸的变量的或者全局变量引用,指针不会逃逸。 command-line-arguments 情况三:仅仅在函数内对变量做取址操作,而未将指针传出,指针不会逃逸 command-line-arguments 情况四:指针作为函数调用参数,则该变量如被逃逸的变量的或者全局变量引用,指针会逃逸。 command-line-arguments 情况五:被指针类型的slice、map和chan引用的指针一定发生逃逸 command-line-arguments 三、总结 标签: golang 转载请注明文章来源 本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
评论已关闭