#include <setjmp.h>
#include <vector>
int main(int argc, char**) {
std::vector<int> foo(argc);
jmp_buf env;
if (setjmp(env)) return 1;
}
用GCC 4.4.1编译上面的代码,g test.cc -Wextra -O1,给出了这个令人困惑的警告:
/usr/include/c++/4.4/bits/stl_vector.h: In function ‘int main(int, char**)’:
/usr/include/c++/4.4/bits/stl_vector.h:1035: warning: variable ‘__first’ might be clobbered by ‘longjmp’ or ‘vfork’
stl_vector.h的第1035行是在构造foo时调用的vector(n,value)构造函数使用的辅助函数中 . 如果编译器可以找出参数值(例如它是一个数字文字),警告就会消失,所以我在这个测试用例中使用了argc,因为编译器无法确定它的值 .
我猜这个警告可能是因为编译器优化了向量构造,所以它实际上发生在setjmp着陆点之后(当构造函数参数依赖于函数的参数时,这似乎就是这种情况) .
我怎样才能避免这个问题,最好不必将setjmp部分分解为另一个函数?
不使用setjmp不是一个选项,因为我遇到了一堆需要使用它来进行错误处理的C库 .
4 回答
规则是调用setjmp的堆栈帧中的任何非易失性非静态局部变量可能会被对longjmp的调用破坏 . 处理它的最简单方法是确保调用setjmp的帧不会调用setjmp:
另请注意,在此上下文中, clobbering 实际上只是意味着重置为调用setjmp时的值 . 因此,如果在修改本地之后永远不能调用longjmp,那么你也可以 .
Edit
来自setjmp的C99规范的确切引用是:
这不是一个你应该忽略的警告,longjmp()和C对象彼此不相处 . 问题是编译器会自动为你的foo对象发出析构函数调用 . longjmp()可以绕过析构函数调用 .
C异常也展开堆栈帧,但它们保证将调用本地对象的析构函数 . 没有来自longjmp()的保证 . 找出longjmp()是否要转换为byte需要仔细分析每个函数中可能由于longjmp()而提前终止的局部变量 . 这并不容易 .
正如错误消息中的行号1035所证明的那样,您的代码段大大简化了实际的问题代码 . 你走得太远了 . 关于你如何使用'第一'没有任何线索 . 问题是编译器即使在实际代码中也无法解决这个问题 . 害怕'setjmp'非零回报后'first'的值可能不是你想象的那样 . 这是因为您在第一次调用(零返回)之前和之后都将其值更改为“setjmp” . 如果变量存储在寄存器中,则该值可能与存储在内存中的值不同 . 所以编译器通过给你警告是保守的 .
要盲目跳跃并回答问题,您可以通过使用'volatile'对'first'的声明进行限定来摆脱警告信息 . 你也可以尝试制作'第一'全球 . 也许通过删除优化级别(-O标志),可能会导致编译器将变量保留在内存中 . 这些都是快速修复,实际上可能隐藏了一个错误 .
你应该真正看看你的代码,以及你如何使用'第一' . 我会采取另一种猜测,并说你可能能够消除这个变量 . 这个名字,'first',是否意味着你用它来表示'setjmp'的第一次调用(零返回)?如果是这样,摆脱它 - 重新设计你的逻辑 .
如果真实代码只是从'setjmp'返回非零返回(如在代码片段中那样),那么'first'的值在该逻辑路径中无关紧要 . 不要在'setjmp'的两侧使用它 .
快速回答:删除-O1标志或将编译器退回到早期版本 . 任何一个警告都会在我的系统上消失 . 我必须首先构建并使用gcc4.4来获取警告 . (这是一个庞大的系统)
没有?我以为不是 .
我真的不明白C对它的对象做了什么,以及它们是如何解除分配的 . OP的评论是,如果使用恒定值,问题就不会发生'argc'的矢量大小让我有机会伸出脖子 . 我猜测只有当初始分配不是常量时,C才会在释放时使用'__first'指针 . 在更高级别的优化中,编译器更多地使用寄存器,并且在set_mp之前和之后的分配之间存在冲突......我不知道,这没有任何意义 .
这个警告的一般含义是“你确定你知道你在做什么吗?”当你执行longjmp时,编译器不知道你是否知道'__first'的值是什么,并且从'setjmp'获得非零返回 . 问题是它(非零)返回后的值是否是放入保存缓冲区的值,或者是保存后创建的值 . 在这种情况下,它是令人困惑的,因为你不知道你使用'__first',并且因为在这样一个简单的程序中,没有(显式)改变'__first'
编译器无法分析复杂程序中的逻辑流程,因此它显然甚至不会尝试任何程序 . 它允许您确实更改值 . 所以它只是给你一个友好的“单挑” . 编译器第二次猜测你,试图提供帮助 .
如果您对选择的编译器和优化感到顽固,那么就有一个编程修复程序 . 在分配矢量之前保存环境 . 将'setjmp'移动到程序的顶部 . 根据矢量使用和实际程序中的错误逻辑,这可能需要进行其他更改 .
edit 1/21 -------
我的理由(使用g -mp-4.4 -Wextra -O1 main.cpp):
没有警告; a.out出品:
从0 0开始
从1 1开始
从2 1开始
从3 1开始
从4 1开始
结束4 1