参考解析
一般来说,局部变量会在函数返回后被销毁,因此被返回的引用就成为了” 无所指” 的引用,程序会进入未知状态。
但这在 Go 中是安全的,Go 编译器将会对每个局部变量进行逃逸分析。如果发现局部变量的作用域超出该函数,则不会将内存分配在栈上,而是分配在堆上,因为他们不在栈区,即使释放函数,其内容也不会受影响。
package main
import "fmt"
func add(x, y int) *int {
res := 0
res = x + y
return &res
}
func main() {
fmt.Println(add(1, 2))
}
这个例子中,函数 add
局部变量 res
发生了逃逸。res 作为返回值,在 main 函数中继续使用,因此 res 指向的内存不能够分配在栈上,随着函数结束而回收,只能分配在堆上。
编译时可以借助选项 -gcflags=-m
,查看变量逃逸的情况
./main.go:6:2: res escapes to heap:
./main.go:6:2: flow: ~r2 = &res:
./main.go:6:2: from &res (address-of) at ./main.go:8:9
./main.go:6:2: from return &res (return) at ./main.go:8:2
./main.go:6:2: moved to heap: res
./main.go:12:13: ... argument does not escape
0xc0000ae008
res escapes to heap
即表示 res
逃逸到堆上了。
知识点补充 —–go 中的堆和栈
我们定义的变量存放在堆还是栈中?一般是这么来分配的
堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS 回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表
栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
那么 go 中的全局变量和局部变量都是放在哪里呢?
1. 变量的生命周期
生命周期是指程序执行过程中变量存在的时间段。
(1)包变量 (全局变量) 一直常驻在内存中直到程序的结束,然后被系统垃圾回收,也就是说包变量的生命周期是整个程序的执行时间
(2)局部变量 在函数中定义的变量,它有一个动态的生命周期:每次执行的时候就创建一个新的实体,一直生存到没有人使用 (例如没有外部指针指向它,函数退出的时候没有路径访问到这个变量) 这个时候它占用的空间就会被回收
2. 堆和栈的分配
如何判断 Golang 变量是分配在栈(stack)上还是堆(heap)上?
Go 语言区别于 C/C++,虽然变量申请在堆空间上,但是它有自动回收垃圾的功能,所以这些堆地址空间也无需我们手动回收,系统会在需要释放的时刻自动进行垃圾回收。
总结:Golang 变量存储在堆上还是栈上由内部实现决定而和具体的语法没有关系。非指针小对象通常保存在栈上,大对象保存在堆上。至于指针保存在堆上还是栈上,要进行逃逸分析
转载:来自 Bittersweet!