通過(guò)字節(jié)碼理解lua的for循環(huán)

SH疫情隔離在家渣刷,打算把lua和unity熱更新相關(guān)的問(wèn)題再升入學(xué)習(xí)一下鹦肿。本篇是這個(gè)系列的第一篇。
一直以來(lái)筆者都抱有這樣一個(gè)觀念:lua是個(gè)好語(yǔ)言但被濫用了辅柴,一門(mén)膠水語(yǔ)言在當(dāng)前的游戲開(kāi)發(fā)中承擔(dān)了它不該承擔(dān)的任務(wù)箩溃。作為一種弱類(lèi)型語(yǔ)言,并且沒(méi)有建立足夠成熟的生態(tài)(包括編輯器碌嘀、第三方庫(kù))的情況下涣旨,被作為手游客戶(hù)端開(kāi)發(fā)的主語(yǔ)言,動(dòng)輒幾萬(wàn)行的業(yè)務(wù)邏輯股冗。這對(duì)項(xiàng)目和開(kāi)發(fā)者來(lái)說(shuō)都不是一個(gè)好的選擇霹陡,只能說(shuō)是向ios這個(gè)傲慢的平臺(tái)的一種妥協(xié)。
如果可以選擇的話(huà),我只想說(shuō):lua??都不寫(xiě)烹棉!
先看一段lua代碼

local i = 0  
for i = 1,5 do  
    i = 5  
    print(i)  
end  
print("after loop")  
print(i) 

這里我們關(guān)注兩個(gè)問(wèn)題:

1.for循環(huán)執(zhí)行了多少次攒霹?
2.afterloop之后,打印出來(lái)的i的值是多少浆洗?
loop.png

第二個(gè)問(wèn)題比較好理解催束,由于在for循環(huán)內(nèi)部出現(xiàn)了同名的控制變量,所以外部i實(shí)際上在循環(huán)內(nèi)部沒(méi)有作用域伏社,因此并沒(méi)有被賦值抠刺。
但第一個(gè)問(wèn)題,為什么循環(huán)依然執(zhí)行了5次摘昌,而不是在i=5這次賦值之后進(jìn)入for循環(huán)的下一次判斷就結(jié)束呢矫付?
要解釋這個(gè)問(wèn)題,我們就需要探究一下lua代碼底層到底是怎么執(zhí)行的了第焰。

lua程序的運(yùn)行方式:

1.由lua解釋器將lua代碼翻譯成指令序列

2.由lua虛擬機(jī)執(zhí)行指令序列

(lua解釋器和lua虛擬機(jī)均由純粹的C語(yǔ)言實(shí)現(xiàn),要理解更完整的實(shí)現(xiàn)細(xì)節(jié)可以參考lua源代碼http://www.lua.org/download.html

每條lua指令由 操作碼+操作數(shù) 組成

一條指令使用一個(gè)32bit的無(wú)符號(hào)整數(shù)表示妨马,其中低6位表示操作碼挺举,操作碼定義在lopcodes.h中

如何查看lua代碼對(duì)應(yīng)的指令序列

方法一,直接通過(guò)luac輸出指令序列(這種方法沒(méi)有對(duì)指令的額外解釋?zhuān)瑢?duì)新手來(lái)說(shuō)不太友好)
命令行:luac -l test.lua
方法二烘跺,通過(guò)ChunkSpy.lua輔助解析
命令行:lua ChunkSpy.lua --source test.lua
(ChunkSpy.lua的獲取地址https://github.com/viruscamp/luadec/tree/master/ChunkSpy

通過(guò)方法二我們可以得到上面那段代碼對(duì)應(yīng)的lua指令序列

1.    local i = 0  
2.003B  01000000           [01] loadk     0   0        ; R0 := K0(=0)  
3.    for i = 1,5 do  
4.003F  41400000           [02] loadk     1   1        ; R1 := K1(=1)  
5.0043  81800000           [03] loadk     2   2        ; R2 := K2(=5)  
6.0047  C1400000           [04] loadk     3   1        ; R3 := K1(=1)  
7.004B  68C00080           [05] forprep   1   4        ; R1 -= R3; pc+=4 (goto [10])  
8.        i = 5  
9.004F  01810000           [06] loadk     4   2        ; R4 := K2(=5)  
10.        print(i)  
11.0053  46C14000           [07] gettabup  5   0   259  ; R5 := U0(=_ENV)[K3(="print")]  
12.0057  80010002           [08] move      6   4        ; R6 := R4  
13.005B  64410001           [09] call      5   2   1    ;  := R5(R6)  
14.    end  
15.005F  6780FE7F           [10] forloop   1   -5       ; R1 += R3; if R1 <= R2 then { R4 := R1; pc+=-5 (goto [6]) }  
16.    print("after loop")  
17.0063  46C04000           [11] gettabup  1   0   259  ; R1 := U0(=_ENV)[K3(="print")]  
18.0067  81000100           [12] loadk     2   4        ; R2 := K4(="after loop")  
19.006B  64400001           [13] call      1   2   1    ;  := R1(R2)  
20.    print(i)  
21.006F  46C04000           [14] gettabup  1   0   259  ; R1 := U0(=_ENV)[K3(="print")]  
22.0073  80000000           [15] move      2   0        ; R2 := R0  
23.0077  64400001           [16] call      1   2   1    ;  := R1(R2)  
24.007B  26008000           [17] return    0   1        ; return  

重點(diǎn)關(guān)注這幾行:
4-7行
9行
15行

我們解釋一下其中幾個(gè)關(guān)鍵的命令

loadk A B   --R(A) := K(B)
加載常量操作碼湘纵,將B所指的常量加載到A所指的寄存器中

move A B    --R(A) := R(B)
賦值操作碼,將寄存器B中的值拷貝到寄存器A中滤淳。 

forprep A B --R(A) -= R(A+2); PC += B
初始化數(shù)字for循環(huán)

forloop A B --R(A) += R(A+2); if R(A) <= R(A+1) then { R(A+3) := R(A); PC + =-B}
執(zhí)行數(shù)字for循環(huán)的一次迭代

我們看一下《lua虛擬機(jī)指令簡(jiǎn)明手冊(cè)》中關(guān)于for循環(huán)指令的說(shuō)明:
數(shù)字for循環(huán)要求棧上的4個(gè)寄存器梧喷,每個(gè)寄存器都必須是數(shù)值。R(A)持有初始值并作為內(nèi)部循環(huán)變量(內(nèi)部索引)脖咐;R(A+1)是界限铺敌;R(A+2)是步進(jìn)值;R(A+3)是局部于 for 塊的實(shí)際循環(huán)變量(外部索引)
我們注意這里屁擅,for循環(huán)用于循環(huán)移步和條件判斷的始終是R(A)寄存器中的值(對(duì)應(yīng)上面那段代碼中的R1)偿凭,而for循環(huán)內(nèi)部執(zhí)行段中使用的變量則是寄存器R(A+3)中的值(對(duì)應(yīng)R4),并且每次重新進(jìn)入循環(huán)體之前派歌,R4還會(huì)重新被賦值為R1中的值(if R1 <= R2 then { R4 := R1} )
并且《lua虛擬機(jī)指令簡(jiǎn)明手冊(cè)》特別強(qiáng)調(diào)了lua的for循環(huán)實(shí)現(xiàn)是和傳統(tǒng)的測(cè)試+跳轉(zhuǎn)的循環(huán)方式是不同的弯囊。

那么我們?cè)賮?lái)看一下所謂的“傳統(tǒng)的測(cè)試和跳轉(zhuǎn)”是怎樣的執(zhí)行過(guò)程。以大家最熟悉的C語(yǔ)言為例:

int main()  
{  
    for (int i = 0; i < 10; i++)  
    {  
        i = 10;  
    }  
  
    return 0;  
}

我們查看對(duì)應(yīng)的匯編代碼:

1.    for (int i = 0; i < 10; i++)  
2.007C17C8  mov         dword ptr [ebp-8],0    
3.007C17CF  jmp         main+3Ah (07C17DAh)    
4.007C17D1  mov         eax,dword ptr [ebp-8]    
5.007C17D4  add         eax,1    
6.007C17D7  mov         dword ptr [ebp-8],eax    
7.007C17DA  cmp         dword ptr [ebp-8],0Ah    
8.007C17DE  jge         main+49h (07C17E9h)    
9.    {  
10.        i = 10;  
11.007C17E0  mov         dword ptr [ebp-8],0Ah    
12.    }  
007C17E7  jmp         main+31h (07C17D1h)

注意到這里所有關(guān)于控制變量i的操作都對(duì)應(yīng)于dword ptr [ebp-8]
這就是傳統(tǒng)的力量胶果!
看到這里匾嘱,我真是要diss一下lua的for循環(huán)設(shè)計(jì),整這些花里胡哨的特性有啥用霸缈佟霎烙?你不會(huì)比C語(yǔ)言之父還懂編程吧?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市吼过,隨后出現(xiàn)的幾起案子锐秦,更是在濱河造成了極大的恐慌,老刑警劉巖盗忱,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酱床,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡趟佃,警方通過(guò)查閱死者的電腦和手機(jī)扇谣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)闲昭,“玉大人罐寨,你說(shuō)我怎么就攤上這事⌒蚓兀” “怎么了鸯绿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)簸淀。 經(jīng)常有香客問(wèn)我瓶蝴,道長(zhǎng),這世上最難降的妖魔是什么租幕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任舷手,我火速辦了婚禮,結(jié)果婚禮上劲绪,老公的妹妹穿的比我還像新娘男窟。我一直安慰自己,他們只是感情好贾富,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布歉眷。 她就那樣靜靜地躺著,像睡著了一般祷安。 火紅的嫁衣襯著肌膚如雪姥芥。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天汇鞭,我揣著相機(jī)與錄音凉唐,去河邊找鬼。 笑死霍骄,一個(gè)胖子當(dāng)著我的面吹牛台囱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播读整,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼簿训,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起强品,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤膘侮,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后的榛,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體琼了,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年夫晌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了雕薪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晓淀,死狀恐怖所袁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凶掰,我是刑警寧澤燥爷,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站懦窘,受9級(jí)特大地震影響局劲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奶赠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望药有。 院中可真熱鬧毅戈,春花似錦、人聲如沸愤惰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)宦言。三九已至扇单,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間奠旺,已是汗流浹背蜘澜。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留响疚,地道東北人鄙信。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像忿晕,于是被迫代替她去往敵國(guó)和親装诡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容