lua_newuserdata簡單使用 (http://blog.csdn.net/slionls/article/details/23358795)
userdata (http://www.shouce.ren/api/lua/5/_157.htm)
How free up memory allocated by lua_newuserdata with delete operator? (http://stackoverflow.com/questions/21792657/how-free-up-memory-allocated-by-lua-newuserdata-with-delete-operator)
========================================================================================
原網址:Lua中的userdata (http://www.jellythink.com/archives/587)
輕量級userdata
怎麼又有了一個輕量級userdata了?這貨又是什麼?專業點,叫做“light userdata”。我在前面總結的userdata叫做“full userdata”。
輕量級userdata是一種表示C指針的值(即void *)。由於它是一個值,所以不用創建它。要將一個輕量級userdata放入棧中,只需要調用lua_pushlightuserdata即可。
voidlua_pushlightuserdata(lua_State*L,void*p); |
儘管兩種userdata在名稱上差不多,但它們之間還是存在很大不同的。輕量級userdata不是緩衝,只是一個指針而已。它也沒有元表,就像數字一樣,輕量級userdata不受到垃圾收集器的管理。
輕量級userdata的真正用途是相等性判斷。一個完全userdata是一個對象,它只與自身相等。而一個輕量級userdata則表示了一個C指針的值。因此,它與所有表示同一個指針的輕量級userdata相等。可以將輕量級userdata用於查找Lua中的C對象。
現在就來說一種輕量級userdata的使用,還記的我在《再說C模塊的編寫(2)》中總結的註冊表麼?談及註冊表的key的時候,說使用UUID是一種不錯的方案,現在就使用輕量級的userdata結合static來實現無衝突的key。
// 壓入輕量級userdata,一個static變量的地址 lua_pushlightuserdata(L,(void*)&key); lua_pushstring(L,"JellyThink"); lua_settable(L,LUA_REGISTRYINDEX); |
由於靜態變量的地址在一個進程中具有唯一性,所以絕對不會出現重複key的問題。
// 從註冊表中取對應的值 lua_pushlightuserdata(L,(void*)&key); lua_gettable(L,LUA_REGISTRYINDEX); |
對於輕量級的userdata,我想我的總結不會到此結束的;但是,在這裏也不會做更多的總結的,在以後,遇到了light userdata時,我再根據項目實例進行總結。而今天這篇博文就到此結束了。下一篇再見。
========================================================================================
0、Lua中使用userdata類型來表示在C中定義的類型。userdata只是提供了一塊原始的內存區域,可以用來存儲任何東西,並且,在lua中userdata沒有任何預定義的操作。在C中調用函數lua_newuserdata會根據指定的大小分配一塊內存,並將相應的userdata壓入棧中,最後返回這個內存塊的地址:void *lua_newuserdata(lua_State *L,size_t size)。
lua cclosure 的 upvalue 數量限制
最近寫的代碼中出了一個奇怪的 bug ,很難調試出來。經過一個晚上的掙扎,終於發現了問題。
第一個問題,在 C 函數中,不能隨意的時候 lua_State 中的虛擬機堆棧,如果需要大量使用堆棧,應該先調用 lua_checkstack 。少量使用堆棧,(在 LUA_MINSTACK 20 )之下時則沒有問題。這個問題其實在文檔裏有寫,我看過忘記了 :( 不過我個人還是覺得 lua_checkstack 的語義有點奇怪,從字面上看,這個 api 不應該有副作用。它能增加可用堆棧的大小違背了 checkstack 的詞義。
第二個問題,當從 lua 調用 C 函數時,當參數數量不足的時候,並不會填入 nil 作爲缺省參數。比如,寫了一個 C 函數,接受兩個參數。當 lua 中調用這個 C 函數時,如果僅傳入一個參數,那麼在 C 中 stack 上 index 2 位置的值並不一定是 nil 。這個時候我們應該用 lua_gettop 得到準確的參數個數以做適當的處理,或者直接在進入 C 函數時調用一次 lua_settop(L,2) 強制堆棧擴展到兩個。
第三個問題,就是一開始最爲迷惑我的問題。在生成 cclosure 的時候,upvalue 不能超過 255 個。而這一點並沒在文檔中說明,運行時壓入超過 255 個 upvalue 也不會報錯。知道仔細查看源碼才發現其中的祕密。
按 lua 的設計,upvalue 的個數應該只受內存大小的限制。但是我的程序在生成一個擁有 258 個 upvalue 的 cclosure 時,upvalue 的個數變成了奇怪的 2 個。我的直覺告訴我,2 == 258%256 。查看源碼證實了我的想法,cfunction 的 upvalue 的數量用了一個 byte 記錄。究其原因,在於 lua 的 object 用了好幾個標記量方便虛擬機運作(比如 gc 用到的 mark 位),當 upvalue 的數量也用一個 byte 的時候,剛好四個 byte 湊齊一個 32bit 字。
爲什麼我會用到如此之多的 upvalue ,這裏就牽扯到最近發現的一個給 lua 寫 C 擴展的技巧。
一般,C 對象保存在 lua 中都採用 userdata 的形式。userdata 會參與 gc 的過程,所以不必考慮內存釋放的問題。但是,往往 C 對象會對 lua 中其它對象做使用上的引用,而 userdata 本身不具備這個能力。以前我的做法是,使用 lua_ref 得到一個整數引用,放在 userdata 中,到 userdata 的 __gc 被觸發或者用戶主動釋放時,作 lua_unref 操作。這樣做不太美觀,也有一點點效率問題。
最近發現,簡單的 C 擴展,我們完全可以用 C closure 實現,把擴展數據放在 upvalue 中。引用的 lua 對象可以直接放進 upvalue ,而其他 C 數據,可以再生成一個 userdata 寫入 upvalue 。這個 closure 可以根據調用時的輸入參數決定對它內部的數據做何種操作。
用這個技術,我實現了一個簡單的循環隊列,它會比用 lua table 實現的隊列稍微高效一些。使用時,可以調用 C 擴展函數創建一個隊列對象(其實是一個 c closure),然後用這個 closure 做進隊列和出隊列的操作。當傳入參數的時候,就把參數進隊列,不傳參數的時候就把隊首的元素出隊列。同時可以根據返回值知道隊列時候爲空和爲滿。
========================================================================================
關鍵函數:
void *lua_newuserdata (lua_State *L, size_t size);
新建full userdata。
(1)分配一塊指定大小的內存;
(2)將該full userdata壓棧;
(3)返回該內存塊的地址給主機程序,主機程序能夠隨意使用這塊內存。
void luaL_argcheck (lua_State *L,int cond,int arg,const char *extramsg);
檢查條件是否滿足。
void luaL_newmetatable (lua_State *L, const char *tname);
創建userdata可用的metatable。
如果registry已經有tnme鍵值,則函數返回0;
否則,創建一個[tname, metatable],並放入registry,並返回1。
兩種情況下,都會講tname對應的值入棧。
堆棧+1
void *luaL_checkudata (lua_State *L, int index, const char *tname);
檢查在棧中指定位置的對象是否爲帶有給定名字的metatable(registry中鍵tname對應的值)的usertata。是則返回userdata地址,否則返回NULL。
void luaL_getmetatable (lua_State *L, const char *tname);
獲取registry中的tname對應的metatable,併入棧。注意區分lua_getmetatable函數。
void luaL_setmetatable (lua_State *L, const char *tname);
將棧頂對象的metatable設置爲registry表中鍵tname對應的值。注意區分lua_setmetatable函數。
int lua_getmetatable (lua_State *L, int index);
獲取index對應的table的metatable,併入棧。如果該table沒有metatable,則返回0,且堆棧不變。
void lua_setmetatable (lua_State *L, int index);
將棧頂的table出棧並設置給index處的值作爲metatable。
堆棧-1
(二)利用metatable標識userdata來增加代碼的安全性
上面的C庫是有缺陷的,比如我們怎麼確保例子中setarray的第一個參數就是我們想要的數組userdata,而不是別的不相關的userdata呢?userdata是一種lua類型,它可以用來表示宿主語言中的各種自定義類型對象,爲了區分特定類型,我們使用的方法是:
我們單獨爲該數組創建一個metatable,每次創建數組userdata時,我們設置其和metatable的關聯。每次我們訪問數組的時候,都檢查一下其是否有一個正確的metatable即可。也就是利用不同的metatable來標記不同類型的userdata。因爲Lua代碼不能夠改變userdata的metatable,所以Lua不會僞造我們的代碼。
(五)light userdata
light userdata不同於full userdata,它有如下特點:
(1)full userdata代表Lua中的C對象,light userdata代表一個C指針的值(也就是一個void *類型的值)。由於它是一個值,我們不能創建他們(同樣的,我們也不能創建一個數字)。
(2)僅僅是一個指針,像數字一樣,沒有metatables,light userdata不需要垃圾收集器來管理她。
(3)可以用於表示不同類型的對象,我們在Lua中使用light userdata表示C對象。
因爲它是一個值,任何指向同一個C地址的light userdata都相等。
void lua_pushlightuserdata (lua_State *L, void *p);
將一個light userdata入棧。
(六)userdata相關的資源釋放
Lua以__gc元方法的方式提供了finalizers。這個元方法只對userdata類型的值有效。當一個userdata將被收集的時候,並且userdata有一個__gc域,Lua會調用這個域的值(應該是一個函數):以userdata作爲這個函數的參數調用。這個函數負責釋放與userdata相關的所有資源,比如說文件描述符、窗口句柄等。