逆向分析Lua語言特性的設計與實現(一) 閉包

2021-06-07 22:05:58 字數 3341 閱讀 3882

一、lua閉包

若將乙個函式寫在另外乙個函式內部,那麼這個位於內部的函式便可以訪問外部函式中的區域性變數,這個特性稱之為closure,中文翻譯為「閉包」。

二、實踐分析閉包的實現

(一)、例項1:lua指令逆向分析

function newcounter()

local i = 0

return function()

i = i+1

return i

endendc1= newcounter()

print(c1())

print(c1())

chunkspry互動模式反編譯:

>function newcounter() local i = 0 return function() i = i+1; return i end end

; source chunk: (interactive mode)

; x86 standard (32-bit, little endian, doubles)

; function [0] definition (level 1)

; 0 upvalues, 0 params, 2 stacks

.function 0 0 2 2

.const "newcounter" ; 0

; function [0] definition (level 2)

; 0 upvalues, 0 params, 2 stacks

.function 0 0 0 2

.local "i" ; 0

.const 0 ; 0

; function [0] definition (level 3) //匿名函式:closure

; 1 upvalues, 0 params, 2 stacks

.function 1 0 0 2

.upvalue "i" ; 0

.const 1 ; 0

[1] getupval 0 0 ; i //r(0):=upvalue(0)

[2] add 0 0 256 ; 1 //i=i+1

[3] setupval 0 0 ; i //upvalue(0):=r(0),將暫存器0中的值寫回upvalue(0)

[4] getupval 0 0 ; i //再次取出upvalue(0),為解下來的返回i做準備

[5] return 0 2

[6] return 0 1

; end of function

[1] loadk 0 0 ; 0

[2] closure 1 0 ; 1 upvalues

[3] move 0 0

[4] return 1 2

[5] return 0 1

; end of function

[1] closure 0 0 ; 0 upvalues

[2] setglobal 0 0 ; newcounter

[3] return 0 1

; end of function

>

從上述lua位元組碼可以看出,區域性變數在閉包函式中是通過upvalue傳遞的。

在匿名函式中,第一條指令[1]操作是從該函式的upvalue列表中,0號索引位置上得到區域性變數i,並拷貝在第0號暫存器中。

在執行期間,upvalue是由closure設定並由虛擬機器維護的。

在pascal中,外層作用域中的變數是通過遍歷棧幀找到的。然而,lua函式是第一類值,可以被賦值給變數在別處引用。lua虛擬機器的解決辦法是通過

getupval   和setupval   提供訪問upvalue的乾淨介面,不過upvalue的管理是虛擬機器自己處理的。

事實上,lua編譯乙個函式時,會為他生成乙個原型(prototype),其中包含了函式體對應的虛擬機器指令、函式用到的常量值(數,文字字串等等)和一些除錯資訊。

在執行時,每當lua執行乙個形如function...end 這樣的表示式時,他就會建立乙個新的資料物件,其中包含了相應函式原型的引用、環境(environment,用來查詢全域性變數的表)的引用及乙個由所有upvalue引用組成的陣列,而這個資料物件就稱為

閉包

。由此可見,函式是編譯期概念,是靜態的,而閉包是執行期概念,是動態的。

upvalue實際是區域性變數,而區域性變數是儲存在函式堆疊框架上(stack frame)的,所以只要upvalue還沒有離開自己的作用域,他就一直生存在函式堆疊上。這種情況下,閉包將通過指向堆疊上的upvalue的引用來訪問他們,一旦upvalue即將離開自己的作用域(這也意味著他馬上要從堆疊中消失),閉包就會為他分配空間並儲存當前的值,以後便可通過指向新分配空間的引用來訪問該upvalue。

(二)、例項2:

function func1(n)

local function func2()

print(n)

endn = n + 1000

return func2

endg1=func1(1)

g1() //列印出1還是1001?

func1將返回內部定義的乙個函式

g1=func1(1),使g1得到了第一類值的函式

g1()這條語句實質上執行了一次這個函式,那麼print(n)這條語句列印的英國是1呢還是1001呢?為什麼呢?

通過執行程式,我們發現奇怪的問題:

內嵌函式定義在n = n + 1000這條語句之前,可為什麼g1()列印出的卻是10001?

upvalue實際是區域性變數,而區域性變數是儲存在函式堆疊框架上(stack frame)的,所以只要upvalue還沒有離開自己的作用域,他就一直生存在函式堆疊上。這種情況下,閉包將通過指向堆疊上的upvalue的引用來訪問他們,一旦upvalue即將離開自己的作用域(這也意味著他馬上要從堆疊中消失),閉包就會為他分配空間並儲存當前的值,以後便可通過指向新分配空間的引用來訪問該upvalue。當執行到g1(),既func1(1)的n = n + 1000時,閉包已建立了,不過n並沒有離開作用域,所以閉包仍然引用堆疊上的n,當return func2完成時,n即將結束生命,此時閉包便將n(已是1001了)複製到自己管理的空間中以便將來訪問。弄清晰了內部的秘密後,執行結果就不難解釋了。

三、閉包的設計

lua源**閉包部分欣賞

未完待續...

C 語言特性中的效能分析

這是一本關於c 效能優化的書,比較不錯,叫 c 應用程式效能優化 第2版 c 語言特性中的效能分析 大多數開發人員通常都有這個觀點,即組合語言和c語言適合用來編寫對效能要求非常高的程式。而c 語言的主要應用範圍是編寫複雜度非常高,但是對 效能要求不是那麼嚴格的程式。因為在大多數人看來,c 語言相對前...

Lua中的表 讀《Lua設計與實現》筆記

1.lua語言用表來表示一切資料結構。2.lua表分為陣列和雜湊表部分。陣列部分索引從1開始。雜湊表部分可以儲存任何不能儲存在陣列部分的資料,唯一的要求是鍵值不能為nil lobject.h typedef struct table node value型別定義 typedef struct lua...

Lua 語言中的點 冒號與self

lua程式設計中,經常遇到函式的定義和呼叫,有時候用點號呼叫,有時候用冒號呼叫,這裡簡單的說明一下原理。girl function girl.gotomarket girl somemoney girl.money girl.money somemoney endgirl.gotomarket gi...