首页 文章

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

提问于
浏览
8
#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 回答

  • 5

    规则是调用setjmp的堆栈帧中的任何非易失性非静态局部变量可能会被对longjmp的调用破坏 . 处理它的最简单方法是确保调用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,那么你也可以 .

    Edit

    来自setjmp的C99规范的确切引用是:

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

  • 2

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

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

  • -1

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

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

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

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

  • 20

    快速回答:删除-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):

    #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

相关问题