参考资料:
Lua是一个嵌入式的语言,它不仅可以是一个独立运行的程序,也可以是一个用来嵌入其它应用的程序库。本文对网上一些资料进行了整理,按照如下的应用场景将Lua与C的交互相关知识进行了串联:
C API是一个C代码与Lua进行交互的函数集,它由以下几部分构成:
(1) 读写Lua全局变量的函数;
(2)调用Lua函数的函数;
(3)运行Lua代码片段的函数;
(4)注册C函数后可以在Lua中被调用的函数;
1 C程序调用Lua。
lua_xx的函数是C-API库函数(最基础的API),luaL_xx的函数是C-API辅助库函数(比基础API更抽象的函数),luaopen_xx的函数是标准库函数,LUA_XX是常量(宏定义)。
1.1 C程序如何调用的Lua程序
程序开始用luaL_open()函数创建一个lua_State。lua_State中保存了Lua运行时的所有的状态信息(比如变量的值等),并且所有的Lua的C的API都有一个lua_newstate指针的参数。luaL_open函数会创建一个全新的Lua运行时状态,其中没有任何预先定义好的函数(包括最基本的print函数)。如果需要试用标准类库的话,只要调用luaL_openlibs(L)函数就打开标准类库就可以了。标准类库被分别封装在不同的包中,当你需要使用的时候再引入到代码中,这样做的好处是可以使Lua尽可能的小(嵌入式语言必须要小),从而可以方便嵌入到其他语言中去。当Lua运行时状态和标准类库都准备完成后,就可以调用luaL_dofile(L,"test.lua")函数来执行Lua脚本。运行结束后,需要调用lua_close(L)来关闭Lua运行时状态。
1.1.1 创建Lua状态机
lua_State* lua_open(); 没有指定内存分配函数的功能,不建议再使用。
lua_State *lua_newstate (lua_Alloc f, void*ud); 创建一个新的、独立的Lua状态机,如果因为内存不足导致创建失败,返回NULL。参数f 指定内存分配函数,参数ud是传给f 函数的指针。
注意:lua_State表示的一个Lua程序的执行状态,它代表一个新的线程(注意是指Lua中的thread类型,不是指操作系统中的线程),每个thread拥有独立的数据栈以及函数调用链,还有独立的调试钩子和错误处理方法。
1.1.2销毁lua状态机
void lua_close(lua_State*L); 销毁Lua状态机的所有对象,回收分配的内存。
1.1.3载入Lua库
Lua程序编译完后,一般生成一个xx.lua的文件,C程序可以通过调用载入相关的API接口对Lua程序进行加载。如:
void luaL_openlibs(lua_State *L);
void luaopen_base(lua_State *L);
void luaopen_package(lua_State *L);
void luaopen_string(lua_State *L);
void luaopen_io(lua_State *L);
void luaopen_table(lua_State *L);
void luaopen_math(lua_State *L);
1.1.4 编译/加载Lua代码。
Chunk是一系列语句,Lua执行的每一块语句,比如一个文件或者交互模式下的每一行都是一个Chunk。每个语句结尾的分号是可选的。如果一行有多个语句,最好用分号隔开。一个Chunk可以是一个语句,亦可以是一系列语句的组合,还可以是函数。Chunk可以很大,在Lua中几个MByte的Chunk也很常见。
int luaL_dofile(lua_State *L, char *lua_script); 加载并执行给定lua文件,成功返回0,错误返回1;
int luaL_dostring (lua_State *L, const char *str); 加载并执行给定string,成功返回0,错误返回1;
int lua_load (lua_State *L, lua_Reader reader, void *data,const char*chunkname); 加载一段chunk(但并不执行它),并将编译后的代码作为一个函数压入堆栈,如果发生错误,则将错误消息压入堆栈;
int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, const char*name); 从一个buffer中加载chunk;
int luaL_loadfile (lua_State *L, const char *filename); 从文件加载chunk;
int luaL_loadstring (lua_State *L, const char *s); 从字符串加载chunk;
1.1.5函数参数检查
voidluaL_checkany (lua_State *L, int narg); 检查函数有多少个(由narg指定)参数;
int luaL_checkint (lua_State *L, int narg); 检查函数的第narg个参数类型是否number型,并且返回这个number型参数;
lua_Integer luaL_checkinteger (lua_State *L, int narg);
long luaL_checklong (lua_State *L, int narg);
const char *luaL_checklstring (lua_State *L, int narg, size_t *l);
lua_Number luaL_checknumber (lua_State *L, int narg); 检查函数的第 narg 个参数是否是一个数字,并返回这个数字。
int luaL_checkoption (lua_State *L, int narg, const char *def, constchar *const lst[]);检查函数第narg个参数是否位字符串类型,并且在lst[](字符串数组)中搜索这个字符串,最后返回匹配的数组下标,如未能匹配,引发一个错误。如果参数def非空,当narg参数不存在或为nil时,就是要def作为搜索串。这个函数的作用是将字符串映射为C的enum类型。
const char *luaL_checkstring (lua_State *L, int narg);
void luaL_checktype (lua_State *L, int narg, int t); 检查函数第narg个参数是否为指定类型;
void *luaL_checkudata (lua_State *L, int narg, const char *tname); 检查函数第narg个参数是否为name类型的userdata;
1.2 C程序与Lua程序如何进行数据交互
1.2.1 虚拟栈介绍
在C和LUA之间交互的关键在于一个虚拟栈(virtual stack),数据交互通过栈进行。操作数据时,首先将数据拷贝到栈上,然后获取数据,栈中的每个数据通过索引值进行定位,索引值为正时表示相对于栈底的偏移索引,索引值为负时表示相对于栈顶的偏移索引。索引值以1或 -1起始值,因此栈顶索引值永远为-1,栈底索引值永远为1 。 "栈"相当于数据在Lua和C之间的中转地,每种数据都有相应的存取接口。
另外,还可以使用栈来保存临时变量。栈的使用解决了C和LUA之间两个不协调的问题:
(1) Lua使用自动垃圾收集机制,而C要求显式的分配和释放内存;
(2) Lua使用动态数据类型,而C使用静态类型;
特别注意的是:
(1)每当Lua调用C函数时,C函数会使用一个局部栈,这个局部栈与之前的栈,以及其它正在调用的C函数使用的栈都是相互独立的。Lua和C就使用这个局部的栈进行数据交互。
(2)当Lua调用C时,栈至少包含LUA_MINSTACK(20)个位置,程序员也可以使用lua_checkstack函数来增加栈的大小。
(3)使用伪索引(Pseudo-Indices)来表示一些不在栈中的数据,比如thread环境、C函数环境、registry、C闭包的upvalues。
· thread环境(全局变量也在这里),使用伪索引 LUA_GLOBALSINDEX;
· 运行中的C函数环境,使用伪索引 LUA_ENVIRONINDEX;
· Registry,使用伪索引 LUA_REGISTRYINDEX;
· C闭包的upvalues,可以使用lua_upvalueindex(n)来访问第n个upvalue;
1.2.2 Lua中的数据结构
Lua是动态类型语言,变量不要类型定义,只需要为变量赋值。值可以存储在变量中,作为参数传递或结果返回。
Lua中有8个基本类型分别为:nil、boolean、number、string、userdata、function、thread和table。
数据类型 | 描 述 |
nil | 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
boolean | 包含两个值:false和true。 |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示 |
function | 由 C 或 Lua 编写的函数 |
userdata | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线路,用于执行协同程序 |
table | Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
1.2.3 table 及 metatable操作
table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数字、字典等。
Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。
Lua table 是不固定大小的,你可以根据自己需要进行扩容。
当我们为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为nil ,则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。
void lua_createtable (lua_State *L, int narr, int nrec); 创建一个空table并压入堆栈,它会为narr个数组风格元素,和nrec个记录风格元素预分配内存空间。
void lua_newtable (lua_State *L); 创建一个空table,并压入stack,等价于lua_createtable(L, 0, 0);
void lua_settable (lua_State *L, int index); 相当于t[k]=v 操作,其中值t由参数index指定,v是栈顶,k是栈顶下一个元素;这个函数会将key和value都弹出栈;
void lua_gettable (lua_State *L, int index); 将t[k]压入堆栈,由参数index指定操作的table,k是栈顶元素。这个函数会弹出栈顶的key,并由t[k]代替;
Lua 元表(Metatable)
在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。
例如,使用元表我们可以定义Lua如何计算两个table的相加操作a+b。
当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"__add"的字段,若找到,则调用对应的值。"__add"等即时字段,其对应的值(往往是一个函数或是table)就是"元方法"。
int luaL_newmetatable (lua_State *L, const char *tname); 在Registry中创建一个key为tname的metatable,并返回1;如果Registry 已经有tname这个key,则返回0;这两种情况都会将metatable压入堆栈;
void luaL_getmetatable (lua_State *L, const char *tname); 将Registry中key为tname的metatable压入堆栈;
int lua_getmetatable (lua_State *L, int index); 将index处的元表压入堆栈;
int lua_setmetatable (lua_State *L, int index); 弹出栈顶,并将它作为由index指定元素的元表;
int luaL_getmetafield (lua_State *L, int obj, const char *e); 将索引obj处的元素的元表的e字段压入堆栈;
1.2.4按照指定格式元素入栈
void lua_pushboolean (lua_State *L, int b); void lua_pushcclosure(lua_State *L, lua_CFunction fn, int n);
void lua_pushcfunction (lua_State *L, lua_CFunction f);
const char *lua_pushfstring (lua_State *L, const char *fmt, ...); 将一个格式化字符串压入堆栈,并返回指向这个字符串的指针
void lua_pushinteger (lua_State *L, lua_Integer n);
void lua_pushlightuserdata (lua_State *L, void *p);
void lua_pushliteral (lua_State *L, const char *s); void lua_pushlstring(lua_State *L, const char *s, size_t len); 将一个指定大小的字符串压入堆栈;
void lua_pushnil (lua_State *L);
void lua_pushstring (lua_State *L, const char *s);
lua_pushvalue 将栈中指定索引的元素复制一份到栈顶;
值得注意的是,向栈中压入一个元素时,应该确保栈中具有足够的空间,可以调用lua_checkstack来检测是否有足够的空间。
实质上这些API是把C语言里面的值封装成Lua类型的值压入栈中的,对于那些需要垃圾回收的元素(如string、full userdata),在压入栈时,都会在Lua(也就是Lua虚拟机中)生成一个副本,从此不会再依赖于原来的C值。例如lua_pushstring 向栈中压入一个以'\0'结尾的字符串,在C中调用这个函数后,可以任意修改或释放这个字符串,也不会出现问题。
1.2.5栈中元素操作
void lua_pop(lua_State *L, int n); 从栈中弹出n个元素;
int lua_gettop (lua_State *L); 返回栈顶元素的索引(也即元素个数);
void lua_concat (lua_State *L, int n); 将栈顶开始的n个元素连接起来,并将它们出栈,然后将结果入栈;
void lua_getfield (lua_State *L, int index, const char *k); 将t[k]压入堆栈,t由参数index指定在栈中的位置;
void lua_setfield (lua_State *L, int index, const char *k); 相当于t[k]=v,t由参数index指定在栈中的位置,v是栈顶元素,改函数会将栈顶的value出栈;
void lua_getglobal(lua_State *L, char *name); 等价于 lua_getfield(L, LUA_GLOBALSINDEX, s),注意:栈中LUA_GLOBALSINDEX索引位置处是当前Lua状态机的全局变量环境。
void lua_setglobal (lua_State *L, const char *name); 等价于 lua_setfield(L, LUA_GLOBALSINDEX, s);
void lua_insert (lua_State *L, int index); 移动栈顶元素到index指定的位置;
void lua_remove (lua_State *L, int index); 移除index处的元素,index之上的元素均下移一个位置;
void lua_replace (lua_State *L, int index); 将栈顶元素移到指定位置,并取代原来的元素,原先的栈顶元素弹出;
int lua_next (lua_State *L, int index); 弹出一个key,然后将t[key]入栈,t是参数index处的table;在利用lua_next遍历栈中的table时,对key使用lua_tolstring尤其需要注意,除非知道key都是string类型。
size_t lua_objlen (lua_State *L, int index); 返回index处元素的长度,对string,返回字符串长度;对table,返回"#"运算符的结果;对userdata,返回内存大小;其它类型返回0;
void luaL_checkstack (lua_State *L, int sz, const char *msg);增加栈大小(新增sz个元素的空间),如果grow失败,引发一个错误,msg参数传递错误消息。
1.2.6栈中指定元素类型检查
int lua_isboolean (lua_State *L, int index);
int lua_iscfunction (lua_State *L, int index);
int lua_isfunction (lua_State *L, int index);
int lua_islightuserdata (lua_State *L, int index);
int lua_isnil (lua_State *L, int index);
int lua_isnone (lua_State *L, int index);
int lua_isnoneornil (lua_State *L, int index);
int lua_isnumber (lua_State *L, int index);
int lua_isstring (lua_State *L, int index);
int lua_istable (lua_State *L, int index);
int lua_isthread (lua_State *L, int index);
int lua_isuserdata (lua_State *L, int index);
1.2.7函数调用
void lua_call (lua_State *L, int nargs, int nresults); 调用函数,参数nargs指定函数参数个数,参数nresults指定返回值个数。首先,被调函数必须在栈中;其次,函数参数必须是按从左往右的顺序入栈的;函数调用时,所有函数参数都会弹出堆栈。函数返回时,其返回值入栈(第一个返回最最先入栈)。
int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc); 以保护模式调用函数,如果发生错误,捕捉它,并将错误消息压入栈,然后返回错误码。
int lua_cpcall (lua_State *L, lua_CFunction func, void *ud); 以保护模式调用C函数func,参数ud指针指向一个用户自定义数据。
1.2.8错误处理
int luaL_error (lua_State *L, const char *fmt, ...);引发一个错误。
1.2.9栈中元素类型转换
int lua_toboolean (lua_State *L, int index); 将给定index索引处的元素转换为bool类型(0或1);
lua_CFunction lua_tocfunction (lua_State *L, int index); 将给定index索引处的元素转换为C函数;
lua_Integer lua_tointeger (lua_State *L, int index); 将给定index索引处的元素转换为int类型;
const char *lua_tolstring (lua_State *L, int index, size_t *len); 将给定index索引处的元素转换为char*类型,如果len不为空,同时还设置len为字符串长度;该函数返回的指针,指向的是Lua虚拟机内部的字符串,这个字符串是以'\0'结尾的,但字符串中间也可能包含值为0的字符。
lua_Number lua_tonumber (lua_State *L, int index);将给定index索引处的元素转换为double类型;
const void *lua_topointer (lua_State *L, int index); 将给定index索引处的元素转换为void*类型;
const char *lua_tostring (lua_State *L, int index); 等价于参数len=NULL时的lua_tolstring;
lua_State *lua_tothread (lua_State *L, int index); 将给定index索引处的元素转换为lua_State*类型(即一个thread);
void *lua_touserdata (lua_State *L, int index); 返回给定index索引处的userdata对应的内存地址;
1.2.10 thread 操作
lua_State *lua_newthread (lua_State *L); 创建一个新的thread,然后压入堆栈,并返回一个lua_State*指针表示创建的新thread。新创建的thread与当前thread共享一个全局环境。没有销毁thread的显式调用,它由垃圾收集器负责回收。
int lua_resume (lua_State *L, int narg);
int lua_yield (lua_State *L, int nresults);
1.2.11关于Registry
在C里面如果函数要保存持久状态,只能依靠全局或static变量,但这样C API库就无法为多个LuaState状态同时提供服务(就类似于带有static变量的C函数是不可重入的)。为此Lua提供了一个名为Registry的预定义table,允许CAPI往Registry里面存储数据。
1.2.12关于References
Reference其实就是在一个指定的表t中保存一次lua的数据对象,Refenence本身其实就是表t的索引子,简称RefIndex,当RefIndex作为Refenence时,t[RefIndex]其实就是用户要求引用的lua数据对象。当RefIndex被luaL_unref()回收时,t每一个被回收的RefIndex构成一个单向链表: t[Refindex] = Refindex0, t[Refindex0] = Refindex1, t[Refindex1] =Refindex2 ... t[0] = Refindex。
注意,t[0]始终是指向了空闲链表的头部。每次调用luaL_ref()且回收链表为空时,都会产生一个新的Reference,每次调用luaL_unref()都会销毁一个指定的Reference存入空闲链表中。
2 Lua调用C函数
从Lua调用C函数我们必须注册函数,也就是说,我们必须把C函数的地址以一个适当的方式传递给Lua解释器。 当Lua调用C函数时,也使用了一个与C语言调用Lua时相同的栈。C函数从栈中获取函数参数,并将结果压入栈中。为了在栈中将函数结果与其他值区分开,C函数还应返回其压入栈中的结果个数。
栈不是一个全局性的结构,每个函数都有自己的局部私有栈。当Lua调用一个C函数时,第一个参数总是这个局部栈的索引1。
对于可被Lua调用的C函数而言,其接口必须遵循Lua要求的形式,即
typedef int (*lua_CFunction)(lua_State* L);
接收一个参数Lua_State*,即Lua的状态,返回值表示压入栈中的结果个数。
把要调用的C 函数注册到lua状态机中:
void lua_register (lua_State *L, const char *name, lua_CFunction f);
lua_register 是一个宏:#define lua_register(L,n,f) (lua_pushcfunction(L, f),lua_setglobal(L, n))
其中,参数name是lua中的函数名,f 是C中的函数。
从宏定义可以看出,这个函数的作用是把C函数压入堆栈,并在全局环境中设置Lua函数名;
3 程序主体在C中运行,C函数注册到Lua中。
C调用Lua,Lua调用C注册的函数,C得到函数的执行结果
#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>
//待Lua调用的C注册函数。
static int add2(lua_State*L)
{
//检查栈中的参数是否合法,1表示Lua调用时的第一个参数(从左到右),依此类推。
//如果Lua代码在调用时传递的参数不为number,该函数将报错并终止程序的执行。
doubleop1 = luaL_checknumber(L,1);
doubleop2 = luaL_checknumber(L,2);
//将函数的结果压入栈中。如果有多个返回值,可以在这里多次压入栈中。
lua_pushnumber(L,op1 +op2);
//返回值用于提示该C函数的返回值数量,即压入栈中的返回值数量。
return1;
}
//另一个待Lua调用的C注册函数。
static int sub2(lua_State*L)
{
doubleop1 = luaL_checknumber(L,1);
doubleop2 = luaL_checknumber(L,2);
lua_pushnumber(L,op1 -op2);
return1;
}
const char* testfunc = "print(add2(1.0,2.0))print(sub2(20.1,19))";
int main()
{
lua_State* L =luaL_newstate();
luaL_openlibs(L);
//将指定的函数注册为Lua的全局函数变量,其中第一个字符串参数为Lua代码
//在调用C函数时使用的全局函数名,第二个参数为实际C函数的指针。
lua_register(L,"add2", add2);
lua_register(L,"sub2", sub2);
//在注册完所有的C函数之后,即可在Lua的代码块中使用这些已经注册的C函数了。
if(luaL_dostring(L,testfunc))
printf("Failedto invoke.\n");
lua_close(L);
return0;
}
4 程序主体在Lua中运行,C函数作为库函数供Lua使用。
例A
#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <lauxlib.h>
#include <lualib.h>
//待注册的C函数,该函数的声明形式在上面的例子中已经给出。
//需要说明的是,该函数必须以C的形式被导出,因此extern "C"是必须的。
//函数代码和上例相同,这里不再赘述。
extern "C" intadd(lua_State* L)
{
doubleop1 = luaL_checknumber(L,1);
doubleop2 = luaL_checknumber(L,2);
lua_pushnumber(L,op1 +op2);
return1;
}
extern "C" intsub(lua_State* L)
{
doubleop1 = luaL_checknumber(L,1);
doubleop2 = luaL_checknumber(L,2);
lua_pushnumber(L,op1 -op2);
return1;
}
//luaL_Reg结构体的第一个字段为字符串,在注册时用于通知Lua该函数的名字。
//第一个字段为C函数指针。
//结构体数组中的最后一个元素的两个字段均为NULL,用于提示Lua注册函数已经到达数组的末尾。
static luaL_Reg mylibs[] = {
{"add",add},
{"sub",sub},
{NULL, NULL}
};
//该C库的唯一入口函数。其函数签名等同于上面的注册函数。见如下几点说明:
//1. 我们可以将该函数简单的理解为模块的工厂函数。
//2. 其函数名必须为luaopen_xxx,其中xxx表示library名称。Lua代码require "xxx"需要与之对应。
//3. 在luaL_register的调用中,其第一个字符串参数为模块名"xxx",第二个参数为待注册函数的数组。
//4. 需要强调的是,所有需要用到"xxx"的代码,不论C还是Lua,都必须保持一致,这是Lua的约定,
// 否则将无法调用。
extern "C" __declspec(dllexport)
int luaopen_mytestlib(lua_State* L)
{
constchar* libName = "mytestlib";
luaL_register(L,libName,mylibs);
return1;
}
require "mytestlib" --指定包名称
--在调用时,必须是package.function
print(mytestlib.add(1.0,2.0))
print(mytestlib.sub(20.1,19))
例B
#include <stdio.h>
#include <math.h>
#include <stdarg.h>
#include <stdlib.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
/* 所有注册给Lua的C函数具有
*"typedef int (*lua_CFunction) (lua_State *L);"的原型。
*/
staticint l_sin(lua_State *L)
{
// 如果给定虚拟栈中索引处的元素可以转换为数字,则返回转换后的数字,否则报错。
doubled = luaL_checknumber(L, 1);
lua_pushnumber(L, sin(d)); /*push result */
/* 这里可以看出,C可以返回给Lua多个结果,
* 通过多次调用lua_push*(),之后return返回结果的数量。
*/
return1; /* number of results */
}
/* 需要一个"luaL_Reg"类型的结构体,其中每一个元素对应一个提供给Lua的函数。
* 每一个元素中包含此函数在Lua中的名字,以及该函数在C库中的函数指针。
* 最后一个元素为“哨兵元素”(两个"NULL"),用于告诉Lua没有其他的函数需要注册。
*/
staticconststructluaL_Reg mylib[] = {
{"mysin", l_sin},
{NULL, NULL}
};
/* 此函数为C库中的“特殊函数”。
* 通过调用它注册所有C库中的函数,并将它们存储在适当的位置。
* 此函数的命名规则应遵循:
* 1、使用"luaopen_"作为前缀。
* 2、前缀之后的名字将作为"require"的参数。
*/
externint luaopen_mylib(lua_State* L)
{
/* void luaL_newlib (lua_State *L,const luaL_Reg l[]);
* 创建一个新的"table",并将"l"中所列出的函数注册为"table"的域。
*/
luaL_newlib(L, mylib);
return1;
}
将”mylib.c”编译为动态连接库,
prompt> gcc mylib.c -fPIC -shared -o mylib.so -Wall
prompt> ls
mylib.c mylib.so a.lua
“a.lua”文件中:
--[[ 这里"require"的参数对应C库中"luaopen_mylib()"中的"mylib"。
C库就放在"a.lua"的同级目录,"require"可以找到。]]
local
mylib =
require
"mylib"
-- 结果与上面的例子中相同,但是这里是通过调用C库中的函数实现。
print
(mylib.mysin(
3.14
/
2
))
--> 0.99999968293183
附加:
1、每一个与Lua通信的C函数都有其独有的虚拟栈。
2、在极端情况下,打印指定目录中文件的例子可能会造成小小的内存泄漏。在内存空间不足的情况下,l_dir()
中的lua_newtable()
、lua_pushstring()
和lua_settable()
都会立即抛出错误并终止程序的运行,这将导致closdir()
无法被调用。
3、通常C库中“特殊的函数”都被定义为公有的(使用extern
修饰),而其他函数均被定义为私有的(使用static
修饰)。
4、当你想要使用C函数扩展你的Lua程序时,即使只有一个C函数,也最好使用C库的方式。因为在不久的将来(通常来说会很快),你将需要其他的C函数。