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

inconsistent warning: variable might be clobbered by ‘longjmp’ or ‘vfork’

本文关键字:longjmp 破坏 vfork 警告 变量 不一致      更新时间:2023-10-16

我基本上说服自己遇到了一些 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,则不会被破坏? 关于使用结构体成功编译的其他方法的任何见解,如代码中的注释所示? 或者这指向编译器错误?

任何见解和意见将不胜感激。

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

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

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

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

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

  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