不一致的警告:变量可能被'longjmp'或'vfork'破坏

时间:2015-01-06 16:27:55

标签: c++ g++4.8 longjmp setjmp

我大部分时间都相信我遇到了一些g ++ 4.8.3错误,但我想我会先问这个列表,因为我对setjmp / longjmp的经验很少。我将我的代码简化为以下foo.cxx:

#include <setjmp.h>
#include <string.h>

// Changing MyStruct to be just a single int makes the compiler happy.
struct MyStruct
{
    int a;
    int b;
};

// Setting MyType to int makes the compiler happy.
#ifdef USE_STRUCT
typedef MyStruct MyType;
#elif USE_INT
typedef int MyType;
#endif

void SomeFunc(MyType val)
{
}

static void static_func(MyType val)
{
    SomeFunc(val);
}

int main(int argc, char **argv)
{
    jmp_buf env;
    if (setjmp(env))
    {
        return 1;
    }

    MyType val;
#ifdef USE_STRUCT
    val.a = val.b = 0;
#elif USE_INT
    val = 0;
#endif
    // Enabling the below memset call makes the compiler happy.
    //memset(&val, 0, sizeof(val));

    // Iterating 1 or 2 times makes the compiler happy.
    for (unsigned i = 0; i < 3; i++)
    {
        // calling SomeFunc() directly makes the compiler happy.
        static_func(val);
    }
    return 0;
}

我使用g ++ 4.8.3编译此代码。对我来说有趣的是,当我定义USE_STRUCT时,编译失败但成功使用USE_INT。代码中有注释进一步表明如何使用USE_STRUCT使编译成功。编译仅在g ++的-fPIC选项中失败,但这是我环境中的必需参数。

要查看编译错误:

g++ -DUSE_STRUCT -Wextra -Wno-unused-parameter -O3 -Werror -fPIC foo.cxx
foo.cxx: In function ‘int main(int, char**)’:
foo.cxx:26:5: error: variable ‘val’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered]

但是使用简单的int就可以了:

g++ -DUSE_INT -Wextra -Wno-unused-parameter -O3 -Werror -fPIC foo.cxx

有人可以向我解释为什么val可能会被破坏,如果它是一个结构但不是如果它是一个int?如编码中的注释所示,对结构进行编译的其他方法的任何见解都会成功吗?或者这是否指向编译器错误?

非常感谢任何见解和评论。

2 个答案:

答案 0 :(得分:3)

setjmp()保存当前堆栈。由于它是在val声明之前调用的,因此该变量不在保存的堆栈中。

setjmp()之后,变量被初始化,如果代码稍后跳回setjmp点,变量将再次初始化,破坏旧变量。如果应该在旧实例上调用一个非平凡的析构函数,那么这是未定义的行为(§18.10/ 4):

  

setjmp / longjmp调用对具有未定义的行为,如果将setjmplongjmp替换为catchthrow会调用任何非 - 任何自动对象的通用析构函数。

可能不会调用旧实例的析构函数。我的猜测是gcc不会警告原始类型,因为它们没有析构函数,但警告更复杂的类型,这可能会有问题。

答案 1 :(得分:0)

这里有几个因素在起作用:

  1. struct代替int
  2. 不使用memset(我承认我不明白这会让事情变得更糟)
  3. 迭代循环两次以上 - 如果只迭代两次,编译器将展开循环
  4. -fPIC命令行选项(这会生成与位置无关的代码)
  5. 只有当所有这四个因素都存在时,编译器才会发出警告。对于优化者而言,它们似乎构成了一场完美的风暴,它有一种精神崩溃(见下文)。如果没有这些因素中的任何一个,编译器只会将所有内容优化为零,所以它可以忽略setjmp

    这是否是一个bug是值得怀疑的 - 代码可能仍然有用(尽管我还没有测试过)。但无论如何,问题似乎已在4.9版本中修复,因此显而易见的解决方案是升级。

    这是机器代码(NSFW):

    SomeFunc(MyStruct):
        rep; ret
    main:
        pushq   %r12
        pushq   %rbp
        pushq   %rbx
        subq    $224, %rsp
        leaq    16(%rsp), %rdi
        call    _setjmp@PLT
        testl   %eax, %eax
        movl    %eax, %ebp
        jne .L5
        movl    $3, %ebx
        movabsq $-4294967296, %r12
    .L4:
        movq    8(%rsp), %rdx
        andq    %r12, %rdx
        movl    %edx, %eax
        movq    %rax, %rdi
        movq    %rax, 8(%rsp)
        call    SomeFunc(MyStruct)@PLT
        subl    $1, %ebx
        jne .L4
    .L3:
        addq    $224, %rsp
        movl    %ebp, %eax
        popq    %rbx
        popq    %rbp
        popq    %r12
        ret
    .L5:
        movl    $1, %ebp
        jmp .L3