代码之家  ›  专栏  ›  技术社区  ›  Tronic

用SETJMP警告C++对象的“可能被破坏”

  •  8
  • Tronic  · 技术社区  · 15 年前
    #include <setjmp.h>
    #include <vector>
    
    int main(int argc, char**) {
     std::vector<int> foo(argc);
     jmp_buf env;
     if (setjmp(env)) return 1;
    }
    

    使用g cc 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 回复  |  直到 15 年前
        1
  •  20
  •   Chris Dodd    15 年前

    规则是堆栈帧中调用setjmp的任何非易失性、非静态局部变量 可以 打电话给朗吉姆普让我大吃一惊。处理它的最简单方法是确保调用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);  
    }
    

    还注意到在这方面, 克劳巴林 实际上只是意味着重置为调用setjmp时的值。所以,如果longjmp在修改了一个本地文件之后永远无法调用,那么您也可以。

    编辑

    关于setjmp的c99规范的准确引用是:

    所有可访问的对象都有值,并且抽象机的所有其他组件都有值 have state,在调用longjmp函数时,除了 包含 调用不具有volatile限定类型的相应setjmp宏 并在setjmp调用和longjmp调用之间进行了更改 不确定。

        2
  •  5
  •   Hans Passant    15 年前

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

    C++异常也取消了堆栈帧,但它们保证将调用本地对象的析构函数。longjmp()没有这样的保证。要找出longjmp()是否会变成字节,需要仔细分析每个函数中的局部变量,这些变量可能由于longjmp()而提前终止。这不容易。

        3
  •  2
  •   gary    15 年前

    如错误消息中的行号1035所示,您的代码片段大大简化了实际的问题代码。你太过分了。你是如何使用“第一”的,这一点毫无头绪。问题是编译器甚至在实际的代码中也无法发现这一点。“setjmp”的非零返回之后的“first”值恐怕不是您所认为的值。这是因为您在第一次调用(零返回)之前和之后都将其值更改为“setjmp”。如果变量存储在寄存器中,则该值可能与存储在内存中的值不同。所以编译器给你一个警告是保守的。

    若要盲目跳跃并回答问题,您可以通过将“first”声明限定为“volatile”来消除警告消息。你也可以尝试让“第一”成为全球性的。也许通过删除优化级别(-o标志),可以使编译器将变量保留在内存中。这些都是快速修复,实际上可能隐藏了一个bug。

    你真的应该看看你的代码,以及你是如何使用'第一'。我再做一个大胆的猜测,并说你可以消除这个变量。这个名字“first”是否意味着您使用它来表示对“setjmp”的第一个调用(零返回)?如果是的话,摆脱它-重新设计你的逻辑。

    如果真正的代码只是在“setjmp”的非零返回上退出(如代码片段中所示),那么“first”的值在该逻辑路径中无关紧要。不要在“setjmp”的两边使用它。

        4
  •  -1
  •   gary    15 年前

    快速的答案是:删除-o1标志或将编译器还原到早期版本。任何一个都让我的系统上的警告消失了。我必须建立和使用GCC4.4在第一时间得到警告。(该死,这是一个庞大的系统)

    不?我想不是。

    我真的不理解C++对它的对象所做的一切,也不知道它们是如何被释放的。然而,op的评论说,如果使用一个常量来代替矢量大小的“argc”,问题就不会发生,这给了我一个伸出脖子的机会。我将冒昧地猜测,只有当初始分配不是常数时,C++才在“取消分配”时使用“第一个指针”。在更高的优化级别上,编译器更多地使用寄存器,并且在setjmp之前和之后的分配之间存在冲突…我不知道,这毫无意义。

    这个警告的一般含义是“你确定你知道你在做什么吗?”编译器不知道当您执行longjmp并从“setjmp”获得非零返回时,是否知道“uu first”的值。问题是(非零)返回后的值是放入保存缓冲区的值,还是保存后创建的值。在本例中,这很令人困惑,因为您不知道您使用的是'\uu first',并且因为在这样一个简单的程序中,没有(显式的)更改为'\uu 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开始
    从11开始
    从21开始
    从31开始
    从41开始
    以4 1结尾

    推荐文章