Go 函数返回局部变量的指针是否安全?

340 阅读3分钟

参考解析

一般来说,局部变量会在函数返回后被销毁,因此被返回的引用就成为了” 无所指” 的引用,程序会进入未知状态。

但这在 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