成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go 結構體函數調用底層實現

開發 后端
我們來了解一下結構體變量聲明和相關函數調用在機器碼或匯編層面的體現。我們以下面代碼為案例進行分析。

[[432712]]

《Go 語言嵌入和多態機制對比》一文中我們了解了 Go 語言的類型系統。下面,我們就來了解一下 Go 語言是如何實現類型系統特性,我們將會深入到 Go 語言運行時和最終機器碼層面對 Go 語言的結構體、函數調用進行了解。

上文已經提及,Go 語言結構體并非 Java 和 C++ 語言中 class 的概念,下面我們來了解一下結構體變量聲明和相關函數調用在機器碼或匯編層面的體現。我們以下面代碼為案例進行分析。

  1. func (u User) addAgeVal(a int32) int32 { 
  2.     n := u.Age + a 
  3.     return n 
  4.  
  5. func (u *User) addAgePtr(a int32) int32 { 
  6.     n := u.Age + a 
  7.     return n 
  8. func main() { 
  9.     u := User{ID: 1, Name"Tom", Age: 23} 
  10.     s1 := u.addAgeVal(1) 
  11.     s2 := u.addAgePtr(2) 
  12.     println(s1 == s2) 

將上述代碼使用如下命令編譯成機器碼,其中 GOOS 指定目標操作系統,GOARCH 指定 CPU 架構,-S 表示打印機器碼,-N 是禁止編譯器優化,-l 是禁止內聯,本機 Go 版本為 go1.16.4。

  1. GOOS=linux GOARCH=amd64 go tool compile -S -N -l main.go 

變量聲明和初始化

我們首先來看 main 函數中 u 變量的聲明和初始化過程。匯編代碼較大,下面只截取部分內容展示,具體如下所示。

由上可見,結構體真的就是基礎類型變量的集合,并沒有額外其他信息的加載,對于類型為 User 的 u 變量的聲明并初始化語句,首先將對應的棧內空間清零,然后依次處理三個初始化參數值,并加載到對應的棧空間位置,完成初始化過程。

其中 ID 和 Age 由于是基礎類型,所以較為簡單,而 Name 字段涉及到 string 類型,稍有區別,String類型的運行時表達,具體如下所示。

  1. type** StringHeader struct { 
  2.     Data uintptr 
  3.     Len int 

由此可見上述匯編中首先將 Tom 字面量地址加載到棧內空間,Tom 字面量則存儲在內存數據段中,給 Data 變量賦值,然后將字面量的長度 3 加載到對應位置,給 Len 變量賦值,具體如下圖所示。

SP 代表棧頂指針,而 "".u +64(SP) 代表相對于棧頂偏移 64 字節的位置,u 則是引用地址的別名,也正是變量 u 的名稱。如圖所示,在棧空間中,并不存在結構體 User,而是由基礎類型數值和指針等組成的一段空間,這段空間就代表著結構體 User。

從棧頂向棧底方向依次為占 8 字節的代表 User.ID 的常量值1,占據 16 字節的代表 User.Name 的字符串 Tom 值地址和占據 8 字節的代表 User.Age 的常量 23,其中字符串 Tom 又由 8 字節的 Data 指針和 8 字節的 Len 組成。

上述代碼中變量 u 未發生逃逸,所以分配在棧中,如果將變量聲明成指針類型并且符合逃逸規則,該結構體就會分配在堆上。

  1. func makeUser() *User { 
  2.     u := &User{ID: 1, Name"Tom", Age: 23} 
  3.    return u 

上述指針變量聲明和初始化過程的匯編如下所示。

可以看出匯編代碼會首先將 Cat 結構體的類型指針加載到棧頂,作為參數;然后調用 newObject 函數來在堆上按照 Cat 結構體類型分配對應的空間,并返回空間的起始地址;最后使用該起始地址設置結構體的變量。

分配在堆上的結構體示意圖在上一個圖的右側顯示。我們可以看到,當結構體分配在棧上時,其內部成員變量會依次排列,占據各自固定的空間;而結構體分配在堆上時,其在棧上只會存在一個指向堆地址的指針,該指針指向結構體在堆上的起始位置。

值接收器函數

下面我們來看一下結構體作為函數接收器如何進行函數調用,包括如何如何傳遞參數和返回值,如何進行值接收器和指針接收器轉換等。上述例子中涉及函數調用的片段如下所示:

Go 的調用規約要求函數參數和返回值都通過棧來傳遞,這部分空間由調用方在其棧幀(stack frame)上提供。

  • 函數接收器是隱式的第一個函數參數,所以上述代碼片段的第一步就是講變量 u 拷貝到對應的棧空間上,這也正對應了值接收器的拷貝機制;
  • 然后第二步則是聲明 int32 類型的值為 1 的參數 a 并分配到指定位置;
  • 接著是使用 CALL 指令調用 User 的 addAgeVal 函數,CALL 指令會將函數的返回值地址推到棧頂,也就是會存儲棧的 +40(SP) 位置上;
  • 而最后會將其值加載到 +60(SP) 上,也就是將函數返回值賦值給變量 s1。

下面,我們來看一下被調用函數 addAgeVal 函數的相關機器碼表達。

addAgeVal 函數大致分為四個步驟:

  • 使用 SUBQ 指令將 SP 減少 16,代表棧增長 16 字節,因為棧幀是向低位增長,其中 8 個字節用于存儲當前的棧幀指針,并使用 LEAQ 計算出新的棧幀指針存到BP中;
  • 初始化函數返回值,因為是其類型是 int32,所以將其設置為對應的零值,棧空間地址是 +64(SP);
  • 從 +48(SP) 位置加載函數接收器 User 的變量 Age 到 AX 寄存器,然后將其和函數參數 a 累加,其位置為 +56(SP)
  • 將二者的和賦值給變量 n,并且將二者的和保存到返回值所在棧空間,也就是 +64(SP);
  • 從 8(SP) 中取出舊棧幀指針,并且將棧幀縮小 16 字節,并調用 RET 指令返回。

綜上,main 函數調用 User 的 addAgeVal 函數的過程如下圖所示。

 如上圖所示,我們看到在 main 函數執行 call 指令前,為調用函數 addAgeVal 的參數和返回值準備好了空間,然后將函數接收器 u 和對應的參數 a 按照順序拷貝到該空間上,然后預留 +40(SP) 的位置給函數調用的返回值。

也正是因為值接收器和函數參數發生拷貝,所以函數內對其修改不會影響原值。

調用 call 指令時,會將指令返回地址壓入棧首,然后再執行 addAgeVal 函數的指令,將棧頂增長 16 字節,從而導致函數接收器、參數和返回值的相對于SP的地址發生變化,增加了 16 字節,所以大家會發現 addAgeVal 函數中指令操作的相對地址發生了變化。

指針接收器函數

下面,我們來看調用指針接收器函數 addAgePtr 相關的具體指令,體會它與值接收器函數的區別。

可以看到調用 addAgePtr 時不會對接收器 u 進行拷貝,而只是將 u 的起始棧地址加載到棧頂,這其實就相當于傳遞了指向 u 的指針。然后是設置參數 a 的值,最后使用 CALL 指令調用 addAgePtr 函數。

而 addAgePtr 函數的指令和 addAgeVal 類似,唯一不同的是要使用指針來獲取接收器 u 的 Age 變量的值,具體如下所示。

從對應的棧空間取到接收器 u 的指針,也就是其起始地址,從起始地址偏移 24 字節就是接收器 u 的 Age 變量位置。整個流程如下圖所示。

如上圖所示,可以看到指針接收器的函數調用時,只需要將其地址作為默認參數進行傳遞,所以在函數內的對接收器的修改,都是直接修改在原值上。

此外,調用 addAgePtr 的場景是在值變量上調用指針接收器函數,我們看到編譯器將值的地址取出作為接收器參數進行傳遞,而如果是指針變量調用值接收器函數的話,則會先對指針進行取地址,然后再將指針指向的值數據進行拷貝。

 

綜上,我們了解了 Go 語言中結構器和結構體函數在機器層級方面的底層實現,后續文章我們再繼續了解 Go 語言相關特性的底層實現。

 

責任編輯:武曉燕 來源: 程序員歷小冰
相關推薦

2020-12-02 09:10:22

Go結構數據類型

2024-10-16 09:57:52

空結構體map屬性

2021-04-20 09:00:48

Go 語言結構體type

2023-07-29 15:03:29

2020-12-02 08:45:36

Go語言

2020-11-30 06:17:03

Go語言

2020-11-26 06:40:24

Go語言基礎

2020-11-23 08:54:14

Go語言結構體

2017-08-31 11:28:47

Slice底層實現

2023-11-21 08:03:43

語言架構偏移量

2021-12-21 08:51:13

Go數據Model

2021-12-20 07:59:07

Go語言結構體

2009-08-13 14:24:44

C#結構體構造函數

2021-11-15 06:56:46

Go語言Tag

2025-05-16 10:05:00

WOLGoSocket

2025-06-12 02:21:00

2021-08-29 07:41:48

數據HashMap底層

2021-11-02 14:54:41

Go結構體標簽

2009-08-13 14:36:40

C#結構體構造函數

2024-10-14 08:51:52

協程Go語言
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 美女国内精品自产拍在线播放 | 99精品99久久久久久宅男 | 国产精品一区一区 | 91久久精品一区二区二区 | 国产成人精品一区二区三区在线 | 亚洲国产欧美国产综合一区 | 中文字幕电影在线观看 | 久久久久久国产精品久久 | 久久99精品久久久久久秒播九色 | 亚洲成人一区二区三区 | 91福利网 | 亚洲天堂免费在线 | 亚洲精品一区二区 | 色毛片 | 国产色黄 | 日韩欧美视频免费在线观看 | 成人午夜性成交 | 中文字幕第100页 | 成人不卡视频 | 黑人巨大精品欧美一区二区一视频 | 欧美日韩一卡二卡 | 精品1区2区3区 | 9色网站 | 在线视频国产一区 | 国产一区二区三区久久久久久久久 | 国产一区91精品张津瑜 | 91九色porny首页最多播放 | 国产精品久久久久久久久久久久久 | 99精品久久久久久中文字幕 | 免费视频一区二区三区在线观看 | 久久精品国产一区二区电影 | 欧美成人猛片aaaaaaa | 久久逼逼 | 成人精品一区亚洲午夜久久久 | a久久| 一级黄a视频 | 国产乱码精品一区二区三区av | 九九色九九| 玩丰满女领导对白露脸hd | 2018中文字幕第一页 | 精品成人 |