C語言API總覽


    Lua是一種嵌入式語言,這就意味着Lua並不是一個獨立運行的應用,而是一個庫,它可以鏈接到其他應用程序,將Lua的功能融入這些應用。
    因爲能夠當作庫來擴展某個應用程序,所以Lua是一種嵌入式語言。同時,使用了Lua語言的程序也可以在Lua環境中註冊新的函數,比如用C語言實現函數,從而增加一些無法直接用Lua語言編寫的功能。因此Lua也是一種可擴展的語言。
    上述兩種對Lua語言的定位分別對應C語言和Lua語言之間的兩種交互形式。在第一種形式中,C語言擁有控制權,而Lua語言被用作庫,這種交互形式中的C代碼被稱爲應用代碼。在第二種形式中,Lua語言擁有控制權,而C語言被用作庫,此時的C代碼被稱爲庫代碼。應用代碼和庫代碼都適用相同的API與Lua語言通信,這些API被稱爲C API。
    C API是一個函數、常量和類型組成的集合,有了它,C語言代碼就能與Lua語言交互。C API包括讀寫Lua全局變量的函數、調用Lua函數的函數、運行Lua代碼段的函數,以及註冊C函數的函數等。通過調用CAPI,C代碼幾乎可以做Lua代碼能夠做的所有事情。
    CAPI遵循C語言的操作模式,與Lua的操作模式由很大的區別。在使用C語言編程時,我們必須注意類型檢查、錯誤恢復、內存分配錯誤和其他一些複雜的概念。CAPI中的大多數函數都不會檢查其參數的正確性,我們必須在調用函數前確保參數的合法性,一旦出錯,程序會直接崩潰而不會收到規範的錯誤信息。此外,CAPI強調的是靈活性和簡潔性,某些情況下會以犧牲易用性爲代價,即便是常見的需求,也可能需要調用好幾個API。這麼做雖然有些繁瑣,但我們卻可以完全控制所有細節。

第一個示例

    首先來學習一個簡單的應用程序的例子:一個獨立的解釋器。

一個簡單地額獨立解釋器

#include <stdio.h>
#include <string.h>
#include "lua.h"
#include "luaxlib.h"
#include "lualib.h"

int main(void){
	char buff[256];
	int error;
	lua_State *L = luaL_newstate();     /*打開Lua*/
	luaL_openlibs(L);				/*打開標準庫*/

	while (fgets(buff,sizeof(buff),stdin)!= NULL){
		error = luaL_loadstring(L,buff)||lua_pcall(L,0,0,0);
		if(error){
			fprintf(stderr,"%s\n",lua_tostring(L,-1));
			lua_pop(L,1);			/*從棧中彈出錯誤信息*/
		}
	}
	lua_close(L);
	return 0;
}

    頭文件lua.h聲明瞭Lua提供的基礎函數,其中包括創建新Lua環境的函數、調用Lua函數的函數、讀寫環境中的全局變量的函數,以及註冊供Lua語言調用新函數的函數等等。lua.h中聲明的所有內容都有一個前綴lua_。
    頭文件luaxlib.h聲明瞭輔助庫所提供的函數,其中所有的聲明均以luaL_開頭。輔助庫使用lua.h提供的基礎API來提供更高層次的抽象,特別是對標準庫用到的相關機制進行抽象。基礎API追求經濟性和正交性,而輔助庫則追求對常見任務的實用性。當然,要在程序中創建其他所需的抽象也是非常簡單的。請記住,輔助庫不能訪問Lua的內部元素,而只能通過lua.h中聲明的官方基礎API完成所有工作。輔助庫能實現什麼,你的程序就能實現什麼。
    Lua標準庫沒有定義任何C語言全局變量,它將其所有的狀態都保存在動態的結構體lua_State中,Lua中的所有函數都接收一個指向該結構的指針作爲參數。這種設計使得Lua是可重入的,並且可以直接用於編程多線程代碼。
    顧名思義,函數luaL_newstate用於創建一個新的Lua狀態。當它創建一個新狀態時,新環境中沒有包含預定義的函數,甚至連print也沒有。爲了保持Lua語言的精煉,所有的標準庫都被組織成不同的包,這樣我們在不需要使用某些包時可以忽略它們。頭文件lualib.h中聲明瞭用於打開這些庫的函數。函數luaL_openlibs用於打開所有的標準庫。
    當創建好一個狀態並且在其中加載標準庫以後,就可以處理用戶的輸入了。程序會首先調用函數luaL_loadstring來編譯用戶輸入的每一行內容。如果沒有錯誤,則返回零,並向棧中壓入編譯後得到的函數。然後,程序調用函數lua_pcall從棧中彈出編譯後的函數,並以保護模式運行。與函數lua_loadstring類似,如果沒有錯誤發生,函數lua_pcall則返回零;當發生錯誤時,這兩個函數都會向棧中壓入一條錯誤信息。隨後我們可以通過函數lua_tostring獲取錯誤信息,並在打印錯誤信息後使用函數lua_pop將從棧中刪除。
    在C語言中,真是的錯誤處理可能會相當複雜,並且如何處理錯誤取決於應用的性質。Lua核不會直接向任何輸出流寫入數據,它只會通過返回錯誤信息來提示錯誤。每個應用可以用其所需的最恰當的方式來處理這些錯誤信息。爲了簡化討論,假設一下示例使用如下簡單的錯誤處理函數,即打印一條錯誤信息,關閉Lua狀態並結束整個應用:

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
void error (lua_State *L,const char *fmt, ...){
	va_list argp;
	va_start(argp,fmt);
	vfprintf(stderr,fmt,argp);
	va_end(argp);
	lua_close(L);
	exit(EXIT_FAILURE);
}

    由於Lua既可以作爲C代碼來編譯,也可以作爲C++代碼來編譯,因此lua.h中並沒有包含以下這種在C標準庫中的常見的寫法:

#ifdef __cplusplus
extern "C"{
	#endif
	...
	#ifdef __cplusplus
}
#endif

如果將Lua作爲C代碼編譯出來後又要在C++中使用,那麼可以引入lua.hpp來替代lua.h,定義如下:

extern "C"{
	#include "lua.h"
}

    Lua和C之間通信的主要組件是無處不在的虛擬棧,幾乎所有的API調用都是在操作這個棧中的值,Lua與C之間所有的數據交換都是通過這個棧完成的。此外,還可以利用棧保存中間結果。
    當我們想在Lua和C之間交換數據時,會面對兩個問題:第一個問題是動態類型和靜態類型體系之間不匹配;第二個問題是自動內存管理和手動內存管理之間不匹配。
    在Lua中,如果我們寫t[k]=v,k和v都可以是幾種不同類型;由於元表的存在,甚至t也可以有不同的類型。然而,如果要在C語言中提供這種操作,任意給定的settable函數都必須有一個固定的類型。爲了實現這樣的操作,我們就需要好幾十個不同的函數。
    可以通過在C語言中聲明某種聯合體類型來解決這個問題,假設這種類型叫lua_Value,它能夠表示Lua語言中所有的值,然後,可以把settable聲明爲:

void lua_settable (lua_Value a,lua_Value k,lua_Value v);

這種方法有兩個缺點。首先,我們很難將如此複雜的類型映射到其他語言中;而在設計Lua時,我們又要求Lua語言不僅能方便地與C/C++交互,而且還能與JavaFortranC#等其他語言方便地交互。其次,Lua語言會做垃圾收集:由於Lua預壓引擎並不知道Lua中的一個表可能會被保存在一個C語言變量中,因此它可能會認爲這個表是垃圾並將其收回。
    因此,LuaAPI中滅有定義任何類似於lua_Value的類型,而是使用棧在Lua和C之間交換數據。棧中的每個元素都能保存Lua中任意類型的值。當我們想要從Lua中獲取一個值時,只需要調用Lua,Lua就會將指定的值壓入棧中。當想要將一個值傳給Lua時,首先要將這個值壓入棧,然後調用Lua將其中棧中彈出即可。儘管我們仍然需要一個不同的函數將每種C語言類型的值壓入棧,還需要另一個不同函數從棧中彈出每種C語言類型的值,但是避免了過多的組合。另外,由於這個棧是Lua狀態的一部分,因此垃圾收集器知道C語言正在使用哪些值。
    幾乎CAPI中的所有函數都會用到棧。正如第一個示例,函數luaL_loadstring將其結果留在棧中;函數lua_pcall從棧中取出要調用的函數,並且也會將錯誤消息留在棧中。
    Lua嚴格地按照LIFO的規則來操作棧。在調用Lua時只有棧頂部的部分會發生改變,而C語言代碼則有更大的自由度。更具體地說,C語言可以檢視棧中的任何一個元素,甚至可以在棧的任意位置插入或刪除元素。

壓入元素

    針對每一種能用C語言直接表示的Lua數據類型,CAPI中都有一個對應的亞棧函數:常量nil使用lua_pushnil;布爾值使用lua_pushboolean;雙精度浮點數使用lua_pushnumber;整型使用lua_pushinteger;任意字符串使用lua_pushlstring;以\0終止的字符串使用lua_pushstring。

void lua_pushnil         (lua_State *L);
void lua_pushboolean     (lua_State *L, int bool);
void lua_pushnumber		 (lua_State *L, lua_Number n);
void lua_pushinteger     (lua_State *L, lua_Integer n);
void lua_pushlstring     (lua_State *L, const char *s, size_t len);
void lua_pushstring      (lua_State *L, const char *s);

當然,也有向棧中壓入C函數和用戶數據的函數。
    類型Lua_Number相當於Lua語言的浮點數類型,默認爲double,但可以在編譯時配置Lua,讓Lua_Number爲float甚至long double。類型lua_Integer相當於Lua語言中的整型,通常被定義爲long long ,既有符號64位整型。同樣,要把Lua語言中的lua_Integer配置爲使用int或long也很容易。如果使用float-int組合,也就是32浮點數類型和整型,即我們所說的精簡Lua,對於資源受限的機器和硬件而言,相當高效。
    Lua語言中的額字符串不是以\0結尾的,它們可以包含任意二進制數據。因此,將字符串壓棧的基本函數lua_pushlstring需要一個明確的長度作爲參數。對於以\0結尾的字符串,也可以使用函數lua_pushstring,該函數通過strlen來計算字符串的長度。Lua語言不會保留指向外部字符串的指針。對於不得不保留的字符串,Lua要麼生成一個內部副本,要麼複用已有的字符串。因此,一旦上述函數返回,即使立即釋放或修改緩衝區也不會出現問題。
    無論何時向棧內壓入一個元素,我們都應該確保棧中有足夠的空間。請注意,現在你是一個C語言程序員,Lua語言不會寵着你。當Lua啓動時,以及Lua調用C語言時,棧中至少有20個空閒的位置。對於大多數情況,這個空間是完全夠用,所以我們一般無須考慮棧空間的問題。不過,有些任務可能會需要更多的棧空間,特備是循環向棧中壓入元素時。在這些情況下,就需要調用哈數lua_checkstack來檢查棧中是否有足夠的空間:

int lua_checkstack (lua_State *L, int sz);

這裏,sz是我們所需要的額外棧位置的數量。如果可能,函數lua_checkstack會增加棧的大小,以容納所需的額外空間;否則,該函數返回零。
    輔助庫也提供了一個高層函數來檢查棧空間:

void luaL_checkstack (lua_State *L, int sz, const char *msg);

該函數類似於函數lua_checkstack,但是如果棧空間不能滿足請求,該函數會使用指定的錯誤信息拋出異常,而不是返回錯誤碼。

查詢元素

    CAPI使用索引來引用棧中的元素。第一個被壓入棧的元素索引爲1,第二個被壓入的元素索引爲2,依次類推。我們還可以以棧頂爲參照,使用負數索引來訪問棧中的元素,此時,-1表示棧頂元素,-2表示在它之前被壓入棧的元素,依次類推。例如,調用lua_tostring(L,-1)會將棧頂的值作爲字符串返回。正如你接下來要看到的,有些情況下從棧底對棧進行索引更加自然,而有些情況下則使用負數索引更好。
    要檢查棧中的一個元素是否爲特定的類型,CAPI提供了一系列名爲lua_is的函數,其中可以是任意一種Lua數據類型。這些函數包括lua_isnil、lua_isnumber、lua_isstring和lua_istable等。所有這些函數都有同樣的原型:

int lua_is* (lua_State *L, int index);

實際上,函數lua_isnumber不會檢查某個值是否爲特定類型,而是檢查該值是否能被轉換爲特定類型。函數lua_isstring與之類似,特別之處在於,它接受數字。
    還有一個函數lua_type,用於返回棧中元素的類型,每一種類型都由一個對應的常量表示,包括LUA_INIT、LUA_TBOOLEAN、LUA_TUMBER、LUA_TSTRING等。還函數一般與switch語句連用。當需要檢查字符串和數值是否存在潛在的強制類型轉換時,該函數也同樣有用。
    函數lua_to*用於從棧中獲取一個值:

int 				lua_toboolean(lua_State *L, int index);
const char 			*lua_tolstring(lua_State *L, int index, size_t *len);
lua_State           *lua_tothread(lua_State *L, int index);
lua_Number          lua_tonumber(lua_State *L, int index);
lua_Integer         lua_tointeger(lua_State *L, int index);

    即使指定的元素的類型不正確,調用這些函數也不會有問題。函數lua_toboolean適用於所有類型,它可以按照如下的規則將任意Lua值轉換爲C的布爾值:nil和false轉換爲0,所有其他的Lua值轉換爲1.對於類型不正確的值,函數lua_tolstring和lua_tothread返回NULL。不過,數值相關的函數都無法提示數值的類型錯誤,因此只能簡單地返回0。以前我們需要調用函數lua_isnumber來檢查類型,但是Lua5.2引入瞭如下的新函數:

lua_Number      lua_tonumberx(lua_State *L, int idx, int *isnum)
lua_Integer     lua_tointegerx(lua_State *L,int idx, int *isnum)

出口參數isnum返回了一個布爾值,來表示Lua值是否被強制轉換爲期望的類型。
    函數lua_tolstring返回一個指向該字符串內部副本的指針,並將字符串的長度存入到參數len指定的位置。我們無法修改這個內部副本。Lua語言保證,只要對應的字符串還在棧中,那麼這個指針就是有效的。當Lua調用的一個C函數返回時,Lua就會清空棧。因此,作爲規則,永遠不要指向Lua字符串的指針存放到獲取該指針的函數之外。
    函數lua_tolstring返回的所有字符串在其末尾都會有一個額外的\0,不過這些字符串中也可能會有\0,因此可以通過第三個參數len獲取字符串的真實長度。特別的,假設棧頂的值是一個字符串,那麼如下推斷永遠成立:

size_t len;
const char *s = lua_tolstring(L, -1 ,&len); /*任意Lua字符串*/
assert(s[len] == '\0');
assert(strlen(s) <= len);

    如果不需要長度信息,可以在調用函數lua_tolstring時將第三個參數設爲NULL。不過,使用宏lua_tostring會更好,因此這個宏就是用NULL作爲第三個參數來調用函數lua_tolstring的。
    爲了掩飾這些函數的用法,示例提供了一個有用的輔助函數,它輸出整個棧的內容。

對棧進行Dump

static void stackDump(lua_State *L){
	int i;
	int top = lua_gettop(L); /*棧的深度*/
	for (i = 1; i<= top;i++){
		int t = lua_type(L,i);
		switch(t){
			case LUA_TSTRING:{
				printf("'%s'",lua_tostring(L,i));
				break;
			}
			case LUA_TBOOLEAN:{
				printf(lua_toboolean(L,i)?"true":"false");
				break;
			}
			case LUA_TUNMBER:{
				printf("%g",lua_tonumber(L,i));
				break;
			}
			default:{
				printf("%s",lua_typename(L,t));
				break;
			}
		}
		printf(" ");
	}
	printf("\n");
}

    這個函數從棧底向棧頂遍歷,並根據每個元素的類型打印其值。它打印字符串時會用單引號將其括起來,對數值類型的值則使用格式"%g"輸出,對於其他C語言中不存在等價類型的值則只打印出它們的類型。
    在Lua5.3中,由於整型總是可以被強制轉換爲浮點型,因此仍然可以用函數lua_tonumber和"%g"的格式打印所有的數值。但是,我們傾向於將整數打印爲整型,以避免損失精度。此時,我們可以用新函數lua_isinteger來區分整型和浮點型:

case LUA_TNUMBER:{
	if (lua_isinteger(L,i))
		printf("%lld",lua_tointeger(L,i));
	else
		printf("%g",lua_tonumber(L,i));
		break;
}

其他棧操作

    除了上述在C語言和棧之間交換數據的函數外CAPI還提供了下列用於通過棧操作的函數:

int lua_gettop			(lua_State *L);
void lua_settop         (lua_State *L, int index);
void lua_pushvalue		(lua_State *L, int index);
void lua_rotate			(lua_State *L, int index, int n);
void lua_remove  		(lua_State *L, int index);
void lua_insert 		(lua_State *L, int index);
void lua_replace        (lua_State *L, int index);
void lua_copy 			(lua_State *L, int fromidx,int toidx);

函數lua_gettop返回棧中元素的個數,也即棧頂元素的索引。函數lua_settop將棧頂設置爲一個指定的值,即修改棧中的元素數量。如果之前的棧頂比新設置的更高,那麼高出來的這些元素就會被丟棄;反之,該函數會向棧中壓入nil來不足大小。特別的,函數lua_settop(L,0)用於清空棧。在調用函數lua_settop時也可以使用負數索引;基於這個功能,CAPI提供了下面的宏,用於從棧中彈出n個元素:

#define lua_pop(L,n)  lua_settop(L,-(n) -1)

    函數lua_pushvalue用於將指定索引上的元素的副本壓入棧。
    函數lua_rotate是Lua5.3中新引入的。顧名思義,該函數將指定索引的元素向棧頂轉動n個位置。若n爲整數,表示將元素向棧頂方向轉動,而n爲負數則表示向相反的方向轉動。這是一個非常有用的函數,另外兩個CAPI操作實際上是基於使用該函數的宏定義的。其中一個是lua_remove,用於刪除指定索引的元素,並將該位置上的所有元素下移以填補空缺,其定義如下:

#define lua_remove(L,idx) \ (lua_rotate(L,(idx),-1),lua_pop(L,1))

也就是說,該函數會將棧頂轉動一格,把想要的那個元素移動到棧頂,然後彈出該元素。另一個宏是lua_insert,用於將棧頂元素移動到指定位置,並上移指定位置之上的所有元素以開闢出一個元素的空間:

#define lua_insert(L,inx)    lua_rotate(L,(idx),1)

    函數lua_replace彈出一個值,並將棧頂設置爲指定索引上的值,而不移動任何元素。最後,函數lua_copy將一個索引上的值複製到另一個索引上,並且原值不受影響。請注意,以下的操作不會對空棧產生影響:

lua_settop(L,-1);/*將棧頂設爲當前值*/
lua_insert(L,-1);/*將棧頂的元素移動到棧頂*/
lua_copy(L,x,x);/*把一個元素複製到它當前的位置*/
lua_rotate(L,x,0);/*旋轉零個位置*/

示例 棧操作示例

#include <stdio.h>
#include "lua.h"
#include "luaxlib.h"

static void stackDump(lua_State *L){
	參見上面那個示例
}
int main(void){
	lua_State *L = luaL_newstate();
	lua_pushboolean(L,1);
	lua_pushnumber(L,10);
	lua_pushnil(L);
	lua_pushstring(L,"hello");
	stackDump(L):
	/*將輸出:true 10 nil 'hello' */
	lua_pushvalue(L.-4); stackDump(L);
	/*將輸出:true 10 nil 'hello' true */
	lua_replace(L,3); stackDump(L);
	/*將輸出:true 10 nil 'hello' */
	lua_settop(L,6);stackDump(L);
	/*將輸出:true 10 true 'hello' nil nil */
	lua_rotate(L,3,1);stackDump(L);
	/*將輸出:true 10 nil true 'hello' nil */
	lua_remove(L,-3);stackDump(L);
	/*將輸出:true 10 nil 'hello' nil*/
	lua_settop(L,-5);stackDump(L);
	/*將輸出:true */
	lua_close(L);
	return 0;
}

使用CAPI進行錯誤處理

    Lua中所有的結構都是動態的:它們會按需擴展,並且在可能時最後重新收縮。這意味着在Lua中內存分配失敗可能無處不在,幾乎所有的操作最終都可能會面臨內存分配失敗。此外,許多操作可能會拋出異常。例如,訪問一個全局變量可能會觸發__index元方法,而該元方法又可能會拋出異常。最後,分配內存的操作會觸發垃圾收集器,而垃圾收集器又可能會調用同樣可能拋出異常的析構器。簡而言之,Lua API中的絕大部分函數都可能拋出異常。
    Lua語言使用異常來提示錯誤,而沒有再API的每個操作中使用錯誤碼。與C++或Java不同,C語言沒有提供異常處理機制。爲了解決這個問題,Lua使用了C語言中的setjmp機制,setjmp營造了一個類似異常處理的機制。因此,大多數API函數都可以跑出異常而不是直接返回。
    在編寫庫代碼時,由於Lua會捕獲所有異常,因此,對我們來說使用longjmp並不是進行額外的操作。不過,在編寫應用程序代碼時,則必須提供一種捕獲異常的方法。

處理應用代碼中的錯誤

    如果應用調用了Lua API中的函數,就可能發生錯誤。Lua語言通常通過長跳轉來提示錯誤。但是,如果沒有相應的setjmp,解釋器就無法進行長跳轉。此時,API中的任何錯誤都會導致Lua調用緊急函數,當這個函數返回後,應用就會退出。我們可以通過函數lua_atpanic來設置自己的緊急函數,但作用不大。
    要正確地處理應用代碼中的錯誤,就必須通過Lua語言調用我們自己的代碼,這樣Lua語言才能設置適合的上下文來捕獲異常,即在setjmp的上下文中運行代碼。類似於通過函數pcall在保護模式中運行Lua代碼,我們也可以用函數lua_pcall運行C代碼。更具體地說,可以把C代碼封裝到一個函數F中,然後使用lua_pcall調用這個函數F。通過這種方式,我們的C代碼會在保護模式下運行。即便發生內存分配失敗,函數lua_pcall也會返回一個對應的錯誤碼,是解釋器能夠保持一致的狀態,如下所示:

static int foo(lua_State *L){
	code to run in protected mode(要以保護模式運行的代碼)
	return 0;
}
int secure_foo(lua_State *L){
	lua_pushcfunction(L,foo)/*將foo作爲Lua函數壓棧*/
	return (lua_pcall(L,0,0,0) == 0);
}

在上述示例中,無論發生什麼,調用secure_foo時都會返回一個布爾值,來表示foo執行是否成功。特別的,請注意,棧中已經預先分配了空間,而且函數lua_pushcfunction不會分配內存,這樣纔不會引發錯誤。

處理庫代碼中的錯誤

    Lua是一種安全的語言。這意味着不管用Lua寫什麼,也不管寫出來的內容多麼不正確,我們總能用它自身的機制來理解程序的行爲。此外,程序中的錯誤也是通過Lua語言的機制來檢測和解釋的。與之相比,許多C語言代碼中的錯誤只能從底層硬件的角度來解釋。
    只要往Lua中加入新的C函數,這種安全性就可能被打破。例如,一個等價於BASIC命令poke的函數就可能導致各種各樣的內存崩潰。因此,我們必須確保新加入的內容對Lua語言來說是安全的,並提供妥善的錯誤處理。
    正如之前討論的,C語言程序必須通過lua_pcall設置錯誤處理。不過,在爲lua編寫庫函數時,通常無須處理錯誤。庫函數拋出的錯誤要麼被Lua中的pcall捕獲,要麼被應用代碼中的lua_pcall捕獲。因此,當C語言庫中的函數檢測到錯誤時,只需要簡單地調用lua_error即可。函數lua_error會收拾Lua系統中的殘局,然後跳轉回保護模式調用處,並傳遞錯誤信息。

內存分配

    Lua語言核心對內存分配不進行任何假設,它既不會調用malloc也不會調用realloc來分配內存。相反,Lua語言核心只會通過一個分配函數來分配和釋放內存,當用戶創建Lua狀態時必須提供該函數。
    luaL_newstate是一個用默認分配函數來創建Lua狀態的輔助函數。該默認分配函數使用了來自C語言標準函數庫的標準函數malloc-realloc-free,對於大多數應用程序來說,這幾個函數夠用了。但是,要完全控制Lua的內存分配也很容易,使用原始的lua_newstate來創建我們自己的Lua狀態即可:

lua_State *lua_newstate(lua_Alloc f, void *ud);

該函數有兩個參數:一個是分配函數,另一個是用戶數據。用這種方式創建的Lua狀態會通過調用f完成所有的內存分配和釋放,甚至結構lua_State也是由f分配的。
    分配函數必須滿足lua_Alloc的類型聲明:

typedef void * (*lua_Alloc) (void *ud,
							 void *ptr,
							 size_t osize,
							 size_t nsize);

第一個參數始終爲lua_newstate所提供的用戶數據;第二個參數是正要被分配或者釋放的地址;第三個參數是原始塊的大小;最後一個參數是請求的塊大小。如果ptr不是NULL,Lua會保證其之前被分配的大小就是osize。
    Lua語言使用NULL表示大小爲零的塊。當nsize爲零時,分配函數必須釋放ptr指向的塊並返回NULL,對應於所要求的大小的塊。當ptr是NULL時,該函數必須分配並返回一個指定大小的塊;如果無法分配指定的塊,則必須返回NULL。如果ptr是NULL並且nsize爲零,則兩條規則都適用:最終結果是分配函數什麼都不做,返回NULL。
    最後,當ptr不是NULL並且nsize不爲零時,分配函數應該像realloc一樣重新分配塊並且返回新地址。同樣,當出現錯誤分配函數必須返回NULL。Lua假定分配函數在塊的新尺寸小於或等於舊尺寸時不會失敗。
    luaL_newstate使用的標準分配函數定義如下:

void *l_alloc (void *ud , void *ptr, size_t osize, size_t nsize){
	(void)ud; (void)osie;/*未使用*/
	if(nsize == 0 ){
		free(ptr);
		return NULL;
	}
else
	return realloc (ptr,nsize);
}

該函數假設free(NULL)什麼也不做,並且realloc(NULL,size)等價於malloc(size)。IOSC標準會託管這兩種行爲。
    我們可以通過調用lua_getallocf恢復Lua狀態的內存分配器:

lua_Alloc lua_getallocf (lua_State *L, void **ud);

如果ud不是NULL,那麼該函數會把*ud設置爲該分配器的用戶數據。我們可以通過調用lua_setallocf來更改Lua狀態的內存分配器:

void lua_setallocf (lua_State *L, Lua_Alloc f, void *ud);

請記住,所有新的分配函數都有責任釋放由前一個分配函數分配的塊。通常情況下,新的分配函數是在舊分配函數的基礎上做了包裝,來追蹤或同步訪問堆的。
    Lua在內部不會爲了重用而緩存空閒內存。它假定分配函數會完成這種緩存工作;而優秀的分配函數確實也會這麼做。Lua不會試圖壓縮內存碎片。研究表明,內存碎片更多是由糟糕的分配決策導致的,而非程序的行爲造成的;而優秀的分配函數不會造成太多內存碎片。
    對於已有的優秀分配函數,想要做到比它更好是很難的,但有時候也不妨試一試。例如,Lua會告訴你已經釋放或者重新分配的塊的大小。因此,一個特定的分配函數不需要保存有關塊大小的信息,以此減少每個塊的內存開銷。
    還有一種可以改善的內存分配的場景,是在多線程系統中。這種系統通常需要對內存分配函數進行線程同步,因爲這些函數使用的是全局資源。不過,對Lua狀態的訪問也必須是同步的——或者更好的情況是,限制只有一個線程能夠訪問Lua狀態。如果每個Lua狀態都從私有的內存池中分配內存,那麼分配函數就可以避免線程同步導致的額外開銷了。

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