捕获 Lua 异常

lua-users home
wiki

作者: KevinBaca

Lua 版本: 5.0 (版本说明)

本文介绍了一组补丁,用于在 C 和 C++ 中简化 Lua 异常处理。

简介

在 Lua 脚本中生成的异常有两种可能的结果

1. 它传播到调用链的顶部。Lua 运行时会发生恐慌,通常会退出程序。

2. 它在调用链中停止,在该链中进行了受保护的调用。进行受保护调用的函数可以处理该异常。

Lua 的 C API 提供了函数 lua_pcall() 用于进行受保护的调用 [1].

Lua 的实现

当程序“抛出”异常时,它正在进行“非局部跳转”。它正在跳转到代码中的一个点,该点可能不在同一个块、同一个函数,甚至同一个模块中。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

这个问题可以通过仔细的编程来缓解。解决方案是

1. 不要在调用 setjmp()/longjmp() 之间更改局部变量。

2. 在 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
    {
    }
}


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2009 年 7 月 3 日上午 9:00 GMT (差异)