Go語言變數生命期和變數逃逸分析
什麼是棧
棧(Stack)是一種擁有特殊規則的線性表資料結構。
1) 概念
棧只允許往線性表的一端放入資料,之後在這一端取出資料,按照後進先出(LIFO,Last InFirst Out)的順序,如下圖
圖:棧的操作及擴充套件
往棧中放入元素的過程叫做入棧。入棧會增加棧的元素數量,最後放入的元素總是位於棧的頂部,最先放入的元素總是位於棧的底部。
從棧中取出元素時,只能從棧頂部取出。取出元素後,棧的數量會變少。最先放入的元素總是最後被取出,最後放入的元素總是最先被取出。不允許從棧底獲取資料,也不允許對棧成員(除棧頂外的成員)進行任何檢視和修改操作。
2) 變數和棧有什麼關係
棧可用於記憶體分配,棧的分配和回收速度非常快。
例:
- func calc(a, b int) int {
- var c int
- c = a * b
- var x int
- x = c * 10
- return x
- }
程式碼說明如下:
- 第 1 行,傳入 a、b 兩個整型引數。
- 第 2 行,宣告 c 整型變數,執行時,c 會分配一段記憶體用以儲存 c 的數值。
- 第 3 行,將 a 和 b 相乘後賦予 c。
- 第 5 行,宣告 x 整型變數,x 也會被分配一段記憶體。
- 第 6 行,讓 c 乘以 10 後儲存到 x 變數中。
- 第 8 行,返回 x 的值。
什麼是堆
堆在記憶體分配中類似於往一個房間裡擺放各種傢俱,傢俱的尺寸有大有小。分配記憶體時,需要找一塊足夠裝下傢俱的空間再擺放傢俱。經過反覆擺放和騰空傢俱後,房間裡的空間會變得亂七八糟,此時再往空間裡擺放傢俱會存在雖然有足夠的空間,但各空間分佈在不同的區域,無法有一段連續的空間來擺放傢俱的問題。
記憶體分配器就需要對這些空間進行調整優化,如下圖
圖:堆的分配及空間
堆分配記憶體和棧分配記憶體相比,堆適合不可預知大小的記憶體分配。缺點是分配速度較慢,而且會形成記憶體碎片。
變數逃逸(Escape Analysis)——自動決定變數分配方式,提高執行效率
Go 語言將這個過程整合到編譯器中,命名為“變數逃逸分析”。這個技術由編譯器分析程式碼的特徵和程式碼生命期,決定應該如何堆還是棧進行記憶體分配,即使程式設計師使用 Go 語言完成了整個工程後也不會感受到這個過程。
1) 逃逸分析
使用下面的程式碼來展現 Go 語言如何通過命令列分析變數逃逸,程式碼如下:
- package main
- import "fmt"
- // 本函式測試入口引數和返回值情況
- func dummy(b int) int {
- // 宣告一個c賦值進入引數並返回
- var c int
- c = b
- return c
- }
- // 空函式, 什麼也不做
- func void() {
- }
- func main() {
- // 宣告a變數並列印
- var a int
- // 呼叫void()函式
- void()
- // 列印a變數的值和dummy()函式返回
- fmt.Println(a, dummy(0))
- }
程式碼說明如下:
- 第 6 行,dummy() 函式擁有一個引數,返回一個整型值,測試函式引數和返回值分析情況。
- 第 9 行,宣告 c 變數,這裡演示函式臨時變數通過函式返回值返回後的情況。
- 第 16 行,這是一個空函式,測試沒有任何引數函式的分析情況。
- 第 23 行,在 main() 中宣告 a 變數,測試 main() 中變數的分析情況。
- 第 26 行,呼叫 void() 函式,沒有返回值,測試 void() 呼叫後的分析情況。
- 第 29 行,列印 a 和 dummy(0) 的返回值,測試函式返回值沒有變數接收時的分析情況。
2) 取地址發生逃逸
下面的例子使用結構體做資料,程式碼如下:
- package main
- import "fmt"
- // 宣告空結構體測試結構體逃逸情況
- type Data struct {
- }
- func dummy() *Data {
- // 例項化c為Data型別
- var c Data
- //返回函式區域性變數地址
- return &c
- }
- func main() {
- fmt.Println(dummy())
- }
程式碼說明如下:
- 第 6 行,宣告一個空的結構體做結構體逃逸分析。
- 第 9 行,將 dummy() 函式的返回值修改為 *Data 指標型別。
- 第 12 行,將 c 變數宣告為 Data 型別,此時 c 的結構體為值型別。
- 第 15 行,取函式區域性變數 c 的地址並返回。Go 語言的特性允許這樣做。
- 第 20 行,列印 dummy() 函式的返回值。
Go 語言最終選擇將 c 的 Data 結構分配在堆上。然後由垃圾回收器去回收 c 的記憶體。
3) 原則
編譯器覺得變數應該分配在堆和棧上的原則是:
- 變數是否被取地址。
- 變數是否發生逃逸。
相關文章
- Go語言之變數逃逸(Escape Analysis)分析Go變數
- go語言變數Go變數
- GO語言————4.4 變數Go變數
- 初學Go語言 變數Go變數
- Go 語言入門教程:變數Go變數
- Javascript 變數生命週期JavaScript變數
- go語言 變數的宣告與使用Go變數
- go語言變數的宣告與賦值Go變數賦值
- 第四節 go 語言變數定義Go變數
- GO語言————6.3 傳遞變長引數Go
- GO語言變數作用域-坑記錄Go變數
- GO語言基礎(結構+語法+型別+變數)Go型別變數
- Go語言學習(3) - 變數與初始化Go變數
- 《快學 Go 語言》第 2 課 —— 變數基礎Go變數
- go語言採坑:閉包共享變數問題Go變數
- C++ 煉氣期之變數的生命週期和作用域C++變數
- Go 語言核心知識(一)--- 環境變數和原始碼檔案Go變數原始碼
- 翻譯|Rust臨時變數的生命週期和“Super Let”Rust變數
- Go語言中的變數作用域Go變數
- C語言sizeof()變數、字元、字串C語言變數字元字串
- 用 Go 語言讀取專案內 .env 環境變數Go變數
- 三分鐘學會go語言的變數定義Go變數
- [JAVA] Java物件導向之final、abstract抽象、和變數生命週期Java物件抽象變數
- Go 之旅 – 變數Go變數
- C語言可變引數詳解C語言
- Go基礎系列:常量和變數Go變數
- go語言中變數前加 *和& 有啥區別啊Go變數
- go 的變數使用Go變數
- JavaScript變數的生命週期:為什麼let不被提升JavaScript變數
- 變數的分類(臨時(本地)變數、環境變數、全域性變數和系統變數)變數
- c語言 - 交換兩個變數(不建立臨時變數)兩種方法C語言變數
- JS變數分析JS變數
- 鴻蒙開發TypeScript語言:【變數宣告】鴻蒙TypeScript變數
- C語言-變數常量資料型別C語言變數資料型別
- C語言--靜態區域性變數C語言變數
- C語言學習筆記之變數C語言筆記變數
- 一個檔案的內容變成一個 go 語言的變數的小工具Go變數
- 成員變數、全域性變數、例項變數、類變數、靜態變數和區域性變數的區別變數