從Go匯編角度解讀for循環(huán)的問題
Go常用的遍歷方式有兩種:for和for-range。實際上,for-range也只是for的語法糖,本文試圖從匯編代碼入手解釋for循環(huán)是如何工作的。
問題
首先來看看幾個令人迷惑的地方。
問題1:遍歷過程中取值
func main() { arr := [5]int{1, 2, 3, 4, 5} for _, v := range arr { println(&v) } }
上面這段代碼里,會打印出什么?
問題2:遍歷過程中修改
arr := []int{1, 2, 3, 4, 5} for v := range arr { arr = append(arr, v) }
上面這段代碼里,遍歷前后arr有哪些變化?
窺探虛實
對于問題1,我們期待會打印出5個不同的地址,實際上最終打印出來的都是同一個地址,我們可以猜測v在循環(huán)過程中只聲明了一次??纯磫栴}1的匯編代碼:
0x0028 00040 (main.go:4) MOVQ ""..stmp_0(SB), AX 0x002f 00047 (main.go:4) MOVQ AX, "".arr+24(SP) 0x0034 00052 (main.go:4) MOVUPS ""..stmp_0+8(SB), X0 0x003b 00059 (main.go:4) MOVUPS X0, "".arr+32(SP) 0x0040 00064 (main.go:4) MOVUPS ""..stmp_0+24(SB), X0 0x0047 00071 (main.go:4) MOVUPS X0, "".arr+48(SP) 0x004c 00076 (main.go:5) MOVQ "".arr+24(SP), AX 0x0051 00081 (main.go:5) MOVQ AX, ""..autotmp_2+64(SP) 0x0056 00086 (main.go:5) MOVUPS "".arr+32(SP), X0 0x005b 00091 (main.go:5) MOVUPS X0, ""..autotmp_2+72(SP) 0x0060 00096 (main.go:5) MOVUPS "".arr+48(SP), X0 0x0065 00101 (main.go:5) MOVUPS X0, ""..autotmp_2+88(SP) 0x006a 00106 (main.go:5) XORL AX, AX 0x006c 00108 (main.go:5) JMP 162 0x006e 00110 (main.go:5) MOVQ AX, ""..autotmp_7+16(SP) 0x0073 00115 (main.go:5) MOVQ ""..autotmp_2+64(SP)(AX*8), CX 0x0078 00120 (main.go:5) MOVQ CX, "".v+8(SP) 0x007d 00125 (main.go:6) CALL runtime.printlock(SB) 0x0082 00130 (main.go:6) LEAQ "".v+8(SP), AX 0x0087 00135 (main.go:6) MOVQ AX, (SP) 0x008b 00139 (main.go:6) CALL runtime.printpointer(SB) 0x0090 00144 (main.go:6) CALL runtime.printnl(SB) 0x0095 00149 (main.go:6) CALL runtime.printunlock(SB) 0x009a 00154 (main.go:5) MOVQ ""..autotmp_7+16(SP), AX 0x009f 00159 (main.go:5) INCQ AX 0x00a2 00162 (main.go:5) CMPQ AX, $5 0x00a6 00166 (main.go:5) JLT 110
00040行:MOVQ ""..stmp_0(SB), AX將stmp_0變量里的內(nèi)容放到AX寄存器里,stmp_0實際上就是arr數(shù)組,在生成的匯編代碼里:
""..stmp_0 SRODATA size=40 0x0000 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 0x0010 03 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 0x0020 05 00 00 00 00 00 00 00
由此可以看到stmp_0正是arr數(shù)組。
00106行:XORL AX AX是初始化AX寄存器,AX寄存器里包含當(dāng)前循環(huán)位置。 00108行:JMP 162表示跳轉(zhuǎn)到00162行。 00162行:CMPQ AX $5比較寄存器AX和5,偽代碼:i < 5,如果滿足條件,則跳轉(zhuǎn)到00110行。
00110行00159行為循環(huán)體代碼,注意到00159行INCQ AX, 意即AX寄存器值自增,到這里我們可以大致分析出來for-range在匯編層面的偽代碼:
for i := 0; i < 5; i++ { }
這也就驗證了上面說的for-range只是普通for的語法糖。
00110到00120行是循環(huán)體代碼的前半部分。從Go 匯編文檔上看:SP寄存器指向當(dāng)前棧幀的局部變量的開始位置,也就是說局部變量放在了SP寄存器的棧幀里。
00115行:MOVQ ""..autotmp_2+64(SP)(AX*8), CX,autotmp_*是為臨時變量自動生成的名字,這行匯編做的事情是將某個v值(注意,是值)放在CX寄存器里。
00120行:MOVQ CX, "".v+8(SP)將CX寄存器里的內(nèi)容放在SP寄存器指向的位置,00125行代碼是一個隔斷,00125之后的代碼與println有關(guān)。重點在這行代碼,每次循環(huán)都會將值放在"".v+8(SP)這個位置,在這個循環(huán)體代碼里,我們并沒有看到其他的臨時變量聲明,到這里,我們可以總結(jié)出:"".v+8(SP)這個位置就是變量v在棧幀中的位置,由于位置一直沒有發(fā)生變化,在進行&v操作時取到的會是同一個地址。
對于問題1,根據(jù)匯編代碼的分析,我們得出結(jié)論:v在循環(huán)過程中只會聲明一次,每次循環(huán)只是將v值替換,并未重新聲明臨時變量,這樣解釋了問題1代碼的輸出結(jié)果。
再回到問題2,我們期待循環(huán)永遠(yuǎn)不會停下來,但實際上循環(huán)5次之后停了下來。我們有理由猜測:循環(huán)體中的arr與arr = append(arr, v)中的并非同一個。
由于兩段代碼的匯編代碼差不多,這里仍以上面的匯編代碼來分析。00106行是初始AX寄存器,也是循環(huán)的開始,所以我們關(guān)注00106行之前的代碼。
根據(jù)上面的分析,在00040行已經(jīng)將數(shù)組內(nèi)容放到了AX寄存器里,00081行到00101行,將數(shù)組拷貝到autotmp_2變量內(nèi),由SP所指向的棧頂。
在讀這段代碼的匯編時,發(fā)現(xiàn)編譯器針對數(shù)組內(nèi)容做了一個小優(yōu)化,當(dāng)數(shù)組長度小于5時候,編譯器會認(rèn)為這個數(shù)組只是臨時變量,會直接做棧上賦值,直接將數(shù)組內(nèi)容放到autotmp_2變量中(棧上),省略了從數(shù)據(jù)只讀區(qū)到AX的過程(即00040行),數(shù)組長度小于5時,匯編代碼如下:
0x0024 00036 (main.go:5) MOVQ $1, ""..autotmp_2+24(SP) 0x002d 00045 (main.go:5) MOVQ $2, ""..autotmp_2+32(SP) 0x0036 00054 (main.go:5) MOVQ $3, ""..autotmp_2+40(SP) 0x003f 00063 (main.go:5) XORL AX, AX
分析到這里,我們可以得到一段表示for循環(huán)的偽代碼:
temp := {1, 2, 3, 4, 5} for i := 0; i < 5; i++ { v := temp[i] }
由此我們可以得到結(jié)論:for-range時拷貝了被訪問的列表(array、slice、hashmap等)。問題2所帶的思考:當(dāng)數(shù)組比較大時,for-range拷貝數(shù)組的開銷也會比較大,在實際應(yīng)用中應(yīng)當(dāng)避免這個開銷。
總結(jié)
從上面的匯編代碼分析過來看,總結(jié)兩點:
1. 循環(huán)過程中位置變量,只會聲明一次,也就是說每次循環(huán)位置變量的地址都是相同的。 2. for-range時拷貝了被訪問的列表(array、slice、hashmap等)。
延申閱讀
A Quick Guide to Go's Assembler
到此這篇關(guān)于從Go匯編角度解讀for循環(huán)的文章就介紹到這了,更多相關(guān)匯編for循環(huán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ARM匯編判斷之如何用匯編判斷數(shù)組中正負(fù)數(shù)個數(shù)
這篇文章主要介紹了ARM匯編判斷之如何用匯編判斷數(shù)組中正負(fù)數(shù)個數(shù),在匯編語言中程序的基本框架是不變的,這里我們可以直接把正向遍歷的程序給貼過來,然后我們思考怎么運用判斷后綴把數(shù)組中正數(shù)和負(fù)數(shù)分開,進行判斷個數(shù)和分別求和,需要的朋友可以參考下2022-04-04UEFI開發(fā)實戰(zhàn)用戶交互界面使用說明VFR文件
這篇文章主要為大家介紹了UEFI開發(fā)實戰(zhàn)用戶交互界面使用說明VFR文件,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06匯編語言DOSBox及debug.exe在Windows64下環(huán)境搭建
這篇文章主要為大家介紹了匯編語言環(huán)境的搭建DOSBox及debug.exe在Windows64下安裝配置過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-11-11