Lua解析和變量作用域


近期研究了一下Lua語言在解析時的一些細節,如果在C程序中執行lua腳本的話, 那么變量的作用域是非常值得關注的,這里記錄一下在分析過程中得到的一些結論。(本文的描述針對的是lua-5.1.5這個版本的代碼)

 

考察下面的兩段代碼:

scope.lua

 1 b = 700             -- GT['b'] = 700
 2 local a = 9         -- 設置在棧上
 3 
 4 function p1()       -- GT['p1'] = Closure B
 5     m = 90          -- GT['m'] = 90
 6     local n = 8     -- 設置在棧上
 7     print(a)        -- 數據來自upval
 8     print(b)        -- 數據來自GT
 9 end
10 
11 function p2()       -- GT['p2'] = Closure C
12     print(m)        -- 數據來自GT
13     print(n)        -- nil
14 end

srv.c

 1 ...
 2 luaL_loadfile(L, "scope.lua");
 3 lua_pcall(L, 0, 0, 0);      // 執行閉包A的字節碼
 4 ...
 5 lua_getglobal(L, "p1");
 6 lua_pcall(L, 0, 0, 0);      // 執行閉包B的字節碼
 7 
 8 lua_getglobal(L, "p2");
 9 lua_pcall(L, 0, 0, 0);      // 執行閉包C的字節碼
10 ...

 

scope.lua腳本中會生成三個閉包A、B和C,其中閉包A是由scope.lua腳本加載(luaL_loadfile)之后生成的,luaL_loadfile最終會調用f_parse來解析腳本並生成閉包A,並且閉包A的環境table會指定為L的global table。

調用閉包A

接下來在srv.c中第3行將會執行閉包A對應的字節碼,操作包括:

  1. 將變量b設置在閉包A的環境Table中
  2. 將變量a設置在棧上
  3. 生成閉包B,賦值給變量p1,同時設置在閉包A的環境Table中,指定閉包B的環境Table就等於閉包A的環境Table。生成的閉包B存在一個upval,指向上一層的局部變量a。
  4. 生成閉包C,賦值給變量p2,同時設置在閉包A的環境Table中,同理閉包C的環境Table也等於閉包A的環境Table。

可以看到變量b、p1和p2都會保存在閉包A的環境Table中,也就是L的global table中。

調用閉包B

在srv.c的第5行執行之后,會將閉包B設置在棧頂,接下來調用lua_pcall便會執行閉包B對應的字節碼, 操作如下:

  1. 將變量m設置在閉包B的環境Table中
  2. 將變量n設置在棧上
  3. 從閉包B的upval中找到變量a的值並設置在棧上,調用print
  4. 從閉包B的環境Table中找到變量b的值並設置在棧上,調用print

前面講過,閉包B的環境Table和閉包A的環境Table是一致的並且都是L的global table,因此可以得到下面的輸出結果:

9
700

  

調用閉包C

同樣的,srv.c執行到第8行和第9行的時候會執行閉包C的字節碼,變量m是從閉包C的環境Table也就是L的global table中獲取,而變量n是閉包B的局部變量,沒有設置在環境Table中,也不存在於閉包C的upval中,因此結果會為空,得到的結果如下:

90
nil

 


 

總結

現在有很多用C語言實現的服務器程序都會嵌入Lua腳本來提高開發效率,並且通過在一個Lua虛擬機中創建多個Lua線程的手段來對每個請求的處理進行區分,因此在編寫Lua腳本的時候要很清楚每個變量的作用域范圍,否則可能會出現數據不一致的情況,某些變量可能是被一個Lua虛擬機中的所有Lua線程共享,而某些變量只會存在於一個Lua線程獨立的數據棧中。

此外,程序中很有可能還會調用一些API來更改Lua線程的global table或環境table,因此更需要特別關注。


 

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM