捕获 Lua 异常 |
|
Lua 版本: 5.0 (版本说明)
本文介绍了一组补丁,用于在 C 和 C++ 中简化 Lua 异常处理。
在 Lua 脚本中生成的异常有两种可能的结果
Lua 的 C API 提供了函数 lua_pcall() 用于进行受保护的调用 [1].
当程序“抛出”异常时,它正在进行“非局部跳转”。它正在跳转到代码中的一个点,该点可能不在同一个块、同一个函数,甚至同一个模块中。Lua 手册将这些非局部跳转的目标称为“恢复点”。
由于 Lua 运行时是用 C 编写的,它不能使用 C++ 异常,因此 Lua 使用 C 函数 setjmp() 和 longjmp() 来实现此行为 [2].
注意:从 Lua 5.1 开始,当编译为 C++ 时,Lua 使用本机 C++ 异常而不是 setjmp() 和 longjmp()。
setjmp()/longjmp() 的问题
setjmp()/longjmp() 习惯用法非常高效且易于使用。它被用于许多 C 库来提供异常处理支持。但是,它并非没有问题。
setjmp() 将 CPU 执行环境的当前状态(寄存器、堆栈指针等)存储到一个缓冲区中。使用该缓冲区调用 longjmp() 会恢复保存的执行环境。本质上,longjmp() “时间旅行” 回到 setjmp() 调用。程序从该点继续执行,其执行环境看起来好像没有改变。
jmp_buf jb;
char* mystr = "Original Value";
/* start */
int i_except = setjmp( jb );
if( 0 == i_except ) /* try */
{
mystr = "New Value";
/* Throw an exception.
Jumps back to "start", and setjmp() returns 33
*/
longjmp( jb, 33 );
}
else /* catch */
{
/* Handle the exception */
}
print( mystr );
许多编译器会通过在寄存器中缓存其值来优化对局部变量的引用。当代码执行是顺序时,这很好。如果编译器用完寄存器,它可以将值存储到内存中并在以后加载它。但是,调用 longjmp() 会恢复由 setjmp() 保存的寄存器状态。如果编译器已将变量的值缓存到寄存器中,并且该值在 setjmp() 之后但在 longjmp() 之前发生了更改,则其新值将在 longjmp() 恢复其旧值时丢失。
在上面的示例中,变量 mystr 易受此问题的影响。根据编译器如何优化代码,当调用 print() 时,mayvar 可能包含“新值”,也可能包含“原始值”。
-- 实际上,上面示例中 setjmp 调用的上下文是非标准的,导致的结果是未定义的。下面这个示例在这方面是 OK 的。 -- Wim Couwenberg
这个问题可以通过仔细的编程来缓解。解决方案是
setjmp()/longjmp() 之间更改局部变量。
setjmp()/longjmp() 块之外声明的变量应该声明为 volatile。
在上面的示例中,将 mystr 声明为 volatile 将阻止编译器将其值缓存到寄存器中
char* volatile mystr;
声明一个 volatile int
volatile int myint;
注意,函数参数必须包含在 volatile 变量集中
int func( int* volatile pInt ) /* pointer declared volatile */
{
jmp_buf jb;
if( 0 == setjmp( jb ) )
{
pInt++;
longjmp( jb, 97 );
}
else
{
}
}