lua userdata 彙總

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)。

     1、實質在C中定義lua的userdata,與定義C模塊完全類似,只不過通常這時需要通過調用lua_newuserdata來告訴lua分配一塊額外的內存,而在內存所有相關的操作都是在C中的定義的,實質就是C模塊中的接口。注意這塊分配的額外內存是由Lua垃圾收集器來管理的,無須關心起釋放等情況。
     2、在實現一個Lua的程序庫或userdate,必須保證該庫或userdata的接口不應破壞C數據或在Lua中導致core dump。
     3、可以爲每種userdata創建一個唯一的元表,來辨別不同類型的userdata,每當創建了一個userdata後,就用相應的元表來標記它,而每得到一個userdata後,就檢查它是否擁有正確的元表,注意Lua代碼中不能改變userdata的元表(當能增加已有元表的屬性,比如對元表key爲__index賦值)。通常是將這個元表存儲在註冊表中,也類型名作爲key,元表爲value。輔助庫提供了一些函數來實現這些:
      int luaL_newmetatable(lua_State*L, const char *tname);
      void luaL_getmetatable(lua_State *L,const char *tnaem);
      void *luaL_checkudata(lua_State*L,int index,const char *tname);
     4、輕量級userdata是一種表示C指針的值(即void*),要將一個輕量級userdata放入棧中,只需要調用lua_pushlightuserdata即可。輕量級userdata只是一個指針而已。它沒有元表,就像數字一樣,輕量級userdata無須受垃圾收集器的管理。
     5、Lua在釋放完全userdata所關聯的內存時,若發現userdata對應的元表還有__gc元方法,則會調用這個方法,並以userdata自身作爲參數傳入。利用該特性,可以再回收userdata的同時,釋放與此userdata相關聯的資源。
 
========================================================================================

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相關的所有資源,比如說文件描述符、窗口句柄等。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章