最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang匯編之控制流深入分析講解

 更新時(shí)間:2023年05月15日 11:48:02   作者:raoxiaoya  
這篇文章主要介紹了Golang匯編之控制流,程序執(zhí)行的流程主要有順序、分支和循環(huán)幾種執(zhí)行流程,本節(jié)主要討論如何將Go語言的控制流比較直觀地轉(zhuǎn)譯為匯編程序,或者說如何以匯編思維來編寫Go語言代碼,感興趣的同學(xué)可以參考下文

順序執(zhí)行

順序執(zhí)行是我們比較熟悉的工作模式,類似俗稱流水賬編程。所有不含分支、循環(huán)和goto語言,并且每一遞歸調(diào)用的Go函數(shù)一般都是順序執(zhí)行的。

比如有如下順序執(zhí)行的代碼:

func main() {
	var a = 10
	println(a)
	var b = (a+a)*a
	println(b)
}

我們嘗試用Go匯編的思維改寫上述函數(shù)。因?yàn)閄86指令中一般只有2個(gè)操作數(shù),因此在用匯編改寫時(shí)要求出現(xiàn)的變量表達(dá)式中最多只能有一個(gè)運(yùn)算符。同時(shí)對(duì)于一些函數(shù)調(diào)用,也需要改用匯編中可以調(diào)用的函數(shù)來改寫。

第一步改寫依然是使用Go語言,只不過是用匯編的思維改寫:

func main() {
	var a, b int
	a = 10
	runtime.printint(a)
	runtime.printnl()
	b = a
	b += b
	b *= a
	runtime.printint(b)
	runtime.printnl()
}

首先模仿C語言的處理方式在函數(shù)入口處聲明全部的局部變量。然后將根據(jù)MOV、ADD、MUL等指令的風(fēng)格,將之前的變量表達(dá)式展開為用=、+=*=幾種運(yùn)算表達(dá)的多個(gè)指令。最后用runtime包內(nèi)部的printint和printnl函數(shù)代替之前的println函數(shù)輸出結(jié)果。

經(jīng)過用匯編的思維改寫過后,上述的Go函數(shù)雖然看著繁瑣了一點(diǎn),但是還是比較容易理解的。下面我們進(jìn)一步嘗試將改寫后的函數(shù)繼續(xù)轉(zhuǎn)譯為匯編函數(shù):

TEXT ·main(SB), $24-0
    MOVQ $0, a-8*2(SP) // a = 0
    MOVQ $0, b-8*1(SP) // b = 0

    // 將新的值寫入a對(duì)應(yīng)內(nèi)存
    MOVQ $10, AX       // AX = 10
    MOVQ AX, a-8*2(SP) // a = AX

    // 以a為參數(shù)調(diào)用函數(shù)
    MOVQ AX, 0(SP)
    CALL runtime·printint
    CALL runtime·printnl

    // 函數(shù)調(diào)用后, AX/BX 可能被污染, 需要重新加載
    MOVQ a-8*2(SP), AX // AX = a
    MOVQ b-8*1(SP), BX // BX = b

    // 計(jì)算b值, 并寫入內(nèi)存
    MOVQ AX, BX        // BX = AX  // b = a
    ADDQ BX, BX        // BX += BX // b += a
    MULQ AX, BX        // BX *= AX // b *= a
    MOVQ BX, b-8*1(SP) // b = BX

    // 以b為參數(shù)調(diào)用函數(shù)
    MOVQ BX, 0(SP)
    CALL runtime·printint
    CALL runtime·printnl

    RET

匯編實(shí)現(xiàn)main函數(shù)的第一步是要計(jì)算函數(shù)棧幀的大小。因?yàn)楹瘮?shù)內(nèi)有a、b兩個(gè)int類型變量,同時(shí)調(diào)用的runtime·printint函數(shù)參數(shù)是一個(gè)int類型并且沒有返回值,因此main函數(shù)的棧幀是3個(gè)int類型組成的24個(gè)字節(jié)的棧內(nèi)存空間。

在函數(shù)的開始處先將變量初始化為0值,其中a-8*2(SP)對(duì)應(yīng)a變量、a-8*1(SP)對(duì)應(yīng)b變量(因?yàn)閍變量先定義,因此a變量的地址更?。?/p>

然后給a變量分配一個(gè)AX寄存器,并且通過AX寄存器將a變量對(duì)應(yīng)的內(nèi)存設(shè)置為10,AX也是10。為了輸出a變量,需要將AX寄存器的值放到0(SP)位置,這個(gè)位置的變量將在調(diào)用runtime·printint函數(shù)時(shí)作為它的參數(shù)被打印。因?yàn)槲覀冎耙呀?jīng)將AX的值保存到a變量內(nèi)存中了,因此在調(diào)用函數(shù)前并不需要在進(jìn)行寄存器的備份工作。

在調(diào)用函數(shù)返回之后,全部的寄存器將被視為被調(diào)用的函數(shù)修改,因此我們需要從a、b對(duì)應(yīng)的內(nèi)存中重新恢復(fù)寄存器AX和BX。然后參考上面Go語言中b變量的計(jì)算方式更新BX對(duì)應(yīng)的值,計(jì)算完成后同樣將BX的值寫入到b對(duì)應(yīng)的內(nèi)存。

最后以b變量作為參數(shù)再次調(diào)用runtime·printint函數(shù)進(jìn)行輸出工作。所有的寄存器同樣可能被污染,不過main馬上就返回不在需要使用AX、BX等寄存器,因此就不需要再次恢復(fù)寄存器的值了。

重新分析匯編改寫后的整個(gè)函數(shù)會(huì)發(fā)現(xiàn)里面很多的冗余代碼。我們并不需要a、b兩個(gè)臨時(shí)變量分配兩個(gè)內(nèi)存空間,而且也不需要在每個(gè)寄存器變化之后都要寫入內(nèi)存。下面是經(jīng)過優(yōu)化的匯編函數(shù):

TEXT ·main(SB), $16-0
    // var temp int

    // 將新的值寫入a對(duì)應(yīng)內(nèi)存
    MOVQ $10, AX        // AX = 10
    MOVQ AX, temp-8(SP) // temp = AX

    // 以a為參數(shù)調(diào)用函數(shù)
    CALL runtime·printint
    CALL runtime·printnl

    // 函數(shù)調(diào)用后, AX 可能被污染, 需要重新加載
    MOVQ temp-8*1(SP), AX // AX = temp

    // 計(jì)算b值, 不需要寫入內(nèi)存
    MOVQ AX, BX        // BX = AX  // b = a
    ADDQ BX, BX        // BX += BX // b += a
    MULQ AX, BX        // BX *= AX // b *= a

    // ...

首先是將main函數(shù)的棧幀大小從24字節(jié)減少到16字節(jié)。唯一需要保存的是a變量的值,因此在調(diào)用runtime·printint函數(shù)輸出時(shí)全部的寄存器都可能被污染,我們無法通過寄存器備份a變量的值,只有在棧內(nèi)存中的值才是安全的。然后在BX寄存器并不需要保存到內(nèi)存。其它部分的代碼基本保持不變。

if/goto跳轉(zhuǎn)

早期的Go雖然提供了goto語句,但是并不推薦在編程中使用。有一個(gè)和cgo類似的原則:如果可以不使用goto語句,那么就不要使用goto語句。Go語言中的goto語句是有嚴(yán)格限制的:它無法跨越代碼塊,并且在被跨越的代碼中不能含有變量定義的語句。雖然Go語言不喜歡goto,但是goto確實(shí)每個(gè)匯編語言碼農(nóng)的最愛。goto近似等價(jià)于匯編語言中的無條件跳轉(zhuǎn)指令JMP,配合if條件goto就組成了有條件跳轉(zhuǎn)指令,而有條件跳轉(zhuǎn)指令正是構(gòu)建整個(gè)匯編代碼控制流的基石。

為了便于理解,我們用Go語言構(gòu)造一個(gè)模擬三元表達(dá)式的If函數(shù):

func If(ok bool, a, b int) int {
	if ok { return a } else { return b }
}

比如求兩個(gè)數(shù)最大值的三元表達(dá)式(a>b)?a:b用If函數(shù)可以這樣表達(dá):If(a>b, a, b)。因?yàn)檎Z言的限制,用來模擬三元表達(dá)式的If函數(shù)不支持范型(可以將a、b和返回類型改為空接口,使用會(huì)繁瑣一些)。

這個(gè)函數(shù)雖然看似只有簡單的一行,但是包含了if分支語句。在改用匯編實(shí)現(xiàn)前,我們還是先用匯編的思維來重寫If函數(shù)。在改寫時(shí)同樣要遵循每個(gè)表達(dá)式只能有一個(gè)運(yùn)算符的限制,同時(shí)if語句的條件部分必須只有一個(gè)比較符號(hào)組成,if語句的body部分只能是一個(gè)goto語句。

用匯編思維改寫后的If函數(shù)實(shí)現(xiàn)如下:

func If(ok int, a, b int) int {
	if ok == 0 { goto L }
	return a
L:
	return b
}

因?yàn)閰R編語言中沒有bool類型,我們改用int類型代替bool類型(真實(shí)的匯編是用byte表示bool類型,可以通過MOVBQZX指令加載byte類型的值)。當(dāng)ok參數(shù)非0時(shí)返回變量a,否則返回變量b。我們將ok的邏輯反轉(zhuǎn)下:當(dāng)ok參數(shù)為0時(shí),表示返回b,否則返回變量a。在if語句中,當(dāng)ok參數(shù)為0時(shí)goto到L標(biāo)號(hào)指定的語句,也就是返回變量b。如果if條件不滿足,也就是ok非0,執(zhí)行后面的語句返回變量a。

上述函數(shù)的實(shí)現(xiàn)已經(jīng)非常接近匯編語言,下面是改為匯編實(shí)現(xiàn)的代碼:

TEXT ·If(SB), NOSPLIT, $0-32
    MOVQ ok+8*0(FP), CX // ok
    MOVQ a+8*1(FP), AX  // a
    MOVQ b+8*2(FP), BX  // b

    CMPQ CX, $0         // test ok
    JZ   L              // if ok == 0, skip 2 line
    MOVQ AX, ret+24(FP) // return a
    RET

L:
    MOVQ BX, ret+24(FP) // return b
    RET

首先是將三個(gè)參數(shù)加載到寄存器中,ok參數(shù)對(duì)應(yīng)CX寄存器,a、b分別對(duì)應(yīng)AX、BX寄存器。然后使用CMPQ比較指令將CX寄存器和常數(shù)0進(jìn)行比較。如果比較的結(jié)果為0,那么下一條JZ為0時(shí)跳轉(zhuǎn)指令將跳轉(zhuǎn)到L標(biāo)號(hào)對(duì)應(yīng)的指令,也就是返回變量b的值。如果比較的結(jié)果不為0,那么JZ指令講沒有效果,繼續(xù)執(zhí)行后的指令,也就是返回變量a的值。

在跳轉(zhuǎn)指令中,跳轉(zhuǎn)的目標(biāo)一般是通過一個(gè)標(biāo)號(hào)表示。不過在有些通過宏實(shí)現(xiàn)的函數(shù)中,更希望通過相對(duì)位置跳轉(zhuǎn),這時(shí)候可以通過PC寄存器來計(jì)算跳轉(zhuǎn)的位置。

for循環(huán)

Go語言的for循環(huán)有多種用法,我們這里只選擇最經(jīng)典的for結(jié)構(gòu)來討論。經(jīng)典的for循環(huán)由初始化、結(jié)束條件、迭代步長三個(gè)部分組成,再配合循環(huán)體內(nèi)部的if條件語言,這種for結(jié)構(gòu)可以模擬其它各種循環(huán)類型。

基于經(jīng)典的for循環(huán)結(jié)構(gòu),我們定一個(gè)LoopAdd函數(shù),可以用于計(jì)算任意等差數(shù)列的和:

func LoopAdd(cnt, v0, step int) int {
	result := v0
	for i := 0; i < cnt; i++ {
		result += step
	}
	return result
}

比如1+2+...+100可以這樣計(jì)算LoopAdd(100, 1, 1),10+8+...+0可以這樣計(jì)算LoopAdd(5, 10, -2)。現(xiàn)在采用前面if/goto類似的技術(shù)來改造for循環(huán)。

新的LoopAdd函數(shù)只有if/goto語句構(gòu)成:

func LoopAdd(cnt, v0, step int) int {
	var i = 0
	var result = 0
LOOP_BEGIN:
	result = v0
LOOP_IF:
	if i < cnt { goto LOOP_BODY }
	goto LOOP_END
LOOP_BODY
	i = i+1
	result = result + step
	goto LOOP_IF
LOOP_END:
	return result
}

函數(shù)的開頭先定義兩個(gè)局部變量便于后續(xù)代碼使用。然后將for語句的初始化、結(jié)束條件、迭代步長三個(gè)部分拆分為三個(gè)代碼段,分別用LOOP_BEGIN、LOOP_IF、LOOP_BODY三個(gè)標(biāo)號(hào)表示。其中LOOP_BEGIN循環(huán)初始化部分只會(huì)執(zhí)行一次,因此該標(biāo)號(hào)并不會(huì)被引用,可以省略。最后LOOP_END語句表示for循環(huán)的結(jié)束。四個(gè)標(biāo)號(hào)分隔出的三個(gè)代碼段分別對(duì)應(yīng)for循環(huán)的初始化語句、循環(huán)條件和循環(huán)體,其中迭代語句被合并到循環(huán)體中了。

下面用匯編語言重新實(shí)現(xiàn)LoopAdd函數(shù)

// func LoopAdd(cnt, v0, step int) int
TEXT ·LoopAdd(SB), NOSPLIT, $0-32
	MOVQ cnt+0(FP), AX   // cnt
	MOVQ v0+8(FP), BX    // v0/result
	MOVQ step+16(FP), CX // step
LOOP_BEGIN:
	MOVQ $0, DX          // i
LOOP_IF:
	CMPQ DX, AX          // compare i, cnt
	JL   LOOP_BODY       // if i < cnt: goto LOOP_BODY
	goto LOOP_END
LOOP_BODY:
	ADDQ $1, DX          // i++
	ADDQ CX, BX          // result += step
	goto LOOP_IF
LOOP_END:
	MOVQ BX, ret+24(FP)  // return result
	RET

其中v0和result變量復(fù)用了一個(gè)BX寄存器。在LOOP_BEGIN標(biāo)號(hào)對(duì)應(yīng)的指令部分,用MOVQ將DX寄存器初始化為0,DX對(duì)應(yīng)變量i,循環(huán)的迭代變量。在LOOP_IF標(biāo)號(hào)對(duì)應(yīng)的指令部分,使用CMPQ指令比較AX和AX,如果循環(huán)沒有結(jié)束則跳轉(zhuǎn)到LOOP_BODY部分,否則跳轉(zhuǎn)到LOOP_END部分結(jié)束循環(huán)。在LOOP_BODY部分,更新迭代變量并且執(zhí)行循環(huán)體中的累加語句,然后直接跳轉(zhuǎn)到LOOP_IF部分進(jìn)入下一輪循環(huán)條件判斷。LOOP_END標(biāo)號(hào)之后就是返回返回累加結(jié)果到語句。

循環(huán)是最復(fù)雜的控制流,循環(huán)中隱含了分支和跳轉(zhuǎn)語句。掌握了循環(huán)基本也就掌握了匯編語言到寫法。掌握規(guī)律之后,其實(shí)匯編語言編程會(huì)變得異常簡單。

到此這篇關(guān)于Golang匯編之控制流深入分析講解的文章就介紹到這了,更多相關(guān)Go語言匯編之控制流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語言range關(guān)鍵字循環(huán)時(shí)的坑

    Go語言range關(guān)鍵字循環(huán)時(shí)的坑

    今天小編就為大家分享一篇關(guān)于Go語言range關(guān)鍵字循環(huán)時(shí)的坑,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-03-03
  • Go語言標(biāo)準(zhǔn)庫sync.Once使用場(chǎng)景及性能優(yōu)化詳解

    Go語言標(biāo)準(zhǔn)庫sync.Once使用場(chǎng)景及性能優(yōu)化詳解

    這篇文章主要為大家介紹了Go語言標(biāo)準(zhǔn)庫sync.Once使用場(chǎng)景及性能優(yōu)化詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • golang int 轉(zhuǎn)float 強(qiáng)轉(zhuǎn)和高精度轉(zhuǎn)操作

    golang int 轉(zhuǎn)float 強(qiáng)轉(zhuǎn)和高精度轉(zhuǎn)操作

    這篇文章主要介紹了golang int 轉(zhuǎn)float 強(qiáng)轉(zhuǎn)和高精度轉(zhuǎn)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • go中string、int、float相互轉(zhuǎn)換方式

    go中string、int、float相互轉(zhuǎn)換方式

    這篇文章主要介紹了go中string、int、float相互轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • golang接口實(shí)現(xiàn)調(diào)用修改(值接收者指針接收者)場(chǎng)景詳解

    golang接口實(shí)現(xiàn)調(diào)用修改(值接收者指針接收者)場(chǎng)景詳解

    這篇文章主要為大家介紹了golang接口實(shí)現(xiàn)調(diào)用修改值接收者指針接收者示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • golang批量執(zhí)行任務(wù)的通用模板分享

    golang批量執(zhí)行任務(wù)的通用模板分享

    這篇文章主要為大家詳細(xì)介紹了golang實(shí)現(xiàn)批量執(zhí)行任務(wù)的通用模板,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下
    2023-11-11
  • Golang?基礎(chǔ)面試題集錦

    Golang?基礎(chǔ)面試題集錦

    這篇文章主要為大家介紹了Golang?基礎(chǔ)面試題集錦,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Golang驗(yàn)證器之validator是使用詳解

    Golang驗(yàn)證器之validator是使用詳解

    Validator是一個(gè)?Golang?的第三方庫,用于對(duì)數(shù)據(jù)進(jìn)行校驗(yàn),常用于?API?的開發(fā)中,對(duì)客戶端發(fā)出的請(qǐng)求數(shù)據(jù)進(jìn)行嚴(yán)格校驗(yàn),防止惡意請(qǐng)求。本文通過示例詳細(xì)講解了Validator的使用,需要的可以參考一下
    2022-08-08
  • Golang 實(shí)現(xiàn)超大文件讀取的兩種方法

    Golang 實(shí)現(xiàn)超大文件讀取的兩種方法

    這篇文章主要介紹了Golang 實(shí)現(xiàn)超大文件讀取的兩種方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Golang獲取本地IP地址方法分享

    Golang獲取本地IP地址方法分享

    這篇文章主要給大家介紹了Golang 獲取本地 IP 地址方法,文中有詳細(xì)的代碼示例,對(duì)我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-07-07

最新評(píng)論

嘉义市| 五峰| 陕西省| 阳朔县| 安宁市| 玉环县| 林西县| 恭城| 凤冈县| 赤城县| 鄢陵县| 青冈县| 榆中县| 固镇县| 平乡县| 临朐县| 伊宁市| 株洲市| 阜平县| 当阳市| 高密市| 东海县| 长沙县| 双牌县| 建阳市| 平武县| 达日县| 芦山县| 两当县| 宜兰县| 达日县| 治县。| 宁河县| 邢台县| 仪征市| 盖州市| 江西省| 陇川县| 巴彦县| 昌乐县| 揭西县|