Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

状态机与内存管理

除了 luaL_newstate() 这种开箱即用的方式,Lua C API 还提供了更底层的状态机控制接口。

自定义内存分配器

luaL_newstate() 内部使用标准 malloc/free。如果你需要自定义内存管理(如使用内存池、追踪泄漏、限制总量),可以使用 lua_newstate

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

lua_Alloc 的签名:

typedef void * (*lua_Alloc)(void *ud, void *ptr, size_t osize, size_t nsize);
场景参数值行为
分配新块ptr=NULL, osize=0, nsize>0分配 nsize 字节
重新分配ptr!=NULL, osize=原大小, nsize>0调整为 nsize 字节
释放ptr!=NULL, nsize=0释放 ptr

osize 的值还暗示了分配用途(Lua 内部用此做统计):

  • osize == LUA_TSTRING 等:为特定类型分配
  • 其他值:原始字节数

Extra Space

每个 lua_State 在头部预留了一块额外空间(大小由 LUAI_EXTRASPACE 决定),可通过宏访问:

void *extra = lua_getextraspace(L);

常用于存储与状态机关联的自定义上下文指针,避免全局变量。

查询与替换分配器

lua_Alloc old = lua_getallocf(L, &old_ud);
lua_setallocf(L, my_alloc, my_ud);

栈转移

当有两个 lua_State*(如主线程和协程)时,可以用 lua_xmove 在它们之间转移栈值:

lua_xmove(from, to, n);   // 从 'from' 栈顶弹出 n 个值,压入 'to' 

lua_xmove 要求两个状态机共享同一个全局表(即由 lua_newthread 创建的协程)。任意两个独立状态机之间不能使用

lua_xmove

版本查询

lua_Number ver = lua_version(L);

返回 LUA_VERSION_NUM(如 504),可用于运行时版本检查。

Panic 函数

当 Lua 错误发生在没有任何保护帧的情况下(极其严重的编程错误),Lua 会调用 panic 函数

lua_CFunction old = lua_atpanic(L, my_panic);

默认 panic 函数打印错误信息并调用 abort()。嵌入到游戏引擎或 GUI 程序时,通常需要替换为自定义实现以优雅退出。

完整代码


#include <assert.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static size_t total_allocated = 0;
static size_t total_freed = 0;
static size_t current_used = 0;

static void *my_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
    (void)ud;
    (void)osize;

    if (nsize == 0) {
        if (ptr) {
            total_freed += osize;
            current_used -= osize;
            free(ptr);
        }
        return NULL;
    } else {
        void *newp = realloc(ptr, nsize);
        if (newp && ptr == NULL) {

            total_allocated += nsize;
            current_used += nsize;
        } else if (newp && ptr != NULL) {

            current_used = current_used - osize + nsize;
            total_allocated += (nsize > osize) ? (nsize - osize) : 0;
        }
        return newp;
    }
}

static int my_panic(lua_State *L) {
    const char *msg = lua_tostring(L, -1);
    fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n",
            msg ? msg : "?");
    return 0;
}

int main(void) {

    lua_State *L = lua_newstate(my_alloc, NULL);
    if (!L) {
        fputs("cannot create state\n", stderr);
        return 1;
    }

    lua_atpanic(L, my_panic);

    assert(lua_version(L) == 504);

    luaL_openlibs(L);

    {
        int *ctx = (int *)lua_getextraspace(L);
        *ctx = 42;
        assert(*(int *)lua_getextraspace(L) == 42);
    }

    size_t before = current_used;
    (void)luaL_dostring(L, "local t = {} for i = 1, 1000 do t[i] = i end");
    assert(current_used > before);

    lua_State *co = lua_newthread(L);
    lua_pushstring(L, "hello from main");
    lua_xmove(L, co, 1);
    assert(strcmp(lua_tostring(co, -1), "hello from main") == 0);
    lua_pop(co, 1);

    void *ud;
    lua_Alloc allocf = lua_getallocf(L, &ud);
    assert(allocf == my_alloc);
    assert(ud == NULL);

    lua_close(L);
    assert(total_allocated > 0);
    assert(total_freed > 0);
    puts("12-state: ok");
    return 0;
}