C++ 不一致警告:变量可能被‘;longjmp’;或‘;vWork’;

C++ 不一致警告:变量可能被‘;longjmp’;或‘;vWork’;,c++,g++4.8,setjmp,C++,G++4.8,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; };

我几乎确信我遇到了一些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;
}
foo.cxx:在函数“int main(int,char**)”中:
foo.cxx:26:5:错误:变量“val”可能被“longjmp”或“vfork”删除[-Werror=clobbered]
但使用简单的int也可以:

g++-DUSE_INT-Wextra-Wno未使用的参数-O3-Werror-fPIC foo.cxx
有人能给我解释一下,如果val是一个struct,它为什么会被删除,而如果它是int,它就不会被删除?如代码中的注释所示,您对使用struct成功编译的其他方法有何见解?或者这是指向一个编译器错误

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

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

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

如果将
setjmp
longjmp
替换为
catch
throw
将调用任何自动对象的非平凡析构函数,则
setjmp
调用对具有未定义的行为


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

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

  • struct
    而不是
    int
  • 不使用
    memset
    (我承认我不明白这怎么会让事情变得更糟)
  • 循环迭代两次以上——如果只迭代两次,编译器将展开循环
  • -fPIC
    命令行选项(这将生成位置独立代码)
  • 只有当这四个因素都存在时,编译器才会产生警告。对于乐观主义者来说,这似乎是一场完美的风暴,他们有一种神经崩溃(见下文)。如果缺少这些因素中的任何一个,编译器只会将所有内容优化为零,因此它可以忽略
    setjmp

    这是否是一个bug还有待商榷——代码可能仍然有效(尽管我还没有测试它)。但无论如何,问题似乎已经在4.9版中得到了解决,因此显而易见的解决方案是升级

    以下是机器代码(NSFW):


    通过
    setjmp
    etc进行攻击可能与在寄存器中有关。关于Basile的评论,打开可能是这个bug,如果降低优化级别会发生什么?您是否检查过编译器生成的程序集(甚至中间)代码?它可能会给你一个提示,告诉你发生了什么。任何优化级别的编译都会失败。关闭优化,编译器会很高兴。我还没有看过任何程序集。我不能用GCC4.9.1复制它,所以它可能会被修复(或者被干扰)。这不是你的例子,你可能已经知道,但只是为了记录:退出一个范围,需要通过代码不冗长的堆栈解开通过代码> LojJMP < /C> >调用未定义的行为在C++中。但是OP的<代码> MyStult是POD类型,所以没有初始化。执行行
    MyStruct val后立即执行、<代码> Val.A<代码>和<代码> Val.B./C> >在“<代码> > LojJMP 。@ 5Gun12EdE:Trtrue之后不再定义。但是,您必须考虑编译器对警告是否正确有多大的困难。
    
    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