??編譯器會優(yōu)先將局部變量存放在棧中,便于及時清理,但是如果存放在棧上的變量被清理了蜗帜,但是函數(shù)其他地方還存在對它的引用,這個時候就會發(fā)生不可知的錯誤资厉。例如下面這段程序:
#Include<stdio.h>
char *returnStr() {
char p[] = "hello world";
return p;
}
int main() {
char *str;
str = returnStr();
printf("%s\n", str);
return 0;
}
??由于"hello world"屬于局部變量厅缺,會被存儲在棧上,在returnStr函數(shù)退出之后宴偿,這個函數(shù)所占用的椣嫔樱空間會被清空,這個局部變量也就不會存在窄刘,這時main函數(shù)再去獲取這塊內(nèi)存存放的值就會發(fā)生異常窥妇。這種情況通常稱為變量逃逸
。
??類似于上面的例子娩践,一個對象的指針被其作用域之外的地方引用活翩,我們就稱這個變量發(fā)生了逃逸烹骨。
??這種情況對于使用C/C++的情況是經(jīng)常發(fā)生的,然而Go語言對這種情況做了特殊處理材泄,使得編程人員無需為這種事情過度擔(dān)心沮焕。go語言通過編譯器的逃逸分析
,在執(zhí)行靜態(tài)代碼分析時拉宗,對內(nèi)存管理進行優(yōu)化峦树,將變量分配到合理的位置。
2旦事、逃逸分析
觀察下面這個例子:
package main
import "fmt"
func returnStr() (*string魁巩, *string) {
strStack := "hello world!"
strHeap := "hello world!"
strStack2 := "hello world!"
fmt.Println("strStack is: ", strStack, "strHeap is: ", strHeap, "strStack2 is: ", strStack2)
return &strHeap, &strStack2
}
func main() {
str, _ := returnStr()
fmt.Println(*str)
}
通過分析可以發(fā)現(xiàn):
1、strStack這個變量在函數(shù)returnStr內(nèi)部使用完成后族檬,就沒用了歪赢,這時可以把這個變量分配到棧上化戳。
2单料、strStack2的地址被返回了,所以也會分配到堆上(這個地方和參考文章說的不同点楼,經(jīng)過多次實驗扫尖,我覺得它還是會被分配到堆上)。
3掠廓、對于strHeap這個變量换怖,在函數(shù)內(nèi)部使用完之后,指針又被返回給main函數(shù)使用蟀瞧,那就把這個變量分配到堆上沉颂。
??如果將變量分配到堆上,可能會造成一定程序的性能損耗悦污,因為不同于棧的自動釋放铸屉,分配在堆上的變量,需要Go頻繁地進行垃圾回收切端。通過逃逸分析彻坛,可以將變量在堆和棧上合理地進行分配,避免不必要的性能浪費踏枣。
??簡單來說,編譯器通過逃逸分析茵瀑,分析出變量是否存在外部引用间驮,判斷變量存放的位置
- 如果外部沒有引用,優(yōu)先存放到棧中
-
如果外部存在應(yīng)用马昨,必然存放到堆中
參考文章:
Go變量逃逸分析