lua 调用文件中的函数调用_深入Lua:调用相关的指令
前言
這一節我們來深入解析與調用相關的指令,這些指令是:
- OP_CALL 調用
- OP_TAILCALL 尾調用
- OP_VARARG 可變參數
- OP_RETURN 返回
解析這些指令的過程中,最重要的是時刻跟蹤棧的變化情況。
簡單調用
- OP_CALL 的語法是:R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))。
- R(A)為要調用的函數本身
- 如果B=1,表示沒有參數,如果B>1,表示有B-1個參數,這些參數從寄存器R(A+1)開始。
- 函數調用完之后,如果C=1,表示沒有返回值,如果C>1,表示有C-1個返回值,這些返回值會存到寄存器R(A)和它后面。從這里可以看出,本來存函數的R(A)最后被替換為返回值。
- OP_RETURN 函數返回指令,語法是:return R(A), ... ,R(A+B-2)
- 如果B=1,表示沒有返回值,如果B>1,表示有B-1個返因值,這些返回值就存在寄存器R(A)和它后面。這和OP_CALL是相呼應的。
有了上面兩個指令,就可以進行函數調用,先看下面的Lua代碼:
1 2 local function add(a, b) 3 return a + b 4 end 5 6 local function div(a, b) 7 return a // b, a % b 8 end 9 10 local function main() 11 local s = add(10, 20) 12 local d, v = div(s, 8) 13 print(d, v) 14 end 15 16 main()通過luac查看上面幾個函數的操作碼,在每行操作碼的最后,我加上了棧的內容,用<>括起來,棧從函數對象開始,函數對象的后面為base, 操作碼中的數字大多相對于base,比如0表示base自己,1表示base+1。
下面是main函數:
1 [11] GETUPVAL 0 0 ; add -- <main|add> 2 [11] LOADK 1 -1 ; 10 -- <main|add|10> 3 [11] LOADK 2 -2 ; 20 -- <main|add|10|20> 4 [11] CALL 0 3 2 -- <main|30> 5 [12] GETUPVAL 1 1 ; div -- <main|30|div> 6 [12] MOVE 2 0 -- <main|30|div|30> 7 [12] LOADK 3 -3 ; 8 -- <main|30|div|30|8> 8 [12] CALL 1 3 3 -- <main|30|3|6> 9 [13] GETTABUP 3 2 -4 ; -- <main|30|3|6|print> 10 [13] MOVE 4 1 -- <main|30|3|6|print|3> 11 [13] MOVE 5 2 -- <main|30|3|6|print|3|6> 12 [13] CALL 3 3 1 -- <main|30|3|6> 13 [14] RETURN 0 1 -- <>下面是add函數:
1 [3] ADD 2 0 1 -- <add|10|20|30> 2 [3] RETURN 2 2 -- <30>下面是div函數:
1 [7] IDIV 2 0 1 -- <div|30|8|3> 2 [7] MOD 3 0 1 -- <div|30|8|3|6> 3 [7] RETURN 2 3 -- <3|6>一開始main函數的調用信息和棧是這樣的:
執行了add函數之后,調用信息和棧變成這樣:
add函數返回,再調用div之后,調用信息和棧變成這樣:
函數返回結果作為函數參數
把上面的Lua代碼修改一下,變成下面這樣:
local function add(a, b)return a + b endlocal function div(a, b)return a // b, a % b endlocal function main()local r = add(div(10, 4))local s = "sum=" .. rprint(s) endmain()變化之處是div函數的返回結果,直接作為add的參數。這一改變使得VM不知道add會得到多少參數,只能借助于div返回的棧頂。
OP_CALL的B和C有另一種情況,B=0時,參數從R(A+1)一直到棧頂;C=0時,返回值從R(A)一直到棧頂。借助這兩種情況就能實現上面的邏輯,main函數如下:
1 [11] GETUPVAL 0 0 ; add -- <main|add> 2 [11] GETUPVAL 1 1 ; div -- <main|add|div> 3 [11] LOADK 2 -1 ; 10 -- <main|add|div|10> 4 [11] LOADK 3 -2 ; 4 -- <main|add|div|10|4> 5 [11] CALL 1 3 0 -- <main|add|2|2> 6 [11] CALL 0 0 2 -- <main|4> 7 [12] LOADK 1 -3 ; -- <main|4|sum=> 8 [12] MOVE 2 0 -- <main|4|sum=|4> 9 [12] CONCAT 1 1 2 -- <main|4|sum=4> 10 [13] GETTABUP 2 2 -4 ; -- <main|4|sum=4|print> 11 [13] MOVE 3 1 -- <main|4|sum=4|print|sum=4> 12 [13] CALL 2 2 1 -- <main|4|sum=4> 13 [14] RETURN 0 1 -- <>第5行CALL 1 3 0,C=0,表示返回結果從R(1)一直到棧頂;第6行CALL 0 0 2,B=0,表示add的參數從R(1)一直棧頂。
從VM代碼看,調用div時,新的CallInfo的nresults等于-1,這表示函數的返回值為LUA_MULTRET;在div返回時,moveresults判斷如果CallInfo的nresults等于-1,就返回函數的實際返回值,并且將L->top調整到n個返回值之后。緊接著下一條指令是對add的調用,就能根據L->top得到實際的參數。
可變參數
可變參數的指令是OP_VARARG:
- OP_VARARG 語法是R(A), R(A+1), ..., R(A+B-1) = vararg,如果B>0,表示從可變參數接受B-1個參數,如果可變參數不滿B-1個,則后面自動填nil。如果B=0,則將傳進函數的所有可變參數賦值給R(A)...。
看下面代碼:
local function main(...)print(...)return ... endmain(10, "ok", false)main函數的操作碼如下:
1 [16] GETTABUP 0 0 -1 ; -- <main|10|ok|false|print> 2 [16] VARARG 1 0 -- <main|10|ok|false|print|10|ok|false> 3 [16] CALL 0 0 1 -- <main|10|ok|false> 4 [17] VARARG 0 0 -- <main|10|ok|false|10|ok|false> 5 [17] RETURN 0 0 -- <10|ok|false>這里要注意一點是main函數的棧base是從可變參數之后開始的,即false后面的寄存器為0。
第2行VARARG 1 0, B=0,表示將所有可變參數保存到R(1)和后面的寄存器,然后設置好L->top。
第3行調用print,B=0,所以參數就是從R(1)一直到L->top。
第4行返回可變參數,B=0,表示將所有可變參數保存到R(0)和后面的寄存器,然后設置好L->top
第5行函數返回,B=0,表示將R(0)到L->top作為返回值。
尾調用
尾調用使用OP_TAILCALL指令,它和OP_CALL的不同之處是,這個指令不會生成新的CallInfo,它會重用調用者的CallInfo,因為尾調用只能在最后一條返回語句產生,在那一刻調用者的CallInfo已經使用完畢,所以可以重用這個CallInfo。尾調用只能是Lua函數,具體可看lvm.c的OP_TAILCALL指令處理。
其他方面和OP_CALL的含義基本一致,下面是一個例子:
local function div(a, b)return a // b, a % b endlocal function calc(a, b)return div(a, b) endcalc(10, 3)calc里面的div調用就是一個尾調用,calc的指令如下:
1 [7] GETUPVAL 2 0 ; div -- <calc|10|3|div> 2 [7] MOVE 3 0 -- <calc|10|3|div|10> 3 [7] MOVE 4 1 -- <calc|10|3|div|10|3> 4 [7] TAILCALL 2 3 0 -- <calc|10|3|3|1> 5 [7] RETURN 2 0 -- <3|1>第4行的C=0,表示返回值為從R(2)一直到棧頂。第5行的B=0,表示從R(2)一直到棧頂作為返回值。結合上面的例子,能得到這種情況一般都是將上一個函數的返回值作為當前函數的返回值。
總結
以上是生活随笔為你收集整理的lua 调用文件中的函数调用_深入Lua:调用相关的指令的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python黑科技:在家远程遥控公司电脑
- 下一篇: [全程建模]rose工具如何打开两个md