错误处理
Lua 使用异常机制(longjmp)处理错误。在 C 代码中调用 Lua 时,必须注意错误传播,否则会导致程序崩溃。
保护模式调用
lua_pcall
始终用 lua_pcall 代替 lua_call(除非你在一个被 Lua 调用的 C 函数内部,且确定不会出错)。
if (lua_pcall(L, nargs, nresults, msgh) != LUA_OK) {
// 出错
const char *errmsg = lua_tostring(L, -1);
fprintf(stderr, "Lua error: %s\n", errmsg);
lua_pop(L, 1); // 弹出错误信息
}
错误处理函数(msgh)
pcall 的第 4 个参数可以指定一个错误处理函数。当发生错误时,Lua
会先调用这个函数,将原始错误信息传给它,然后将处理函数的返回值作为最终错误信息。
常见用途是打印调用栈:
lua_getglobal(L, "debug");
lua_getfield(L, -1, "traceback");
lua_remove(L, -2); // 移除 debug table,保留 traceback 函数
// 此时 traceback 函数在栈顶,其索引就是 -1
if (lua_pcall(L, nargs, nresults, -2) != LUA_OK) { // 注意索引计算
...
}
从 C 报告错误
抛出 Lua 错误
在注册给 Lua 的 C 函数中,如果发现参数错误,应该使用 luaL_error 或 luaL_argerror:
static int myfunc(lua_State *L) {
if (!lua_isinteger(L, 1)) {
return luaL_argerror(L, 1, "expected integer"); // 不会返回
}
// ...
}
格式化错误信息
return luaL_error(L, "bad value at index %d: %s", idx, reason);
luaL_error使用lua_error抛出异常,不会返回到调用者。
C 函数中的保护模式
如果你想在 C 函数内部保护一段代码,使用 lua_pcall 即可。但如果需要在 C 层面做更细粒度的保护,可以使用:
int lua_cpcall(lua_State *L, lua_CFunction func, void *ud);
不过 Lua 5.2+ 更推荐的方式是在 C 函数内直接组织好栈,然后使用 lua_pcall。
常见错误场景
| 场景 | 结果 | 正确做法 |
|---|---|---|
lua_call 在保护模式外调用出错代码 | 程序 longjmp 到未知位置,崩溃 | 使用 lua_pcall |
lua_tostring 对非 string/number 使用 | 返回 NULL | 先用 lua_isstring 检查 |
| 栈索引越界 | 未定义行为 | 用 lua_gettop 确认栈大小 |
lua_tolstring 后修改字符串 | 未定义行为 | 复制到 C 缓冲区后再修改 |
完整代码
#include <assert.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <stdio.h>
#include <string.h>
static int c_divide(lua_State *L) {
lua_Integer a = luaL_checkinteger(L, 1);
lua_Integer b = luaL_checkinteger(L, 2);
if (b == 0)
return luaL_error(L, "division by zero");
lua_pushinteger(L, a / b);
return 1;
}
static int safe_divide(lua_State *L) {
lua_getglobal(L, "debug");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return luaL_error(L, "debug library not available");
}
lua_getfield(L, -1, "traceback");
lua_remove(L, -2);
lua_pushcfunction(L, c_divide);
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
if (lua_pcall(L, 2, 1, -4) != LUA_OK) {
return 1;
}
return 1;
}
int main(void) {
lua_State *L = luaL_newstate();
luaL_openlibs(L);
lua_pushcfunction(L, c_divide);
lua_setglobal(L, "c_divide");
lua_pushcfunction(L, safe_divide);
lua_setglobal(L, "safe_divide");
luaL_dostring(L, "print(c_divide(10, 3))");
assert(luaL_dostring(L, "print(c_divide('a', 3))") != LUA_OK);
lua_pop(L, 1);
assert(luaL_dostring(L, "print(c_divide(10, 0))") != LUA_OK);
lua_pop(L, 1);
luaL_dostring(L, "print(safe_divide(10, 0))");
lua_close(L);
puts("06-errors: ok");
return 0;
}