longjmp/setjmp跨平台差异:Windows异常Linux正常运行问题
setjmp/longjmp跨平台行为差异:Windows触发异常的原因
先看你提供的代码:
#include <csetjmp> #include <iostream> using namespace std; jmp_buf jb1; jmp_buf jb2; auto bar() { if (setjmp(jb2) == 0) { longjmp(jb1, 1); } else { longjmp(jb1, 2); } } void foo() { bar(); } void testjmp() { auto i = setjmp(jb1); if (i == 0) { foo(); } else { cout << "i = " << i << endl; } i = setjmp(jb1); if (i == 0) { longjmp(jb2, -1); } else { cout << "i = " << i << endl; } } int main() { testjmp(); return 0; }
这个代码在Linux能正常运行,但Windows上触发异常,核心问题在于两个平台对longjmp的安全检查逻辑不一样,而你的代码触发了C标准里的未定义行为。
具体拆解执行流程和问题点:
- 第一次进入
testjmp(),调用setjmp(jb1)保存当前栈帧,返回0后调用foo()。 foo()调用bar(),bar()里调用setjmp(jb2)保存自己的栈帧,返回0后立刻longjmp(jb1,1)跳回testjmp()的第一个setjmp点。- 这时候
bar()和foo()的函数已经执行完毕,它们的栈帧早就被销毁(弹出调用栈)了。 - 接下来
testjmp()里再次调用setjmp(jb1),返回0后尝试longjmp(jb2, -1)——这里就是关键:jb2保存的是已经被销毁的bar()的栈帧,相当于要跳回一个不存在的栈空间里。
对于这种情况:
- Linux的glibc实现比较宽松,允许这种未定义行为的执行(虽然结果不可靠,但不会直接崩溃);
- 而Windows的MSVC runtime会严格检查
longjmp的目标栈帧有效性,一旦发现要跳转到已经被弹出的栈帧,就会直接触发异常,避免程序执行到非法内存区域。
本质上,你的代码违反了setjmp/longjmp的核心使用规则:longjmp只能跳转到当前调用栈中仍然有效的setjmp点,也就是保存jmp_buf的函数栈帧还未被销毁的时候。
内容的提问来源于stack exchange,提问作者l0n0l




