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

Go 結(jié)構(gòu)體函數(shù)調(diào)用底層實(shí)現(xiàn)

開發(fā) 后端
我們來(lái)了解一下結(jié)構(gòu)體變量聲明和相關(guān)函數(shù)調(diào)用在機(jī)器碼或匯編層面的體現(xiàn)。我們以下面代碼為案例進(jìn)行分析。

[[432712]]

《Go 語(yǔ)言嵌入和多態(tài)機(jī)制對(duì)比》一文中我們了解了 Go 語(yǔ)言的類型系統(tǒng)。下面,我們就來(lái)了解一下 Go 語(yǔ)言是如何實(shí)現(xiàn)類型系統(tǒng)特性,我們將會(huì)深入到 Go 語(yǔ)言運(yùn)行時(shí)和最終機(jī)器碼層面對(duì) Go 語(yǔ)言的結(jié)構(gòu)體、函數(shù)調(diào)用進(jìn)行了解。

上文已經(jīng)提及,Go 語(yǔ)言結(jié)構(gòu)體并非 Java 和 C++ 語(yǔ)言中 class 的概念,下面我們來(lái)了解一下結(jié)構(gòu)體變量聲明和相關(guān)函數(shù)調(diào)用在機(jī)器碼或匯編層面的體現(xiàn)。我們以下面代碼為案例進(jìn)行分析。

  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) 

將上述代碼使用如下命令編譯成機(jī)器碼,其中 GOOS 指定目標(biāo)操作系統(tǒng),GOARCH 指定 CPU 架構(gòu),-S 表示打印機(jī)器碼,-N 是禁止編譯器優(yōu)化,-l 是禁止內(nèi)聯(lián),本機(jī) Go 版本為 go1.16.4。

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

變量聲明和初始化

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

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

其中 ID 和 Age 由于是基礎(chǔ)類型,所以較為簡(jiǎn)單,而 Name 字段涉及到 string 類型,稍有區(qū)別,String類型的運(yùn)行時(shí)表達(dá),具體如下所示。

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

由此可見上述匯編中首先將 Tom 字面量地址加載到棧內(nèi)空間,Tom 字面量則存儲(chǔ)在內(nèi)存數(shù)據(jù)段中,給 Data 變量賦值,然后將字面量的長(zhǎng)度 3 加載到對(duì)應(yīng)位置,給 Len 變量賦值,具體如下圖所示。

SP 代表?xiàng)m斨羔槪?"".u +64(SP) 代表相對(duì)于棧頂偏移 64 字節(jié)的位置,u 則是引用地址的別名,也正是變量 u 的名稱。如圖所示,在棧空間中,并不存在結(jié)構(gòu)體 User,而是由基礎(chǔ)類型數(shù)值和指針等組成的一段空間,這段空間就代表著結(jié)構(gòu)體 User。

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

上述代碼中變量 u 未發(fā)生逃逸,所以分配在棧中,如果將變量聲明成指針類型并且符合逃逸規(guī)則,該結(jié)構(gòu)體就會(huì)分配在堆上。

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

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

可以看出匯編代碼會(huì)首先將 Cat 結(jié)構(gòu)體的類型指針加載到棧頂,作為參數(shù);然后調(diào)用 newObject 函數(shù)來(lái)在堆上按照 Cat 結(jié)構(gòu)體類型分配對(duì)應(yīng)的空間,并返回空間的起始地址;最后使用該起始地址設(shè)置結(jié)構(gòu)體的變量。

分配在堆上的結(jié)構(gòu)體示意圖在上一個(gè)圖的右側(cè)顯示。我們可以看到,當(dāng)結(jié)構(gòu)體分配在棧上時(shí),其內(nèi)部成員變量會(huì)依次排列,占據(jù)各自固定的空間;而結(jié)構(gòu)體分配在堆上時(shí),其在棧上只會(huì)存在一個(gè)指向堆地址的指針,該指針指向結(jié)構(gòu)體在堆上的起始位置。

值接收器函數(shù)

下面我們來(lái)看一下結(jié)構(gòu)體作為函數(shù)接收器如何進(jìn)行函數(shù)調(diào)用,包括如何如何傳遞參數(shù)和返回值,如何進(jìn)行值接收器和指針接收器轉(zhuǎn)換等。上述例子中涉及函數(shù)調(diào)用的片段如下所示:

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

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

下面,我們來(lái)看一下被調(diào)用函數(shù) addAgeVal 函數(shù)的相關(guān)機(jī)器碼表達(dá)。

addAgeVal 函數(shù)大致分為四個(gè)步驟:

  • 使用 SUBQ 指令將 SP 減少 16,代表?xiàng)T鲩L(zhǎng) 16 字節(jié),因?yàn)闂窍虻臀辉鲩L(zhǎng),其中 8 個(gè)字節(jié)用于存儲(chǔ)當(dāng)前的棧幀指針,并使用 LEAQ 計(jì)算出新的棧幀指針存到BP中;
  • 初始化函數(shù)返回值,因?yàn)槭瞧漕愋褪?int32,所以將其設(shè)置為對(duì)應(yīng)的零值,棧空間地址是 +64(SP);
  • 從 +48(SP) 位置加載函數(shù)接收器 User 的變量 Age 到 AX 寄存器,然后將其和函數(shù)參數(shù) a 累加,其位置為 +56(SP)
  • 將二者的和賦值給變量 n,并且將二者的和保存到返回值所在棧空間,也就是 +64(SP);
  • 從 8(SP) 中取出舊棧幀指針,并且將棧幀縮小 16 字節(jié),并調(diào)用 RET 指令返回。

綜上,main 函數(shù)調(diào)用 User 的 addAgeVal 函數(shù)的過程如下圖所示。

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

也正是因?yàn)橹到邮掌骱秃瘮?shù)參數(shù)發(fā)生拷貝,所以函數(shù)內(nèi)對(duì)其修改不會(huì)影響原值。

調(diào)用 call 指令時(shí),會(huì)將指令返回地址壓入棧首,然后再執(zhí)行 addAgeVal 函數(shù)的指令,將棧頂增長(zhǎng) 16 字節(jié),從而導(dǎo)致函數(shù)接收器、參數(shù)和返回值的相對(duì)于SP的地址發(fā)生變化,增加了 16 字節(jié),所以大家會(huì)發(fā)現(xiàn) addAgeVal 函數(shù)中指令操作的相對(duì)地址發(fā)生了變化。

指針接收器函數(shù)

下面,我們來(lái)看調(diào)用指針接收器函數(shù) addAgePtr 相關(guān)的具體指令,體會(huì)它與值接收器函數(shù)的區(qū)別。

可以看到調(diào)用 addAgePtr 時(shí)不會(huì)對(duì)接收器 u 進(jìn)行拷貝,而只是將 u 的起始棧地址加載到棧頂,這其實(shí)就相當(dāng)于傳遞了指向 u 的指針。然后是設(shè)置參數(shù) a 的值,最后使用 CALL 指令調(diào)用 addAgePtr 函數(shù)。

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

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

如上圖所示,可以看到指針接收器的函數(shù)調(diào)用時(shí),只需要將其地址作為默認(rèn)參數(shù)進(jìn)行傳遞,所以在函數(shù)內(nèi)的對(duì)接收器的修改,都是直接修改在原值上。

此外,調(diào)用 addAgePtr 的場(chǎng)景是在值變量上調(diào)用指針接收器函數(shù),我們看到編譯器將值的地址取出作為接收器參數(shù)進(jìn)行傳遞,而如果是指針變量調(diào)用值接收器函數(shù)的話,則會(huì)先對(duì)指針進(jìn)行取地址,然后再將指針指向的值數(shù)據(jù)進(jìn)行拷貝。

 

綜上,我們了解了 Go 語(yǔ)言中結(jié)構(gòu)器和結(jié)構(gòu)體函數(shù)在機(jī)器層級(jí)方面的底層實(shí)現(xiàn),后續(xù)文章我們?cè)倮^續(xù)了解 Go 語(yǔ)言相關(guān)特性的底層實(shí)現(xiàn)。

 

責(zé)任編輯:武曉燕 來(lái)源: 程序員歷小冰
相關(guān)推薦

2020-12-02 09:10:22

Go結(jié)構(gòu)數(shù)據(jù)類型

2024-10-16 09:57:52

空結(jié)構(gòu)體map屬性

2023-07-29 15:03:29

2021-04-20 09:00:48

Go 語(yǔ)言結(jié)構(gòu)體type

2020-11-23 08:54:14

Go語(yǔ)言結(jié)構(gòu)體

2020-12-02 08:45:36

Go語(yǔ)言

2020-11-30 06:17:03

Go語(yǔ)言

2020-11-26 06:40:24

Go語(yǔ)言基礎(chǔ)

2017-08-31 11:28:47

Slice底層實(shí)現(xiàn)

2023-11-21 08:03:43

語(yǔ)言架構(gòu)偏移量

2021-12-21 08:51:13

Go數(shù)據(jù)Model

2021-12-20 07:59:07

Go語(yǔ)言結(jié)構(gòu)體

2009-08-13 14:24:44

C#結(jié)構(gòu)體構(gòu)造函數(shù)

2021-11-15 06:56:46

Go語(yǔ)言Tag

2025-06-12 02:21:00

2025-05-16 10:05:00

WOLGoSocket

2021-08-29 07:41:48

數(shù)據(jù)HashMap底層

2021-11-02 14:54:41

Go結(jié)構(gòu)體標(biāo)簽

2009-08-13 14:36:40

C#結(jié)構(gòu)體構(gòu)造函數(shù)

2021-12-27 08:53:23

Go函數(shù) Nil
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产精品国产三级国产aⅴ无密码 | 香蕉二区| 亚洲狠狠爱一区二区三区 | 九九热精品视频 | 国产精品成人品 | 亚洲综合字幕 | 久久国产精品久久久久 | 粉嫩av久久一区二区三区 | 二区在线视频 | 欧州一区二区三区 | 四季久久免费一区二区三区四区 | 日韩免费毛片视频 | 欧美激情亚洲天堂 | 久久激情av | 一级毛片观看 | 狠狠操电影 | 草草视频在线播放 | 91传媒在线观看 | 在线一区二区观看 | 亚洲 中文 欧美 日韩 在线观看 | 中文字幕国产视频 | 久久狠狠 | 中文字幕 在线观看 | 国产毛片久久久久久久久春天 | 久久大陆 | 自拍偷拍精品 | 国产伦精品一区二区三区四区视频 | 亚洲国产成人精品久久 | 日本成人午夜影院 | 成人综合伊人 | 日韩国产一区二区三区 | 国产精品一二三区 | 久草日韩 | 亚洲 中文 欧美 日韩 在线观看 | 美女张开腿露出尿口 | 9久9久9久女女女九九九一九 | 亚洲日本中文字幕在线 | 91精品久久久久久久99 | 精品一二区 | 青青久在线视频 | www.久久国产精品 |