??編譯器會優先將局部變量存放在棧中,便于及時清理,但是如果存放在棧上的變量被清理了,但是函數其他地方還存在對它的引用,這個時候就會發生不可知的錯誤。例如下面這段程序:
#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函數退出之后,這個函數所占用的棧空間會被清空,這個局部變量也就不會存在,這時main函數再去獲取這塊內存存放的值就會發生異常。這種情況通常稱為變量逃逸
。
??類似于上面的例子,一個對象的指針被其作用域之外的地方引用,我們就稱這個變量發生了逃逸。
??這種情況對于使用C/C++的情況是經常發生的,然而Go語言對這種情況做了特殊處理,使得編程人員無需為這種事情過度擔心。go語言通過編譯器的逃逸分析
,在執行靜態代碼分析時,對內存管理進行優化,將變量分配到合理的位置。
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)
}
通過分析可以發現:
1、strStack這個變量在函數returnStr內部使用完成后,就沒用了,這時可以把這個變量分配到棧上。
2、strStack2的地址被返回了,所以也會分配到堆上(這個地方和參考文章說的不同,經過多次實驗,我覺得它還是會被分配到堆上)。
3、對于strHeap這個變量,在函數內部使用完之后,指針又被返回給main函數使用,那就把這個變量分配到堆上。
??如果將變量分配到堆上,可能會造成一定程序的性能損耗,因為不同于棧的自動釋放,分配在堆上的變量,需要Go頻繁地進行垃圾回收。通過逃逸分析,可以將變量在堆和棧上合理地進行分配,避免不必要的性能浪費。
??簡單來說,編譯器通過逃逸分析,分析出變量是否存在外部引用,判斷變量存放的位置
- 如果外部沒有引用,優先存放到棧中
-
如果外部存在應用,必然存放到堆中
逃逸分析
參考文章:
Go變量逃逸分析