setjmp执行前创建的对象会被销毁吗?

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);
    }
}

在这个例子中,析构函数被调用了(正如我所预期的那样),但这是标准行为吗?还是编译器的扩展,或者只是未定义行为?
输出结果:
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()
1个回答

7
根据是否在执行相同的控制传递时会调用析构函数,这可能是未定义行为。在C++03中,从第18.7节“其他运行时支持”,第4段:

此国际标准中的函数签名longjmp(jmp_buf jbuf, int val)行为更加受限。如果通过抛出异常将控制转移至程序中的另一个(目的地)点,任何自动对象都将被销毁,则在转移控制到相同(目的地)点的抛出点处调用longjmp(jbuf, val)将具有未定义的行为。

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

在此国际标准中,函数签名longjmp(jmp_buf jbuf, int val)的行为更加受限。如果通过catch和throw替换setjmp和longjmp将为任何自动对象调用非平凡析构函数,则setjmp/longjmp调用对具有未定义行为。

但是,对于此特定代码段,似乎没有在过渡中调用任何析构函数,因此我认为它是安全的。


现在,如果您将创建移动到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会调用析构函数。

实际上,有些人会说在C++中根本不应该使用setjmp/longjmp,我自己也倾向于这种观点。即使在C中,我也很难看到需要这样做的情况。

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


但在这种情况下,没有任何自动对象会被异常销毁。 - john
是的,没有抛出异常。 - BЈовић
1
@BЈовић,无论你的特定环境是否工作,都不是UB的决定因素。有时UB正如你所期望的那样工作,但它仍然是UB,并且在其他人的环境中(或在蓝月亮期间,或使用不同的编译器选项等)可能完全不同。对于明确的信息,标准始终是控制性文件。 - paxdiablo
是的,它们是相关的,但是在负面意义上。如果您在当前的longjmp位置放置一个throw,并在当前的setjmp位置放置一个catch,则不会调用任何析构函数。只有在您在setjmplongjmp之间的某个地方创建了obj时才会出现问题。 - paxdiablo
1
@BЈовић,至少作为跳转的一部分,它不应该被销毁。当它超出范围时,在main结束时将被销毁。这在处理本地/块范围的“正常”标准部分中有所涵盖(C++03的3.3.2,C++11的3.3.3)。 - paxdiablo
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接