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的值是多少浆洗?
第二個(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ǔ)言之父還懂編程吧?