C/C++实现中longjmp会取消哪些操作?

14
是否存在主要的C/C++实现,其中longjmp函数“展开”,即与自动存储对象的析构函数、__attribute__((__cleanup__(...)))、POSIX线程取消处理程序等相互作用,而不仅仅是恢复由setjmp保存的寄存器上下文?我特别关注具有此属性的POSIX实现(或非POSIX但类似于POSIX的系统),但一般情况下也很有趣。
对于赏金,我正在寻找符合POSIX标准或至少类似于POSIX的系统,而不是已经提到的Windows。

16
我非常怀疑这件事。 - Lightness Races in Orbit
4
没问题,您的最爱MSVC++已经实现了它。 - Hans Passant
7
@Christophe。但http://msdn.microsoft.com/en-us/library/yz2ez4as.aspx说如果你使用了`/EH`编译选项,它会调用析构函数。 - rici
4
我建议你抛出一个异常,在你想停止执行的地方捕获它。 - GingerPlusPlus
5
明确一下,您并不关心行为是否合法,您只关心这样的实现是否存在?因为C++11规定,在代码中使用setjmp/longjmp,在此过程中如果需要调用非平凡析构函数会导致未定义行为。 - jxh
显示剩余16条评论
2个回答

4
我正在尝试理解此处想要实现的逻辑目标。
setjmp(3)手册页面说明:
setjmp()将堆栈上下文/环境保存在 env 中,以备 longjmp(3) 后续使用。如果调用 setjmp() 的函数返回,则堆栈上下文将失效。
这意味着,如果从调用 setjmp() 的堆栈上下文中返回,您将不能再次 longjmp 返回到该上下文,否则会导致未定义的行为。
因此,我认为在进行有效的 longjmp 调用时,setjmp 必须在当前堆栈上下文中的某个位置。因此,将堆栈展开并调用所有自动变量的析构函数等的 longjmp 看起来在逻辑上等同于在最初进行 setjmp() 调用的点抛出异常,并在那里捕获它。
那么抛出和捕获异常与您所期望的 setjmp/longjmp 语义有何不同呢?假设您拥有所期望的 setjmp/longjmp 实现,那么将其替换为常规的 try/throw,并捕获所抛出的异常有何不同?
我唯一能看到的区别是由 try/catch 块引入的额外内部作用域;而 setjmp 实际上并没有打开新的内部作用域。
因此,答案似乎很简单:每个符合标准的 C++ 实现都有 setjmp/longjmp 的等效实现,其具有所需的语义。它称为 try/throw/catch。

我的兴趣不在于是否有类似或“等效”的longjmp可以解开,而在于longjmp是否可以。请参见评论中迄今为止的讨论。 - R.. GitHub STOP HELPING ICE
不,POSIX 定义的最接近取消的东西类似于线程取消,并且当您使用 longjmp 绕过清理上下文时会明确地未定义发生了什么。因此,个别实现可能会将其保留为未定义状态,或者尝试定义在这种情况下会发生什么行为。当然,从 POSIX 的角度来看,C++ 甚至不存在,因此 POSIX 对 C++ 异常的交互没有任何意见。 - R.. GitHub STOP HELPING ICE

0

Interix(SUA)默认情况下不调用析构函数,但在x86模式下有一个选项。

test.cc保存的测试程序如下:

#include <stdio.h>
#include <setjmp.h>

struct A {
  ~A() { puts("~A"); }
};

jmp_buf buf;

void f() {
  A a;
  longjmp(buf, 1);
}

int main() {
  if (setjmp (buf))
    return 0;

  f();
}

以下是Interix的行为方式。为了简洁起见,我省略了PATH的必要设置。

$ cc -mx86 test.cc && ./a.out
$ cc -mx86 -X /EHa test.cc && ./a.out
cl:命令行警告D9025:用'/EHa'覆盖'/EHs'
~A
$ cc -mamd64 test.cc && ./a.out
$ cc -mamd64 -X /EHa test.cc && ./a.out
cl:命令行警告D9025:用'/EHa'覆盖'/EHs'
$ 

评论表明cc -X /EHa不符合POSIX标准,例如因为/EHa会捕获信号。这并非完全正确:

$ cat test.cc
#include <signal.h>
int main() {
  try {
    raise(SIGFPE);
  } catch (...) {
    // ignore
  }
}
$ cc -mx86 -X /EHa test.cc && ./a.out
cl:命令行警告D9025:用'/EHa'覆盖'/EHs'
Floating point exception (core dumped)
如果我将raise(SIGFPE)更改为除以零,我确实看到异常处理程序捕获它,但是POSIX和C++都不需要任何特定的行为,因此这不会影响符合性。也不是所有异步信号都被捕获:对于此程序:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void sigint(int signal) {
  puts("sigint");
  exit(0);
}
int main() {
  signal(SIGINT, sigint);
  try {
    for (;;) ;
  } catch (...) {
    // ignore
  }
}

“sigint” 在按下Ctrl-C后会被打印出来,正如预期的那样。我没有看到任何理由声称此实现不符合POSIX要求。

3
一枚硬币总有两面。系统必须支持 POSIX 规范,程序也必须使用它。但是对于赏金而言,R..正在寻找一个符合 POSIX 标准(第一属性)并能在取消时调用析构函数(第二属性)的实现。当然,Interix 支持这两个属性,但是如果你以不支持第一属性的方式编译程序,那么就只能支持第二个属性。所以我认为这很有问题。顺便说一下,即使微软也警告用户不要在移植性程序中使用 SEH 和 /EHa - Stefan Weiser
4
虽然我没有相关链接,但这可以轻易证明。如果使用/EHa编译不会破坏POSIX信号,那么以下程序将具有一个非0的退出码,因为有一个致命错误,就像在CentOS上一样:http://ideone.com/HpOmzf。这是因为POSIX定义,当发生未处理的信号时,退出码被设置为一个非0值。使用`/EHa`编译它将返回0,因为每个异常都被捕获了(包括除以0异常),正如此处所述:http://msdn.microsoft.com/en-us/library/1deeycx5.aspx。 - Stefan Weiser
2
@StefanWeiser:该程序调用了未定义的行为,因此POSIX对其所做的事情没有任何说明。如果您将其更改为raise(SIGFPE),是否会发生相同的事情? - R.. GitHub STOP HELPING ICE
2
@StefanWeiser,正如R..所指出的那样,你的程序行为是未定义的,即使它被定义了,也不能证明你的观点。 POSIX允许catch(...)对引发的信号产生影响,仅仅是因为它根本没有提到C++。(我还没有测试任何东西。) - user743382
2
@hvd,@R..:我猜我们对兼容性的说法不一样。在我看来,如果一个程序在每个POSIX兼容系统上都表现出相同的行为,那么它就是POSIX兼容的。你可以用任何其他信号替换SIGFPE,这也是MS抛出并捕获的。顺便说一句,只要它不是由raisekill引发的,行为就是未定义的。所以我可以使用raise更新程序...结果相同... - Stefan Weiser
显示剩余20条评论

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