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

走進(jìn)Golang之運(yùn)行與Plan9匯編

開發(fā) 后端
今天我們一起來學(xué)習(xí)一下生成的目標(biāo)代碼如何在計(jì)算機(jī)上執(zhí)行。以及通過查閱 Golang 的 Plan9 匯編來了解Golang的一些內(nèi)部秘密。

通過上一篇走進(jìn)Golang之匯編原理,我們知道了目標(biāo)代碼的生成經(jīng)歷了那些過程。今天我們一起來學(xué)習(xí)一下生成的目標(biāo)代碼如何在計(jì)算機(jī)上執(zhí)行。以及通過查閱 Golang 的 Plan9 匯編來了解Golang的一些內(nèi)部秘密。

Golang的運(yùn)行環(huán)境

當(dāng)我們把編譯后的Go代碼運(yùn)行起來,它會(huì)以進(jìn)程的方式出現(xiàn)在系統(tǒng)中。然后開始處理請(qǐng)求、數(shù)據(jù),我們會(huì)看到這個(gè)進(jìn)程占用了內(nèi)存消耗、cpu占比等等信息。本文就是要來解釋在程序的運(yùn)行過程中,內(nèi)存、CPU、操作系統(tǒng)(當(dāng)然還有其它的硬件,文中關(guān)系不大,就不說了)是如何進(jìn)行配合,完成了我們代碼所指定的事情。

內(nèi)存

首先,我們先來說說內(nèi)存。先來看一個(gè)我們運(yùn)行的go進(jìn)程。

代碼如下: 

  1. package main  
  2. import (  
  3.     "fmt"  
  4.     "log"  
  5.     "net/http"  
  6.  
  7. func main() {  
  8.     http.HandleFunc("/", sayHello)  
  9.     err :http.ListenAndServe(":9999", nil)  
  10.     if err != nil {  
  11.         log.Fatal("ListenAndServe: ", err)  
  12.     }  
  13.  
  14. func sayHello(w http.ResponseWriter, r *http.Request) {  
  15.     fmt.Printf("fibonacci: %d\n", fibonacci(1000))  
  16.     _, _ = fmt.Fprint(w, "Hello World!")  
  17.  
  18. func fibonacci(num int) int {  
  19.     if num < 2 {  
  20.         return 1  
  21.     }  
  22.     return fibonacci(num-1) + fibonacci(num-2)  

來看一下執(zhí)行情況:

  1. dayu.com >ps aux  
  2. USER               PID   %CPU  %MEM      VSZ     RSS    TT  STAT   STARTED    TIME     COMMAND  
  3. xxxxx              3584  99.2  0.1     4380456  4376   s003  R+    8:33下午   0:05.81  ./myhttp 

這里我們先來不關(guān)注其它指標(biāo),先來看 VSZ 與 RSS。

  •  VSZ: 是指虛擬地址,他是程序?qū)嶋H操作的內(nèi)存。包含了分配還沒有使用的內(nèi)存。
  •  RSS: 是實(shí)際的物理內(nèi)存,包含了棧內(nèi)存與堆內(nèi)存。

每一個(gè)進(jìn)程都是運(yùn)行在自己的內(nèi)存沙盒里,程序被分配的地址都是 “虛擬內(nèi)存”,物理內(nèi)存對(duì)程序開發(fā)者來說實(shí)際是不可見的,而且虛擬地址比進(jìn)程實(shí)際的物理地址要大的多。我們經(jīng)常編程中取指針對(duì)應(yīng)的地址實(shí)際就是虛擬地址。這里一定要注意區(qū)分虛擬內(nèi)存與物理內(nèi)存。來一張圖感受一下。

這張圖主要是為了說明兩個(gè)問題:

  1.  程序使用的是虛擬內(nèi)存,但是操作系統(tǒng)會(huì)把虛擬內(nèi)存映射到物理內(nèi)存;你會(huì)發(fā)現(xiàn)自己機(jī)器上所有進(jìn)程的VSZ要大得多;
  2.  物理內(nèi)存可以被多個(gè)進(jìn)程共享,甚至一個(gè)進(jìn)程內(nèi)的不同地址可能映射的都是同一個(gè)物理內(nèi)存地址。

上面搞明白了程序中的內(nèi)存具體是指什么,接下來說明程序是如何使用內(nèi)存的(虛擬內(nèi)存),內(nèi)存說白了就是比硬盤存取速度更快的一個(gè)硬件,為了方便內(nèi)存的管理,操作系統(tǒng)把分配給進(jìn)程的內(nèi)存劃分成了不同的功能塊。像我們經(jīng)常說的:代碼區(qū),靜態(tài)數(shù)據(jù)區(qū),堆區(qū),棧區(qū)等。

這里借用一張網(wǎng)絡(luò)上的圖來看一下。

這里就是我們程序(進(jìn)程)在虛擬內(nèi)存中的分布。

代碼區(qū):存放的就是我們編譯后的機(jī)器碼,一般來說這個(gè)區(qū)域只能是只讀。

靜態(tài)數(shù)據(jù)區(qū):存放的是全局變量與常量。這些變量的地址編譯的時(shí)候就確定了(這也是使用虛擬地址的好處,如果是物理地址,這些地址編譯的時(shí)候是不可能確定的)。Data與BSS都屬于這一部分。這部分只有程序中止(kill掉、crasg掉等)才會(huì)被銷毀。

棧區(qū):主要是 Golang 里邊的函數(shù)、方法以及其本地變量存儲(chǔ)的地方。這部分伴隨函數(shù)、方法開始執(zhí)行而分配,運(yùn)行完后就被釋放,特別注意這里的釋放并不會(huì)清空內(nèi)存。后面文章講內(nèi)存分配的時(shí)候再詳細(xì)說;還有一個(gè)點(diǎn)需要記住棧一般是從高地址向低地址方向分配,換句話說:高地址屬于棧低,低地址屬于棧底,它分配方向與堆是相反的。

堆區(qū):像 C/C++ 語言,堆完全是程序員自己控制的。但是 Golang 里邊由于有GC機(jī)制,我們寫代碼的時(shí)候并不需要關(guān)心內(nèi)存是在棧還是堆上分配。Golang 會(huì)自己判斷如果變量的生命周期在函數(shù)退出后還不能銷毀或者棧上資源不夠分配等等情況,就會(huì)被放到堆上。堆的性能會(huì)比棧要差一些。原因也留到內(nèi)存分配相關(guān)的文章再給大家介紹。

內(nèi)存的結(jié)構(gòu)搞明白了,我們的程序被加載到內(nèi)存還需要操作系統(tǒng)來指揮才能正確運(yùn)行。

補(bǔ)充一個(gè)比較重要的概念:

尋址空間:一般指的是CPU對(duì)于內(nèi)存尋址的能力,通俗地說,就是能最多用到多少內(nèi)存的一個(gè)問題。比如:32條地址線(32位機(jī)器),那么總的地址空間就有 2^32 個(gè),如果是64位機(jī)器,就是 2^64 個(gè)尋址空間。可以使用 uname -a 來查看自己系統(tǒng)支持的位數(shù)字。

操作系統(tǒng)、CPU、內(nèi)存互相配合

為了講清楚程序運(yùn)行與調(diào)用,我們得先理清楚操作系統(tǒng)、內(nèi)存、CPU、寄存器這幾者之間的關(guān)系。

  •  CPU: 計(jì)算機(jī)的大腦,它才能理解并執(zhí)行指令;
  •  寄存器:嚴(yán)格講寄存器是CPU的組成部分,它主要負(fù)責(zé)CPU在計(jì)算時(shí)臨時(shí)存儲(chǔ)數(shù)據(jù);當(dāng)然CPU還有多級(jí)的高速緩存,與我們這里相關(guān)度不大,就略過,大家知道其目的是為了彌補(bǔ)內(nèi)存與CPU速度的差距即可;
  •  內(nèi)存:像上面內(nèi)存被劃分成不同區(qū),每一部分存了不同的數(shù)據(jù);當(dāng)然這些區(qū)的劃分、以及虛擬內(nèi)存與物理內(nèi)存的映射都是操作系統(tǒng)來做的;
  •  操作系統(tǒng):控制各種硬件資源,為其它運(yùn)行的程序提供操作接口(系統(tǒng)調(diào)用)及管理。

這里操作系統(tǒng)是一個(gè)軟件,CPU、寄存器、內(nèi)存(物理內(nèi)存)都是實(shí)打?qū)嵉挠布2僮飨到y(tǒng)雖然也是一堆代碼寫出來的。但是她是硬件對(duì)其它應(yīng)用程序的接口??偟膩碇v操作系統(tǒng)通過系統(tǒng)調(diào)用控制所有的硬件資源,他把其它的程序調(diào)度到CPU上讓其它程序執(zhí)行,但是為了讓每個(gè)程序都有機(jī)會(huì)使用CPU,CPU又通過時(shí)間中斷把控制權(quán)交給操作系統(tǒng)。

讓操作系統(tǒng)可以控制我們的程序,我們編寫的程序需要遵循操作系統(tǒng)的規(guī)定。這樣操作系統(tǒng)才能控制程序執(zhí)行、切換進(jìn)程等操作。

最后我們的代碼被編譯成機(jī)器碼之后,本質(zhì)就是一條條的指令。我們期望的就是CPU去執(zhí)行完這些指令進(jìn)而完成任務(wù)。而操作系統(tǒng)又能夠幫助我們讓CPU來執(zhí)行代碼以及提供所需資源的調(diào)用接口(系統(tǒng)調(diào)用)。是不是非常簡(jiǎn)單?

Go程序的調(diào)用規(guī)約

在上面我們知道整個(gè)虛擬內(nèi)存被我們劃分為:代碼區(qū)、靜態(tài)數(shù)據(jù)區(qū)、棧區(qū)、堆區(qū)。接下來要講的Go程序的調(diào)用規(guī)約(其實(shí)就是函數(shù)、方法運(yùn)行的規(guī)則),主要是涉及上面所說的棧部分(堆部分會(huì)在內(nèi)存分配的文章里邊去講)。以及計(jì)算機(jī)軟硬各個(gè)部分如何配合。接下來我們就來看一下程序的基本單位函數(shù)跟方法是怎么執(zhí)行與相互調(diào)用的。

函數(shù)在棧上的分布

這一部分,我們先來了解一些理論,然后接著用一個(gè)實(shí)際的例子來分析一下。先通過一張圖來看一下在 Golang 中函數(shù)是如何在棧上分布的。

幾個(gè)涉及到的專業(yè)用語:

  •  棧:這里說的棧跟上面的解釋含義一致。無論是進(jìn)程、線程、goroutine都有自己的調(diào)用棧;
  •  棧幀:可以理解是函數(shù)調(diào)用時(shí)在棧上為函數(shù)所分配的區(qū)域;
  •  調(diào)用者:caller,比如:a函數(shù)調(diào)用了b函數(shù),那么a就是調(diào)用者;
  •  被調(diào)者:callee,還是上面的例子,b就是被調(diào)者。

棧幀

這幅圖所展示的就是一個(gè) 棧幀 的結(jié)構(gòu)。也可以說棧楨是棧給一個(gè)函數(shù)分配的棧空間,它包括了函數(shù)調(diào)用者地址、本地變量、返回值地址、調(diào)用者參數(shù)等信息。

這里有幾個(gè)注意點(diǎn),圖中的 BP、SP都表示對(duì)應(yīng)的寄存器。

  •  BP:基址指針寄存器(extended base pointer),也叫幀指針,存放著一個(gè)指針,表示函數(shù)棧開始的地方。
  •  SP:棧指針寄存器(extended stack pointer),存放著一個(gè)指針,存儲(chǔ)的是函數(shù)??臻g的棧頂,也就是函數(shù)??臻g分配結(jié)束的地方,注意這里是硬件寄存器,不是Plan9中的偽寄存器。

BP 與 SP 放在一起,一個(gè)表示開始(棧頂)、一個(gè)表示結(jié)束(棧低)。

有了上面的基礎(chǔ)知識(shí),接著下面用實(shí)際的例子來驗(yàn)證一下。

Go的調(diào)用實(shí)例

才開始,我們就從一個(gè)簡(jiǎn)單的函數(shù)開始來分析一下整個(gè)函數(shù)的調(diào)用過程(下面涉及到 Plan9 匯編,請(qǐng)別慌,大部分都能夠看懂,并且我也會(huì)寫注釋)。 

  1. package main  
  2. func main() {  
  3.     a :3  
  4.     b :2  
  5.     returnTwo(a, b)  
  6.  
  7. func returnTwo(a, b int) (c, d int) {  
  8.     tmp :1 // 這一行的主要目的是保證棧楨不為0,方便分析  
  9.     c = a + b  
  10.     d = b - tmp  
  11.     return  

上面有兩個(gè)函數(shù),main 定義了兩個(gè)本地變量,然后調(diào)用 returnTwo 函數(shù)。returnTwo 函數(shù)有兩個(gè)參數(shù)與兩個(gè)返回值。設(shè)計(jì)兩個(gè)返回值主要是一起來看一下 golang 的多返回值是如何實(shí)現(xiàn)的。接下來我們把上面的代碼對(duì)應(yīng)的匯編代碼展示出來。

有幾行代碼需要特別解釋下, 

  1. 0x0000 00000 (test1.go:3) TEXT "".main(SB), ABIInternal, $56-0 

這一行中的重點(diǎn)信息:$56-0。56 表示的該函數(shù)棧楨大?。▋蓚€(gè)本地變量,兩個(gè)參數(shù)是int類型,兩個(gè)返回值是int類型,1個(gè)保存base pointer,合計(jì)7 * 8 = 56);0表示 mian 函數(shù)的參數(shù)與返回值大小。待會(huì)可以在 returnTwo 中去看一下它的返回值又是多少。

接下來在看一下計(jì)算機(jī)是怎么在棧上分配大小的。 

  1. 0x000f 00015 (test1.go:3)       SUBQ    $56, SP // 分配,56的大小在上面第一行定義了  
  2. ... ...  
  3. 0x004b 00075 (test1.go:7)       ADDQ    $56, SP // 釋放掉,但是并未清空 

這兩行,一個(gè)是分配,一個(gè)是釋放。為什么用了 SUBQ 指令就能進(jìn)行分配呢?而 ADDQ 是釋放?記得我們前面說過嗎? SP 是一個(gè)指針寄存器,并且指向棧頂,棧又是從高地址向低地址分配。那么對(duì)它做一次減法,是不是表示從高地址向低地址方向移動(dòng)指針了呢?釋放也是同樣的道理,一次加法操作又把 SP 恢復(fù)到初始狀態(tài)。

再來看一下對(duì) BP 寄存器的操作。 

  1. 0x0013 00019 (test1.go:3)       MOVQ    BP, 48(SP) // 保存BP  
  2. 0x0018 00024 (test1.go:3)       LEAQ    48(SP), BP // BP存放了新的地址  
  3. ... ...  
  4. 0x0046 00070 (test1.go:7)       MOVQ    48(SP), BP // 恢復(fù)BP的地址 

這三行代碼是不是感覺很變扭?寫來寫去讓人云里霧里的。我先用文字描述一下,后面再用圖來解釋。

    我們先做如下假設(shè):此時(shí) BP 指向的 值 是:0x00ff,48(SP) 的 地址 是:0x0008。

  •  第一條指令 MOVQ BP, 48(SP) 是把 0x00ff 寫入到 48(SP)的位置;
  •  第二條指令 LEAQ 48(SP), BP 是更新寄存器指針,讓 BP 保存 48(SP) 這個(gè)位置的地址,也就是 0x00ff 這個(gè)值。
  •  第三條指令 MOVQ 48(SP), BP ,因?yàn)橐婚_始 48(SP) 保存了最開始 BP 的所存的值 0x00ff,所以這里是又把 BP 恢復(fù)回去了。

這幾行代碼的作用至關(guān)重要,正因?yàn)槿绱嗽趫?zhí)行的時(shí)候,我們才能找到函數(shù)開始的地方以及回到調(diào)用函數(shù)的位置,它才可以繼續(xù)往下執(zhí)行(如果覺得饒,先放過,后面有圖,看完后再回來理解)。接著來看一下 returnTwo 函數(shù)。

這里 NOSPLIT|ABIInternal, $0-32 說明,該函數(shù)的棧楨大小是0,由于有兩個(gè)int參數(shù),以及2個(gè)int返回值,合計(jì)為 4*8 = 32 字節(jié)大小,是不是跟上面的 main 函數(shù)對(duì)上了?。

這里有沒有對(duì) returnTwo 函數(shù)的棧楨大小是0表示迷惑呢?難道這個(gè)函數(shù)不需要棧空間嗎?其實(shí)主要原因是:golang的參數(shù)傳遞與返回值都是要求使用棧來進(jìn)行的(這也是為什么go能夠支持多參數(shù)返回的原因)。所以參數(shù)與返回值所需空間都由 caller 來提供。

接下來,我們用完整的圖來演示一下這個(gè)調(diào)用過程。

這個(gè)圖就畫了將近1個(gè)小時(shí),希望對(duì)大家理解有幫助。

整個(gè)的流程是:初始化 ----> call main function ----> call returnTwo function ----> returnTwo return ----> main return。

通過這張圖,在結(jié)合我上面的文字解釋,相信大家能夠理解了。不過這里還有幾個(gè)注意點(diǎn):

  •  BP 與 SP 是寄存器,它保存的是棧上的地址,所以執(zhí)行中可以對(duì) SP 做運(yùn)算找到下一個(gè)指令的位置;
  •  棧被回收 ADDQ $56, SP ,只是改變了 SP 指向的位置,內(nèi)存中的數(shù)據(jù)并不會(huì)清空,只有下次被分配使用的時(shí)候才會(huì)清空;
  •  callee的參數(shù)、返回值內(nèi)存都是caller分配的;
  •  returnTwo ret的時(shí)候,call returnTwo的next指令 所在棧位置會(huì)被彈出,也就是圖中 0x0d00 地址所保存的指令,所以 returnTwo 函數(shù)返回后,SP 又指向了 0x0d08 地址。

由于上面涉及到一些 Plan9 的知識(shí),就順帶一起介紹一些它的語法,如果直接講語法會(huì)很枯燥,下面會(huì)結(jié)合一些實(shí)際中會(huì)用到的情況來介紹。既有收獲又能學(xué)會(huì)語法。

Go的匯編plan9

我們整個(gè)程序的編譯最終會(huì)被翻譯成機(jī)器碼,而匯編可以算是機(jī)器碼的文本形式,他們之間可以一一對(duì)應(yīng)。所以如果我們能夠看懂匯編一點(diǎn)點(diǎn)就能夠分析出很多實(shí)際問題。

開發(fā)go語言的都是當(dāng)前世界最TOP的那群程序員,他們選擇了持續(xù)裝逼,不用標(biāo)準(zhǔn)的 AT&T 也不用 Intel 匯編器,偏要自己搞一套,沒辦法,誰讓人家牛呢!Golang的匯編是基于 Plan9 匯編的,個(gè)人覺得要完全學(xué)懂太復(fù)雜了,因?yàn)檫@涉及到很多底層知識(shí)。不過如果只是要求看懂還是能夠做到的。下面我們就舉一些例子來試試看。

PS: 這東西完全學(xué)懂也沒有必要,投入產(chǎn)出比太低了,對(duì)于一個(gè)應(yīng)用工程師能夠看懂就行。

在正式開始前,我們還是補(bǔ)充一些必要信息,上文已經(jīng)涉及過一些,為了完整這里在整體介紹一下。

幾個(gè)重要的偽寄存器

  •  SB:是一個(gè)虛擬寄存器,保存了靜態(tài)基地址(static-base) 指針,即我們程序地址空間的開始地址;
  •  NOSPLIT:向編譯器表明不應(yīng)該插入 stack-split 的用來檢查棧需要擴(kuò)張的前導(dǎo)指令;
  •  FP:使用形如 symbol+offset(FP) 的方式,引用函數(shù)的輸入?yún)?shù);
  •  SP:plan9 的這個(gè) SP 寄存器指向當(dāng)前棧幀的局部變量的開始位置,使用形如 symbol+offset(SP) 的方式,引用函數(shù)的局部變量,注意:這個(gè)寄存器與上文的寄存器是不一樣的,這里是偽寄存器,而我們展示出來的都是硬件寄存器。

其它還有一些操作指令,根據(jù)名字多半都能夠看出來,就不再介紹,直接開始干。

查看go應(yīng)用代碼對(duì)應(yīng)的翻譯函數(shù) 

  1. package main  
  2. func main() {  
  3. func test() []string {  
  4.     a :make([]string, 10)  
  5.     return a  
  6.  
  7. --------  
  8. "".test STEXT size=151 args=0x18 locals=0x40  
  9.         0x0000 00000 (test1.go:6)       TEXT    "".test(SB), ABIInternal, $64-24 // 棧幀大小,與參數(shù)、返回值大小  
  10.         0x0000 00000 (test1.go:6)       MOVQ    (TLS), CX  
  11.         0x0009 00009 (test1.go:6)       CMPQ    SP, 16(CX)  
  12.         0x000d 00013 (test1.go:6)       JLS     141  
  13.         0x000f 00015 (test1.go:6)       SUBQ    $64, SP  
  14.         0x0013 00019 (test1.go:6)       MOVQ    BP, 56(SP)  
  15.         0x0018 00024 (test1.go:6)       LEAQ    56(SP), BP 
  16.         ... ...  
  17.         0x001d 00029 (test1.go:6)       MOVQ    $0, "".~r0+72(SP)  
  18.         0x0026 00038 (test1.go:6)       XORPS   X0, X0  
  19.         0x0029 00041 (test1.go:6)       MOVUPS  X0, "".~r0+80(SP)  
  20.         0x002e 00046 (test1.go:7)       PCDATA  $2, $1  
  21.         0x002e 00046 (test1.go:7)       LEAQ    type.string(SB), AX  
  22.         0x0035 00053 (test1.go:7)       PCDATA  $2, $0  
  23.         0x0035 00053 (test1.go:7)       MOVQ    AX, (SP)  
  24.         0x0039 00057 (test1.go:7)       MOVQ    $10, 8(SP)  
  25.         0x0042 00066 (test1.go:7)       MOVQ    $10, 16(SP)  
  26.         0x004b 00075 (test1.go:7)       CALL    runtime.makeslice(SB) // 對(duì)應(yīng)的底層runtime function 
  27.         ... ...  
  28.         0x008c 00140 (test1.go:8)       RET  
  29.         0x008d 00141 (test1.go:8)       NOP  
  30.         0x008d 00141 (test1.go:6)       PCDATA  $0, $-1  
  31.         0x008d 00141 (test1.go:6)       PCDATA  $2, $-1  
  32.         0x008d 00141 (test1.go:6)       CALL    runtime.morestack_noctxt(SB)  
  33.         0x0092 00146 (test1.go:6)       JMP     0 

根據(jù)對(duì)應(yīng)的代碼行數(shù)與名字,很明顯的可以看到應(yīng)用層寫的 make 對(duì)應(yīng)底層是 makeslice。

逃逸分析

這里先說一下逃逸分析的概念。這里牽扯到棧、堆分配的問題。如果變量被分配到棧上,會(huì)伴隨函數(shù)調(diào)用結(jié)束自動(dòng)回收,并且分配效率很高;其次分配到堆上,則需要GC進(jìn)行標(biāo)記回收。所謂逃逸就是指變量從棧上逃到了堆上(很多人對(duì)這個(gè)概念都不清楚就在談逃逸分析,面試遇到了好幾次😓)。 

  1. package main  
  2. func main() {  
  3.  
  4. func test() *int {  
  5.     t :3  
  6.     return &t  
  7.  
  8. ------  
  9. "".test STEXT size=98 args=0x8 locals=0x20  
  10.         0x0000 00000 (test1.go:6)       TEXT    "".test(SB), ABIInternal, $32-8  
  11.         0x0000 00000 (test1.go:6)       MOVQ    (TLS), CX  
  12.         0x0009 00009 (test1.go:6)       CMPQ    SP, 16(CX)  
  13.         0x000d 00013 (test1.go:6)       JLS     91  
  14.         0x000f 00015 (test1.go:6)       SUBQ    $32, SP  
  15.         0x0013 00019 (test1.go:6)       MOVQ    BP, 24(SP)  
  16.         0x0018 00024 (test1.go:6)       LEAQ    24(SP), BP  
  17.         ... ...  
  18.         0x001d 00029 (test1.go:6)       MOVQ    $0, "".~r0+40(SP)  
  19.         0x0026 00038 (test1.go:7)       PCDATA  $2, $1  
  20.         0x0026 00038 (test1.go:7)       LEAQ    type.int(SB), AX  
  21.         0x002d 00045 (test1.go:7)       PCDATA  $2, $0  
  22.         0x002d 00045 (test1.go:7)       MOVQ    AX, (SP)  
  23.         0x0031 00049 (test1.go:7)       CALL    runtime.newobject(SB) // 堆上分配空間,表示逃逸了  
  24.         ... ... 

這里如果是對(duì) slice 使用匯編進(jìn)行逃逸分析,并不會(huì)很直觀。因?yàn)橹粫?huì)看到調(diào)用了 runtime.makeslice 函數(shù),該函數(shù)內(nèi)部其實(shí)又調(diào)用了 runtime.mallocgc 函數(shù),這個(gè)函數(shù)會(huì)分配的內(nèi)存其實(shí)就是堆上的內(nèi)存(如果棧上足夠保存,是不會(huì)看到對(duì) runtime.makslice 函數(shù)的調(diào)用)。

實(shí)際go也提供了更方便的命令來進(jìn)行逃逸分析:go build -gcflags="-m" ,如果真的是做逃逸分析,建議使用該命令,別折騰用匯編。

傳值還是傳指針

對(duì)于golang中的基本類型:字符串、整型、布爾類型就不多說了,肯定是值傳遞,那么對(duì)于結(jié)構(gòu)體、指針到底是值傳遞還是指針傳遞呢? 

  1. package main    
  2. type Student struct {    
  3.     name string    
  4.     age  int    
  5. }    
  6. func main() {    
  7.     jack := &Student{"jack", 30}    
  8.     test(jack)    
  9. }    
  10. func test(s *Student) *Student {    
  11.     return s    
  12. }    
  13. -------    
  14. "".test STEXT nosplit size=20 args=0x10 locals=0x0    
  15.         0x0000 00000 (test1.go:14)      TEXT    "".test(SB), NOSPLIT|ABIInternal, $0-16   
  16.         ... ...   
  17.         0x0000 00000 (test1.go:14)      MOVQ    $0, "".~r1+16(SP) // 初始返回值為0   
  18.         0x0009 00009 (test1.go:15)      PCDATA  $2, $1   
  19.         0x0009 00009 (test1.go:15)      PCDATA  $0, $1   
  20.         0x0009 00009 (test1.go:15)      MOVQ    "".s+8(SP), AX // 將引用地址復(fù)制到 AX 寄存器  
  21.         0x000e 00014 (test1.go:15)      PCDATA  $2, $0   
  22.         0x000e 00014 (test1.go:15)      PCDATA  $0, $2   
  23.         0x000e 00014 (test1.go:15)      MOVQ    AX, "".~r1+16(SP) // 將 AX 的引用地址又復(fù)制到返回地址   
  24.         0x0013 00019 (test1.go:15)      RET  

 

通過這里可以看到在go里邊,只有值傳遞,因?yàn)樗讓舆€是通過拷貝對(duì)應(yīng)的值。

總結(jié)

今天的文章到此結(jié)束,本次主要講了下面幾個(gè)點(diǎn):

  1.  計(jì)算機(jī)軟硬資源之間的相互配合;
  2.  Golang 編寫的代碼,函數(shù)與方法是怎么執(zhí)行的,主要講了棧上分配與相關(guān)調(diào)用;
  3.  使用 Plan9 分析了一些常見的問題。

希望本文對(duì)大家在理解、學(xué)習(xí)Go的路上有一些幫助。 

責(zé)任編輯:龐桂玉 來源: segmentfault
相關(guān)推薦

2023-04-17 14:32:20

2019-11-15 15:20:27

Golang編譯器前端

2021-05-18 09:39:19

互聯(lián)網(wǎng)操作系統(tǒng)Go

2014-07-09 13:18:55

編程谷歌

2013-12-30 11:21:31

Go編譯器

2025-05-28 01:58:00

Go語言UTF-8

2012-11-13 10:27:45

PythonGo編程語言

2021-07-05 15:22:07

紫光CIO

2012-08-20 10:49:13

編程

2016-11-01 19:10:33

vue.js前端前端框架

2012-08-10 09:45:17

IBM專家集成系統(tǒng)PureSystemPureFlex Sy

2011-04-01 09:34:18

C#奇妙函數(shù)

2021-12-23 06:57:32

數(shù)據(jù)通信Websocket

2012-08-20 09:53:48

編程編程建議程序員

2022-01-21 10:58:39

JavaScriptGolangPython

2021-07-22 09:43:09

Golang語言并發(fā)機(jī)制

2020-12-22 11:54:42

C語言Cortex-A9LED匯編

2017-01-23 13:34:44

2010-05-24 18:19:44

SNMP報(bào)文

2021-05-07 09:17:21

HTTPTCP協(xié)議
點(diǎn)贊
收藏

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

主站蜘蛛池模板: av一区在线 | 免费视频一区 | 一级毛片视频 | 欧美一级片在线看 | 欧美久 | 日韩欧美精品一区 | 国产日韩精品一区二区三区 | 欧美寡妇偷汉性猛交 | 欧美亚洲国产精品 | 亚洲欧美在线一区 | 日日操夜夜操视频 | 久久亚洲国产 | 欧美激情在线精品一区二区三区 | 欧美激情欧美激情在线五月 | 日韩一区二区av | 毛片一区二区三区 | 亚洲自拍一区在线观看 | 亚洲国产区 | 久久久精品一区 | 在线中文视频 | 性做久久久久久免费观看欧美 | 91久久国产综合久久 | 日本一道本视频 | 国产精品久久久久久久7电影 | 精品日韩 | 亚洲欧美日韩高清 | 久久精品一区二区三区四区 | 亚洲在线免费 | 成人亚洲 | 麻豆一区二区三区精品视频 | 秋霞在线一区 | 中文字幕不卡 | 欧美日韩中文在线 | 亚洲在线久久 | 午夜网| 激情五月激情综合网 | 日本成人三级电影 | 成人自拍视频 | 热久久999 | 久久中文高清 | 日韩av免费在线电影 |