日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

lua 调用文件中的函数调用_深入Lua:调用相关的指令

發布時間:2023/12/10 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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:调用相关的指令的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。