捕获 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 { } }