3

jpeglib中,必须使用 setjmp/longjmp 来实现自定义错误处理。

有很多资源说 setjmp/longjmp 不能很好地与 c++ 配合使用(例如,这个问题中的答案告诉他们确实与 RAII 一起使用),但是这个问题的答案说调用了析构函数。

我有这个例子(取自这里并稍作修改):

#include <iostream>
#include <csetjmp>

std::jmp_buf jump_buffer;

struct A
{
    A(){std::cout<<"A()"<<std::endl;}
    ~A(){std::cout<<"~A()"<<std::endl;}
};

void a(int count) 
{
    std::cout << "a(" << count << ") called\n";
    std::longjmp(jump_buffer, count+1);  // setjump() will return count+1
}

int main()
{
    // is this object safely destroyed?
    A obj;

    int count = setjmp(jump_buffer);
    if (count != 9) {
        a(count);
    }
}

在这个例子中,析构函数被调用(如我所料),但它是标准行为吗?还是编译器的扩展,还是简单的 UB?


输出:

A()
a(0) called
a(1) called
a(2) called
a(3) called
a(4) called
a(5) called
a(6) called
a(7) called
a(8) called
~A()
4

1 回答 1

7

可能是未定义的行为,具体取决于是否调用析构函数作为执行相同控制转移的异常。在 C++03 中。从部分18.7 Other runtime supportparagraph 4

函数签名longjmp(jmp_buf jbuf, int val)在本国际标准中具有更多限制行为。如果任何自动对象将被程序中将控制权转移到另一个(目标)点的抛出异常破坏,那么longjmp(jbuf, val)在将控制权转移到同一(目标)点的抛出点处的调用具有未定义的行为。

c++11中有类似的语言:

函数签名longjmp(jmp_buf jbuf, int val)在本国际标准中具有更多限制行为。如果将 and 替换为and ,setjmp/longjmp则调用对具有未定义的行为,并且将为任何自动对象调用任何非平凡的析构函数。setjmplongjmpcatchthrow

但是,对于这段特定的代码,似乎没有在转换中调用析构函数,所以我相信它是安全的。


现在,如果您要更改代码以将创建移动到之后setjmp这将成为未定义的行为。在我的设置(Debian下的gcc 4.4.5)中,以下代码(其他所有内容与您的问题相同):

int main() {
    int count = setjmp (jump_buffer);
    A obj;
    if (count != 4) a (count);
}

结果输出:

A()
a(0) called
A()
a(1) called
A()
a(2) called
A()
a(3) called
A()
~A()

并且您可以看到析构函数没有作为跳转的一部分被调用,尽管未定义,它可能在某些系统上。


底线是,您不应该从区域 A 跳转到区域 B,在该区域中等价物throw/catch会正确破坏对象,因为无法保证longjmp会调用析构函数。

实际上,有些人会说你根本不应该setjmp/longjmp在 C++中使用,而我自己也倾向于这样。即使在 C 语言中,我也很难看到对它的需求。

我想我在整个职业生涯中都使用过一次(这是一个漫长的职业生涯),在 MS-DOS 下的 Turbo C 中实现协作线程。我想不出我曾经使用过它的其他时间。并不是说没有任何用途,但它们非常罕见。

于 2013-04-29T06:37:30.403 回答