案例
先看一段C语言代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| #include <stdio.h>
int *foo(int arg_val) {
int foo_val = 11;
return &foo_val;
}
int main()
{
int *main_val = foo(666);
printf("%d\n", *main_val);
}
|
1
2
3
4
5
| ➜ Desktop gcc cDemo.c
cDemo.c:7:13: warning: address of stack memory associated with local variable 'foo_val' returned [-Wreturn-stack-address]
return &foo_val;
^~~~~~~
1 warning generated.
|
如上C/C++编译器明确给出了警告,foo把一个局部变量的地址返回了
在Go语言下代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| package main
func foo(arg_val int)(*int) {
var foo_val int = 11;
return &foo_val;
}
func main() {
main_val := foo(666)
println(*main_val)
}
|
1
2
| ➜ goDemo go run main.go
11
|
在Go语言下却正常运行
分析
go语言编译器会自动决定把一个变量放在栈还是放在堆,编译器会做逃逸分析(escape analysis),当发现变量的作用域没有跑出函数范围,就可以在栈上,反之则必须分配在堆。
逃逸规则
如果变量需要使用堆空间,那么他就应该进行逃逸。但是实际上Golang并不仅仅把逃逸的规则如此泛泛。Golang会有很多场景具备出现逃逸的现象。
一般我们给一个引用类对象中的引用类成员进行赋值,可能出现逃逸现象。可以理解为访问一个引用对象实际上底层就是通过一个指针来间接的访问了,但如果再访问里面的引用成员就会有第二次间接访问,这样操作这部分对象的话,极大可能会出现逃逸的现象。
Go语言中的引用类型有func(函数类型),interface(接口类型),slice(切片类型),map(字典类型),channel(管道类型),*(指针类型)等。
Golang中一个函数内局部变量,不管是不是动态new出来的,它会被分配在堆还是栈,是由编译器做逃逸分析之后做出的决定。