一般在用c写的lua函数里,检测到某些参数或前置条件错误时,会用luaL_error来报错,这时脚本层如果是xpcall,其错误处理函数就会被调用,在其中可用debug.traceback()来打印记录错误栈,这是脚本错误调试的常用做法。
但是在c++环境中使用lua时,却有一个细微而致命的bug:lua通常是按c语言方式编译,因此luaL_error最终会调用longjump来实现跨函数远程跳转,而这种跳转不会遵循c++关于stack unwinding的规范,其最直接的影响就是局部变量的析构函数不会被调用。
如果使用的是官方版lua,那么可以用c++方式编译lua来解决。但是如果用的luajit,它本身是不支持c++编译的(很多语法错误),那只有自求多福了,在luajit的网站上对此也有说明:
C++ Exception Interoperability
LuaJIT has built-in support for interoperating with C++ exceptions. The available range of features depends on the target platform and the toolchain used to compile LuaJIT:
Platform Compiler Interoperability POSIX/x64, DWARF2 unwinding GCC 4.3+ Full Other platforms, DWARF2 unwinding GCC Limited Windows/x64 MSVC or WinSDK Full Windows/x86 Any No Other platforms Other compilers No Full interoperability means:
- C++ exceptions can be caught on the Lua side with pcall(), lua_pcall() etc.
- C++ exceptions will be converted to the generic Lua error "C++ exception", unless you use the C call wrapper feature.
- It's safe to throw C++ exceptions across non-protected Lua frames on the C stack. The contents of the C++ exception object pass through unmodified.
- Lua errors can be caught on the C++ side with catch(...). The corresponding Lua error message can be retrieved from the Lua stack.
- Throwing Lua errors across C++ frames is safe. C++ destructors will be called.
Limited interoperability means:
- C++ exceptions can be caught on the Lua side with pcall(), lua_pcall() etc.
- C++ exceptions will be converted to the generic Lua error "C++ exception", unless you use the C call wrapper feature.
- C++ exceptions will be caught by non-protected Lua frames and are rethrown as a generic Lua error. The C++ exception object will be destroyed.
- Lua errors cannot be caught on the C++ side.
- Throwing Lua errors across C++ frames will not call C++ destructors.
No interoperability means:
-
It's not safe to throw C++ exceptions across Lua frames.
-
C++ exceptions cannot be caught on the Lua side.
-
Lua errors cannot be caught on the C++ side.
-
Throwing Lua errors across C++ frames will not call C++ destructors.
-
Additionally, on Windows/x86 with SEH-based C++ exceptions: it's not safe to throw a Lua error across any frames containing a C++ function with any try/catch construct or using variables with (implicit) destructors. This also applies to any functions which may be inlined in such a function. It doesn't matter whether lua_error() is called inside or outside of a try/catch or whether any object actually needs to be destroyed: the SEH chain is corrupted and this will eventually lead to the termination of the process.
在x86平台上是完全不支持的。那么解决办法只有一条:自己保证luaL_error的调用不会冲掉局部对象的析构函数。在同一个函数里,一般可以用无条件的块来人为分隔作用域,如:
{ std::string s="abc"; ... } luaL_error(L,...);
但是如果上层函数中还有局部对象,那只有自己根据具体的业务需求做调整了。通常来说,通过lua_register注册的c函数,其上层调用就是lua函数了,因此只要管好从它到抛出错误的点之间的调用路径上,没有被冲击的局部对象即可。但如果调用该c函数的lua函数,本身又是从另一个c函数里调过来的,该怎样考虑呢?很简单,从c里调lua时,必须包在pcall里,这样抛上来的错误就一定到此截止了。