8
#include <setjmp.h>
#include <向量>

int main(int argc, char**) {
 std::vector<int> foo(argc);
 jmp_buf 环境;
 如果(setjmp(env))返回1;
}

使用 GCC 4.4.1 编译上述代码 g++ test.cc -Wextra -O1 会给出这个令人困惑的警告:

/usr/include/c++/4.4/bits/stl_vector.h:在函数'int main(int,char**)'中:
/usr/include/c++/4.4/bits/stl_vector.h:1035:警告:变量“__first”可能被“longjmp”或“vfork”破坏

stl_vector.h 的第 1035 行位于我在构造 foo 时调用的 vector(n, value) 构造函数使用的辅助函数中。如果编译器可以计算出参数值(例如,它是一个数字文字),警告就会消失,所以我在这个测试用例中使用 argc 因为编译器无法确定它的值。

我猜这个警告可能是因为编译器优化了向量构造,所以它实际上发生在 setjmp 着陆点之后(当构造函数参数依赖于函数的参数时,这似乎是这种情况)。

我怎样才能避免这个问题,最好不必将 setjmp 部分分解为另一个函数?

不使用 setjmp 不是一个选项,因为我被一堆需要使用它来处理错误的 C 库所困。

4

4 回答 4

23

规则是调用 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 函数时,除了包含调用相应 setjmp 的函数的本地自动存储持续时间的对象的值没有 volatile 限定类型并且在 setjmp 调用和 longjmp 调用之间发生更改的宏是不确定的。

于 2010-01-20T23:25:44.220 回答
5

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

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

于 2010-01-08T05:26:01.960 回答
2

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

要盲目地回答这个问题,您可以通过使用 'volatile' 限定 'first' 的声明来摆脱警告消息。您也可以尝试将“第一”设为全局。也许通过删除优化级别(-O 标志),您可以使编译器将变量保留在内存中。这些是快速修复,实际上可能隐藏了一个错误。

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

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

于 2010-01-08T04:19:46.427 回答
-1

快速回答:删除 -O1 标志或将编译器回归到早期版本。任何一个都使警告在我的系统上消失了。我必须首先构建和使用 gcc4.4 来获得警告。(该死的,这是一个巨大的系统)

不?我以为不是。

我真的不明白 C++ 对其对象所做的一切,以及它们是如何被释放的。然而 OP 的评论是,如果使用常量值代替向量大小的 'argc',则不会出现问题,这让我有机会伸出脖子。我会冒险猜测 C++ 仅在初始分配不是常量时才在释放时使用 '__first' 指针。在更高级别的优化中,编译器更多地使用寄存器,并且前后setjmp分配之间存在冲突......我不知道,这没有任何意义。

此警告的一般含义是“您确定您知道自己在做什么吗?” 编译器不知道您是否知道执行 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

于 2010-01-20T22:04:24.270 回答