10

我基本上确信我遇到了一些 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 则不会?如代码中的注释所示,有关使结构成功编译的其他方法的任何见解?或者这是否指向编译器错误?

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

4

2 回答 2

5

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

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

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

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

于 2015-01-06T19:14:37.113 回答
0

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

  1. struct代替int
  2. 不使用memset(我承认我不明白这会使事情变得更糟)
  3. 循环迭代两次以上——如果只迭代两次,编译器将展开循环
  4. -fPIC命令行选项(这会产生与位置无关的代码)

只有当所有这四个因素都存在时,编译器才会产生警告。似乎它们构成了优化者的完美风暴,它有一种神经衰弱(见下文)。如果缺少这些因素中的任何一个,编译器只会将所有内容优化为无,因此它可以忽略setjmp.

这是否是一个错误还有待商榷——代码大概仍然有效(尽管我还没有测试过)。但无论如何,这个问题似乎已经在 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
于 2015-01-06T19:16:38.910 回答