一、lua_State 创建
C/C++ 与 Lua 的交互是通过 lua_State
这一句柄进行交互。我们常规的创建都是通过 luaL_newstate
这一辅助函数,他的源码实现如下:
cpp
LUALIB_API lua_State *luaL_newstate (void) {
lua_State *L = lua_newstate(l_alloc, NULL);
if (l_likely(L)) {
lua_atpanic(L, &panic);
lua_setwarnf(L, warnfoff, L); /* default is warnings off */
}
return L;
}
通过源码可以知道,真正的创建是通过 lua_newstate
函数
cpp
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud)
描述
函数创建并返回 lua_State
类型的指针,后续通过这一指针和 Lua 进行交互。这期间所有的内存分配和释放都会由参数 f 函数进行完成,包括该函数返回的 lua_State 。
参数
- 参数 f :分配函数
- 参数 ud :自定义用户数据,会携带进入 f 函数
返回值:
lua_State 的指针
二、分配函数
Lua 中默认的分配函数使用了 C 语言标准函数库的标准函数 malloc-realloc-free
进行内存的管理,以下便是 Lua 默认分配函数。
cpp
static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) {
(void)ud; (void)osize; /* not used */
if (nsize == 0) {
free(ptr);
return NULL;
}
else
return realloc(ptr, nsize);
}
我们可以自行定义,只需遵循 lua_Alloc
格式,通过 lua_newstate
函数或 lua_setallocf
函数设置即可生效
cpp
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);
参数:
- 参数 ud:
lua_newstate
的第二个参数便会作为该参数,并且是每次调用该分配函数都会携带。 - 参数 ptr: 正要被分配、或是重新分配、或者是要被释放的块地址。
- 参数 osize: 原始块的大小。
- 参数 nsize: 需要申请的块大小。
返回值:
- 如果需要创建新的内存块,则将创建的内存块指针返回。
- 如果不需要创建内存块,则返回 NULL。
值得注意:
从分配函数的参数和返回值,已经知道了这一函数的职责:释放原始块和创建新的块并返回。所以会涉及以下一些小细节:
- 当 ptr 是 NULL ,则原始内存块大小肯定是零,所以 Lua 使用 osize 存放某些调试信息。
- 当 ptr 是 NULL ,则分配函数必须分配并返回 nsize 指定大小的块。如果无法分配相应的块,则返回 NULL 。如果此时 nsize 为 零 则返回 NULL 。
- 当 nsize 为零时,分配函数必须释放 ptr 指向的块并返回 NULL 。
- 如果 ptr 不是 NULL 并且 nsize 不为零,则可以使用
realloc
进行重新分配块并返回(地址可能和原来一样,也可能不一样)。和第二点一样,如果出错了则返回 NULL 。 - Lua 会假定分配函数在块的新尺寸( nsize )小于或等于旧尺寸( osize )时不会失败。
三、获取当前的内存分配函数
通过 lua_getallocf
获取 Lua State 当前的内存分配函数
cpp
LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);
参数:
- 参数 L: lua_State 指针。
- 参数 ud: 会把当前设置的自定义用户数据设置给这一参数。
返回值:
Lua 当前使用的内存分配函数
四、设置内存分配函数
通过 lua_setallocf
设置新的内存分配函数
cpp
LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
参数:
- 参数 L: lua_State 指针。
- 参数 f: 需要设置新的内存分配函数。
- 参数 ud: 自定义用户数据。
五、举个例子
下面的代码主要验证了三个步骤:
- 创建 lua_State 时传入自己的内存分配函数和自定义用户数据,函数中会打印所有的参数。
- 获取当前的分配函数。
- 替换当前的分配函数,最后关闭 lua_State 的时候会打印日志。
cpp
static void *alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
const char *udContent = *(const char **) ud;
std::cout << "ud: " << udContent << "; *ptr: " << ptr << "; osize: " << osize << "; nsize: " << nsize << std::endl;
if (nsize == 0) {
free(ptr);
return nullptr;
} else
return realloc(ptr, nsize);
}
static void *newalloc(void *ud, void *ptr, size_t osize, size_t nsize) {
const char *udContent = *(const char **) ud;
std::cout << "新的分配函数 " << "ud: " << udContent << "; *ptr: " << ptr << "; osize: " << osize << "; nsize: " << nsize << std::endl;
if (nsize == 0) {
free(ptr);
return nullptr;
} else
return realloc(ptr, nsize);
}
void allocationFunction() {
// 常规的创建 lua_State
// auto L = luaL_newstate();
printf("------------- 自定义内存分配函数 -------------\n");
// 自定义内存分配数据
auto ud = "ud test message.";
auto L = lua_newstate(alloc, &ud);
printf("------------- 获取 Lua 当前的内存分配函数 -------------\n");
// lua_getallocf 第二个参数会返回之前设置的 自定义内存分配数据
void *userData = nullptr;
lua_Alloc allocFunc = lua_getallocf(L, &userData);
const char *udContent = *(const char **) userData;
std::cout << &allocFunc << " " << udContent << std::endl;
printf("------------- 设置 Lua 的内存分配函数 -------------\n");
auto nud = "new ud test message.";
lua_setallocf(L, newalloc, &nud);
std::cout << "关闭 lua_State " << std::endl;
lua_close(L);
}
运行之后的日志
cpp
------------- 自定义内存分配函数 -------------
ud: ud test message.; *ptr: 0x0; osize: 8; nsize: 1624
...... 省略很多内存分配打印日志
ud: ud test message.; *ptr: 0x0; osize: 4; nsize: 30
------------- 获取 Lua 当前的内存分配函数 -------------
0x7ff7b7775f80 ud test message.
------------- 设置 Lua 的内存分配函数 -------------
关闭 lua_State
新的分配函数 ud: new ud test message.; *ptr: 0x0; osize: 0; nsize: 0
...... 省略很多内存分配打印日志
新的分配函数 ud: new ud test message.; *ptr: 0x7fd3ef80c400; osize: 1624; nsize: 0
六、写在最后
Lua 项目地址:Github传送门 (如果对你有所帮助或喜欢的话,赏个star吧,码字不易,请多多支持)
如果觉得本篇博文对你有所启发或是解决了困惑,点个赞或关注我呀。
公众号搜索 "江澎涌",更多优质文章会第一时间分享与你。