逃逸分析是什么
在编写 C/C++ 代码时,为了提高效率,经常会将返回值修改成返回指针,企图避免构造函数的开销。
其实这里隐藏了一个陷阱:在函数内部定义了一个局部变量,函数结束时返回这个局部变量的地址(指针)。因为这些局部变量是在栈上分配的(即静态内存分配),函数一旦执行完毕,变量占据的内存空间会被销毁,任何对这个返回值做的操作(如解引用),都将扰乱程序的运行,甚至导致程序直接崩溃。比如下面的这段代码:
// c++
int* foo() {
int t = 3;
return &t;
}
1
2
3
4
5
2
3
4
5
有些人可能知道上面这个陷阱,做了一些改进:在函数内部使用 new 运算符构造一个变量 (即动态内存分配),然后返回此变量的地址。因为变量是在堆上创建的,所以在函数退出时不会被销毁。改进后的代码如下:
// c++
int* foo() {
int* t = new int;
*t = 3;
return t;
}
1
2
3
4
5
6
2
3
4
5
6
但是,这样就行了吗?新建出来的对象该在何时何地删除呢?调用者可能会忘记删除或者直接将返回值传给其他函数,之后就再也不能删除它了,也就发生了所谓的内存泄漏。
C++ 是公认的语法最复杂的语言,据说没有人可以完全掌握它的语法。而这一切在 Go 语言中就大不相同了,像上面示例的 C++ 代码放到 Go 里没有任何问题:
// go
func foo() *int {
t := new(int)
*t = 3
return t
}
1
2
3
4
5
6
2
3
4
5
6
“你表面的光鲜,一定是背后有很多人在支撑”,放到 Go 语言里就是指编译器的逃逸分析:它是编译器执行静态代码分析后,对内存管理进行的优化和简化。
在编译原理中,分析指针动态范围的方法被称之为逃逸分析。通俗来讲,当一个对象的指针被多个方法或线程引用时,则称这个指针发生了逃逸。逃逸分析决定一个变量是分配在堆上还是分配在栈上。
上次更新: 5/9/2023, 10:58:32 AM