使用setjmp在C ++对象上警告“可能会被破坏”

时间:2010-01-08 01:46:45

标签: c++ longjmp

#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 个答案:

答案 0 :(得分:20)

规则是调用setjmp 的堆栈帧中的任何非易失性非静态局部变量可能被longjmp调用破坏。处理它的最简单方法是确保调用setjmp的框架不包含您关心的任何此类变量。这通常可以通过将setjmp本身放入一个函数并将引用传递给另一个不调用setjmp的函数中的函数来完成:

#include <setjmp.h>
#include <vector>

int wrap_libcall(std::vector<int> &foo)
{
  jmp_buf env;
  // no other local vars
  if (setjmp(env)) return 1;
  // do stuff with your library that might call longjmp
  return 0;
}

int main(int argc, char**) { 
  std::vector<int> foo(argc);
  return wrap_libcall(foo);  
}

另请注意,在此上下文中, clobbering 实际上只意味着重置为调用setjmp时的值。因此,如果在修改本地之后永远不能调用longjmp,那么你也可以。

修改

关于setjmp的C99规范的确切引用是:

  

所有可访问的对象都具有值以及抽象机器的所有其他组件   有状态,截至调用longjmp函数时,除了值的   自动存储持续时间的对象是包含该函数的函数的本地对象   调用没有volatile限定类型的相应setjmp宏   并且在setjmp调用和longjmp调用之间进行了更改   不确定的。

答案 1 :(得分:5)

这不是一个你应该忽略的警告,longjmp()和C ++对象彼此不相处。问题是编译器会自动为你的foo对象发出析构函数调用。 longjmp()可以绕过析构函数调用。

C ++异常也展开堆栈帧,但它们保证将调用本地对象的析构函数。没有来自longjmp()的保证。找出longjmp()是否要转换为byte需要仔细分析每个函数中可能由于longjmp()而提前终止的局部变量。这不容易。

答案 2 :(得分:2)

正如错误消息中的行号1035所证明的那样,您的代码段大大简化了实际的问题代码。你走得太远了。关于你如何使用'第一'没有任何线索。问题是编译器即使在实际代码中也无法解决这个问题。害怕'setjmp'非零回报后'first'的值可能不是你想象的那样。这是因为您在第一次调用(零返回)之前和之后都将其值更改为“setjmp”。如果变量存储在寄存器中,则该值可能与存储在内存中的值不同。所以编译器通过给你警告是保守的。

要盲目跳跃并回答问题,您可以通过使用'volatile'对'first'的声明进行限定来摆脱警告信息。你也可以尝试制作'第一'全球。也许通过删除优化级别(-O标志),可能会导致编译器将变量保留在内存中。这些是快速修复,实际上可能隐藏了一个错误。

你应该真正看看你的代码,以及你如何使用'第一'。我会采取另一种猜测,并说你可能能够消除这个变量。这个名字,'first',是否意味着你用它来表示'setjmp'的第一次调用(零返回)?如果是这样,摆脱它 - 重新设计你的逻辑。

如果真实代码只是从'setjmp'返回非零返回(如在代码片段中),那么'first'的值在该逻辑路径中无关紧要。不要在'setjmp'的两侧使用它。

答案 3 :(得分:-1)

快速回答:删除-O1标志或将编译器退回到早期版本。任何一个警告都会在我的系统上消失。我必须首先构建并使用gcc4.4来获取警告。 (认为​​这是一个庞大的系统)

没有?我没想过。

我真的不明白C ++对其对象所做的一切,以及它们是如何解除分配的。 OP的评论是,如果使用常数值代替矢量大小的'argc',问题就不会发生,这让我有机会坚持下去。我猜测只有当初始分配不是常量时,C ++才会在释放时使用'__first'指针。在更高级别的优化中,编译器更多地使用寄存器,并且在set_mp之前和之后的分配之间存在冲突......我不知道,这没有任何意义。

这个警告的一般含义是“你确定你知道你在做什么吗?”当你执行longjmp时,编译器不知道你是否知道'__first'的值是什么,并且从'setjmp'获得非零返回。问题是它(非零)返回后的值是否是放入保存缓冲区的值,或者是保存后创建的值。在这种情况下,它是令人困惑的,因为你不知道你使用'__first',因为在这样一个简单的程序中,没有(显式)改变'__first'

编译器无法分析复杂程序中的逻辑流程,因此它显然甚至不会尝试任何程序。它允许您确实更改值。所以它只是给你一个友好的“单挑”。编译器第二次猜测你,试图提供帮助。

如果您对选择的编译器和优化感到顽固,那么就有一个编程修复程序。在分配矢量之前保存环境。将'setjmp'移动到程序的顶部。根据矢量使用和实际程序中的错误逻辑,这可能需要进行其他更改。

编辑1/21 -------

我的理由(使用g ++ - mp-4.4 -Wextra -O1 main.cpp):

#include <setjmp.h>
#include <vector>
#include <iostream>

int main(int argc, char**) {
    jmp_buf env;
    int id = -1, idd = -2;

    if ((id=setjmp(env)))
        idd = 1;
    else 
        idd = 0;
    std::cout<<"Start with "<< id << " " << idd <<std::endl;
    std::vector<int> foo(argc );

    if(id != 4)
        longjmp(env, id+1);

    std::cout<<"End with "<< id << " " << idd <<std::endl;
}

没有警告; a.out出品:

从0 0开始 从1 1开始 从2 1开始 从3 1开始 从4 1开始 以4 1结束