是否存在longjmp
函数“展开”的主要 C/C++ 实现,即它与自动存储对象__attribute__((__cleanup__(...)))
、POSIX 线程取消处理程序等的析构函数交互,而不仅仅是恢复由 保存的寄存器上下文setjmp
?我对具有此属性的 POSIX 实现的存在(或不存在)特别感兴趣,但 C/C++ 通常也很有趣。
对于赏金,我正在寻找符合 POSIX 或至少类似 POSIX 的系统,而不是已经提到的 Windows。
是否存在longjmp
函数“展开”的主要 C/C++ 实现,即它与自动存储对象__attribute__((__cleanup__(...)))
、POSIX 线程取消处理程序等的析构函数交互,而不仅仅是恢复由 保存的寄存器上下文setjmp
?我对具有此属性的 POSIX 实现的存在(或不存在)特别感兴趣,但 C/C++ 通常也很有趣。
对于赏金,我正在寻找符合 POSIX 或至少类似 POSIX 的系统,而不是已经提到的 Windows。
我试图理解这里试图实现的逻辑目标。
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 等效项。它被称为尝试/抛出/捕获。
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” 〜一个 $ cc -mamd64 test.cc && ./a.out $ cc -mamd64 -X /EHa test.cc && ./a.out cl:命令行警告 D9025:用“/EHa”覆盖“/EHs” $
评论表明cc -X /EHa
不符合 POSIX,例如因为/EHa
会捕获信号。这并不完全正确:
$猫测试.cc #include <信号.h> int main() { 尝试 { 提高(SIGFPE); } 抓住 (...) { // 忽视 } } $ cc -mx86 -X /EHa test.cc && ./a.out cl:命令行警告 D9025:用“/EHa”覆盖“/EHs” 浮点异常(核心转储)
如果我将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 要求。