lua堆棧
來源 https://blog.csdn.net/suhuaiqiang_janlay/article/details/56702381
來源 https://blog.csdn.net/suhuaiqiang_janlay/article/details/63683036
一、Lua腳本語言
1. 概述
Lua是一種腳本編程語言,與一般腳本語言不同,被稱為是嵌入式的腳本語言。Lua最著名的應用是在暴雪公司的網絡游戲魔獸世界中。
Lua語言可以獨立進行編程,但這不是其主要的使用方式。Lua最典型的用法,是作為一個庫,嵌入到其他大型語言(稱為宿主語言)的應用程序之中,為應用程序提供參數配置或邏輯描述等功能,帶來前所未有的靈活性。
Lua常見的宿主語言有:C/C++、Java、.NET,甚至腳本語言如PHP、Ruby。
2. Lua與相似解決方案的比較
Lua體積很小,往往使用靜態鏈接嵌入到程序內部,在發布應用時不需要附帶任何的運行時支持。
3. 宿主語言中嵌入Lua的工作流程
(1)宿主語言建立Lua解釋器對象
(2)將宿主語言實現的Lua擴展,如函數等,注冊到Lua解釋器中,供其使用。
(3)讀入Lua源程序或預先編譯好的Lua程序。
(4)執行讀入的Lua程序。
二、Lua虛擬機的初始化
Lua工作的核心是Lua虛擬機,宿主語言在加載和執行Lua腳本時,做的第一件事情就是創建並初始化Lua虛擬機。
1. 創建Lua虛擬機
lua_State *lua_newstate(lua_Alloc f, void *ud) API可以為我們創建一個新的獨立的Lua虛擬機。
參數指定了虛擬機中的內存分配策略,例如我們已經在自己的代碼中實現了內存池,這時候只需要寫一個符合lua_Alloc原型的適配器,然后指定為Lua的內存分配器就可以了,使得內存分配更加靈活。當然,如果不想自定義內存分配策略,也可以使用luaL_newstate,這樣Lua會幫你定義默認的內存分配策略。
我們可以先來看一下luaL_newstate的源碼:
// luaconf.h /* @@ LUA_EXTRASPACE defines the size of a raw memory area associated with ** a Lua state with very fast access. ** CHANGE it if you need a different size. */ #define LUA_EXTRASPACE (sizeof(void *)) // lstate.c /* ** thread state + extra space */ typedef struct LX { lu_byte extra_[LUA_EXTRASPACE]; lua_State l; } LX; /* ** Main thread combines a thread state and the global state */ typedef struct LG { LX l; global_State g; } LG; // lstate.c LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; lua_State *L; global_State *g; LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); if (l == NULL) return NULL; L = &l->l.l; g = &l->g; ...... return L; }
可見,通過luaL_newstate 創建Lua虛擬機時,第一塊申請的內存將用來存儲global_State(全局狀態機)和lua_State(主線程)實例。為了避免內存碎片的產生,同時減少內存分配和釋放的次數,Lua采用了一個小技巧:利用一個LG結構,把分配lua_State和global_State的行為關聯在一起。這個LG結構是在C文件內部定義,而不存在公開的H文件中,僅供該C代碼文件使用,因此這種依賴數據結構內存布局的用法負作用不大。
2. 關於global_State和lua_State
在一個獨立的Lua虛擬機中,global_State是一個全局的結構, 而lua_State可以有多個。
global_State
global_State結構,我們可以稱之為Lua全局狀態機。從Lua的使用者角度來看,global_State結構是完全感知不到的:我們無法用Lua公開的API獲取到它的指針、句柄或引用,而且實際上我們也並不需要引用到它。但是對於Lua的實現來說,global_State是十分重要的部分。
它管理着Lua中全局唯一的信息,主要是以下功能:
(1)內存分配策略及其參數
在調用lua_newstate的時候配置它們. 也可以通過lua_getallocf和lua_setallocf隨時獲取和修改它
(2)字符串的hashtable
全局的字符串哈希表,即保存那些短字符串,使得整個虛擬機中短字符串只有一份實例。具體參見 Lua字符串處理
(3)垃圾回收相關的信息,內存使用統計量
(4)panic, 當無保護調用發生時, 會調用該函數, 默認是null, 可以通過lua_atpanic配置.(用於異常處理)
(5)注冊表, 注冊表是一個全局唯一的table
(6)記錄lua中元方法名稱 和 基本類型的元表
[注意, lua中table和userdata每個實例可以擁有自己的獨特的元表--記錄在table和userdata的mt字段, 其他類型是每個類型共享一個元表--就是記錄在這里].
(7)upvalue鏈表
(8)主lua_State, 一個lua虛擬機中, 可以有多個lua_State, lua_newstate會創建出一個lua_State(稱為主線程), 並邦定到global_state的主lua_State上
lua_State
線程,這里線程的概念區別於操作系統的線程,實際上也是Lua中定義的一種狀態機。lua_State主要是管理一個lua虛擬機的執行環境, 一個lua虛擬機可以有多個執行環境。
(1)要注意的是, 和nil, string, table一樣,lua_State也是lua中的一種基本類型, lua中的表示是TValue {value = lua_State, tt = LUA_TTHREAD}
(2)lua_State的成員和功能
a. 棧的管理, 包括管理整個棧和當前函數使用的棧的情況
b. CallInfo的管理, 包括管理整個CallInfo數組和當前函數的CallInfo
c. hook相關的, 包括hookmask, hookcount, hook函數等
d. 全局表l_gt, 注意這個變量的命名, 很好的表現了它其實只是在本lua_State范圍內是全局唯一的的, 和注冊表不同, 注冊表是lua虛擬機范圍內是全局唯一的
e. gc的一些管理和當前棧中upvalue的管理
f. 錯誤處理的支持
(3)從lua_State的成員可以看出來, lua_State最主要的功能就是函數調用以及和c的通信.
3. lua_newstate函數的流程
(1)新建一個global_state和一個lua_State
(2)初始化, 包括給g_s創建注冊表, g_s中各個類型的元表的默認值全部置為0
(3)給l_s創建全局表, 預分配l_s的CallInfo和stack空間
(4)其中涉及到了內存分配統統使用lua_newstate傳進來的內存分配器分配
1:lua_push* 壓棧API
lua_push*這些API是把C語言里面的值封裝成Lua類型的值壓入棧中的,對於那些需要垃圾回收的元素,在壓入棧時,都會在Lua(也就是Lua虛擬機中)生成一個副本。比如lua_pushstring(lua_State *L, const char *s)會向中棧壓入由s指向的以'\0'結尾的字符串,在C中調用這個函數后,我們可以任意釋放或修改由s指向的字符串,也不會出現問題,原因就是在執行lua_pushstring過程中Lua會生成一個內部副本。實質上,Lua不會持有指向外部字符串的指針,也不會持有指向任何其他外部對象的指針(除了C函數,因為C函數總是靜態的)。
總之,一旦C中值被壓入棧中,Lua就會生成相應的結構(實質就是Lua中實現的相應數據類型)並管理(比如自動垃圾回收)這個值,從此不會再依賴於原來的C值。
2:lua棧大小
lua 只保證在從 Lua 進入 C 的邊界上提供額外的 LUA_MINSTACK 個 slot 。這個值默認為 20 ,一般是夠用的。正因為一般夠用,反而容易被編寫 C 擴展的同學忽視。尤其是在 C 擴展的代碼里有 C 層次上的遞歸時,非常容易在邊界情況下棧溢出。因為 Lua 的 stack 實際上又經常留出超過 LUA_MINSTACK 的空間,這種 bug 不易察覺。記住:如果你在 C 擴展中做復雜的事情,一定要記得在使用 lua stack 前,用 luaL_checkstack 留夠你需要的空間。
OK,這篇文章主要是借lua_newstate講述global_State和lua_State的結構與作用,希望對大家了解Lua工作環境有一點幫助。
下一篇將講述Lua棧相關的內容,更新中。。。
參考文獻:
http://www.cnblogs.com/ringofthec/archive/2010/11/09/lua_State.html
http://blog.csdn.net/maximuszhou/article/details/46277695
一、Lua棧
1. 什么是lua棧
lua的棧類似於以下的定義, 它是在創建lua_State的時候創建的: TValue stack[max_stack_len] // 欲知內情可以查 lstate.c 的stack_init函數
存入棧的數據類型包括數值, 字符串, 指針, talbe, 閉包等, 下面是一個棧的例子:
2. TValue結構
壓入的類型有數值, 字符串, 表和閉包[在c中看來是不同類型的值], 但是最后都是統一用TValue這種數據結構來保存的:), 下面用圖簡單的說明一下這種數據結構:
p -- 可以存一個指針, 實際上是lua中的light userdata結構
n -- 所有的數值存在這里, 不過是int , 還是float
b -- Boolean值存在這里, 注意, lua_pushinteger不是存在這里, 而是存在n中, b只存布爾
gc -- 其他諸如table, thread, closure, string需要內存管理垃圾回收的類型都存在這里
gc是一個指針, 它可以指向的類型由聯合體GCObject定義, 從圖中可以看出, 有string, userdata, closure, table, proto, upvalue, thread
從上面的圖可以的得出如下結論:
1. lua中, number, boolean, nil, light userdata四種類型的值是直接存在棧上元素里的, 和垃圾回收無關.
2. lua中, string, table, closure, userdata, thread存在棧上元素里的只是指針, 他們都會在生命周期結束后被垃圾回收.
lua value 和 c value的對應關系
c lua
nil 無 {value=0, tt = t_nil}
boolean int 非0, 0 {value=非0/0, tt = t_boolean}
number int/float等 1.5 {value=1.5, tt = t_number}
lightuserdata void*, int*, 各種* point {value=point, tt = t_lightuserdata}
string char str[] {value=gco, tt = t_string} gco=TString obj
table 無 {value=gco, tt = t_table} gco=Table obj
userdata 無 {value=gco, tt = t_udata} gco=Udata obj
closure 無 {value=gco, tt = t_function} gco=Closure obj
二、通過Lua棧實現和C++的通訊
1. Lua和C通訊的約定
lua和c通信時有這樣的約定: 所有的lua中的值由lua來管理, c++中產生的值lua不知道, 類似表達了這樣一種意思: "如果你(c/c++)想要什么, 你告訴我(lua), 我來產生, 然后放到棧上, 你只能通過api來操作這個值, 我只管我的世界", 這個很重要, 因為:
"如果你想要什么, 你告訴我, 我來產生"就可以保證, 凡是lua中的變量, lua要負責這些變量的生命周期和垃圾回收, 所以, 必須由lua來創建這些值(在創建時就加入了生命周期管理要用到的簿記信息)
"然后放到棧上, 你只能通過api來操作這個值", lua api給c提供了一套完備的操作界面, 這個就相當於約定的通信協議, 如果lua客戶使用這個操作界面, 那么lua本身不會出現任何"意料之外"的錯誤.
"我只管我的世界"這句話體現了lua和c/c++作為兩個不同系統的分界, c/c++中的值, lua是不知道的, lua只負責它的世界。
2. 棧的索引規則
棧底到棧頂索引呈+1遞增的規律,同時索引有正數索引和負數索引兩種表示方式:
1. 正數索引,不需要知道棧的大小,我們就能知道棧底在哪,棧底的索引永遠是1
即:棧底是1,然后一直到棧頂逐漸+1
2. 負數索引,不需要知道棧的大小,我們就能知道棧頂在哪,棧頂的索引永遠是-1
即:棧頂是-1,然后一直到棧底逐漸-1
3. Lua和C++通訊實例
假設在一個lua文件中有如下定義:
-- hello.lua 文件 myName = "beauty girl"
想要在C++中獲取到myName的值,可以lua_getglobal 來獲取:
/* 取得table變量,在棧頂 */ lua_getglobal(pL, "myName ");
lua_getglobal的處理過程如下:(請注意紅色數字,代表通信順序)
1) C++想獲取Lua的myName字符串的值,所以它把myName放到Lua堆棧(棧頂),以便Lua能看到
2) Lua從堆棧(棧頂)中獲取myName,此時棧頂再次變為空
3) Lua拿着這個myName去Lua全局表查找myName對應的字符串
4) 全局表返回一個字符串”beauty girl”
5) Lua把取得的“beauty girl”字符串放到堆棧(棧頂)
6) C++可以從Lua堆棧中取得“beauty girl”
現在,我們給helloLua.lua文件添加一個table全局變量:
-- helloLua.lua文件 myName = "beauty girl" helloTable = {name = "mutou", IQ = 125}
我們看到,多了一個helloTable的變量,它和數組十分相似,又和HashMap有點類似,總之它很強大。
獲取helloTable變量的方式和以前是一樣的:
/* 取得table變量,在棧頂 */ lua_getglobal(pL, "helloTable");
這樣,helloTable變量就被存放到棧頂。
可我們並不是要取table變量,因為C++中是無法識別Lua的table類型的,所以我們要取得table中具體的值,也就是name和IQ的值。
有一個和lua_getglobal類似的函數,叫做lua_gettable,顧名思義,它是用來取得table相關的數據的。
lua_gettable函數會從棧頂取得一個值,然后根據這個值去table中尋找對應的值,最后把找到的值放到棧頂。
lua_pushstring()函數可以把C++中的字符串存放到Lua的棧里;
然后再用lua_gettable()取執行前面所說的步驟,lua_gettable的第二個參數是指定的table變量在棧中的索引。
為了方便理解,我們畫個圖來表示:
這是初始狀態,堆棧里還沒有任何東西,那么,現在要先把helloTable變量放到棧頂:
/* 取得table變量,在棧頂 */ lua_getglobal(pL, "helloTable");
然后就變成了這樣:
接着,我們要取得table的name對應的值,那么,先要做的就是把”name”字符串入棧:
/* 將C++的字符串放到Lua的棧中,此時,棧頂變為“name”, helloTable對象變為棧底 */ lua_pushstring(pL, "name");
然后變成這樣:
注意了,我把棧的索引也加上了,因為我們即將要使用,這次我們用負數索引。
由於”name”的入棧,現在helloTable變量已經不在棧頂了。
接着,我們調用要做最重要的一步了,取得name在table中對應的值:
/* 從table對象尋找“name”對應的值(table對象現在在索引為-2的棧中,也就是當前的棧底), 取得對應值之后,將值放回棧頂 */ lua_gettable(pL, -2);
此時,棧變成這樣:
lua_gettable倒底做了什么事情?
首先,我們來解釋一下lua_gettable的第二個參數,-2是什么意思,-2就是剛剛helloTable變量在棧中的索引。
然后,Lua會去取得棧頂的值(之前的棧頂是”name”),然后拿着這個值去helloTable變量中尋找對應的值,當然就找到”mutou”了。
最后,Lua會把找到的值入棧,於是”mutou”就到了棧頂了。
最后,簡單寫了個Lua和C++相互調用的實例,代碼地址:Lua和C++交互示例代碼
============= End