为什么 GCC 包含 "empty" XOR

Why does GCC include an "empty" XOR

我有以下代码:

typedef struct {
        int x;
        int y;
        int z;
        int w;
} s32x4;

s32x4
f() {
        s32x4 v;
        v.x = 0

        return v;
}

生成 (gcc -O2):

f:
        xor     eax, eax
        xor     edx, edx          ; this line is questionable
        ret

其中 clang 输出 (clang -O2):

f:                                      # @f
        xor     eax, eax
        ret

问题

备注

你读取了一个部分未初始化的结构对象到return它,这是当场的()未定义行为,即使调用者没有使用return值.

16 字节的结构在 x86-64 System V ABI 的 RDX:RAX 中被 return 编辑(任何更大的结构都可以通过让调用者传递一个指向 return 值对象的指针)。 GCC 正在将未初始化的部分归零,clang 正在留下那里的所有垃圾。

GCC 喜欢在任何时候打破依赖关系,因为可能存在将错误的依赖关系耦合到某些东西中的风险。 (例如 pxor xmm0,xmm0 在使用 之前 cvtsi2sd xmm0, eax)。 Clang 在将其排除在外时更加“积极”,有时即使这样做的代码大小好处很小,例如使用 mov al, 1 而不是 mov eax,1,或 mov al, [rdi] 而不是 movzx eax, byte ptr [rdi])


您所看到的最简单形式是 returning 一个未初始化的纯文本 int,
GCC 和 clang code-gen 之间的相同区别:

int foo(){
    int x;
    return x;
}

(Godbolt)

# clang 11.0.1 -O2
foo:
        # leaving EAX unwritten
        ret


# GCC 10.2 -O2
foo:
        xor     eax, eax        # return 0
        ret

这里 clang “开始”省略了整个指令。当然,这是未定义的行为(读取未初始化的对象),因此标准实际上允许任何内容,包括 ud2 (保证引发非法指令异常),或者甚至省略 ret 假设此代码-path 不可访问,即永远不会调用该函数。或 return 0xdeadbeef,或调用任何其他函数,如果您有恶意的 DeathStation 9000 C 实现。

在标准定义了使用未初始化自动变量的值的程序的行为的情况下,处理某些极端情况的最简单方法是将此类值初始化为零。这样做的编译器将避免任何其他极端情况处理的需要。

例如,考虑一下:

#include <string.h>
extern unsigned short volatile vv;
int test(int a, int mode)
{
    unsigned short x,y;

    if (mode)
        x=vv;
    memcpy(&y,&x,sizeof x);
    return y;
}

应在使用 32 位寄存器保存 32 位及更小整数类型的所有自动对象的平台上处理。如果 mode 为零,此函数应将两个未指定的字节值复制到包含 y 和 return 的字节中,使其保存 0-65535 范围内的任意数字。然而,在 ARM GCC 4.5.4 上,此函数将使用 R0 寄存器来保存 x 和 y,而无需在 'mode==0' 情况下写入该寄存器。这将导致 y 表现得好像它包含作为第一个参数传递的任何内容,即使该值超出范围 0-65535 也是如此。更高版本通过将 R0 预初始化为零(当然始终在 0-65535 范围内)来避免此问题。

我不确定 gcc 在 OP 的示例中将事物置零的决定是否是试图抢占可能会出现问题的极端情况的结果,但肯定是在某些情况下它会在不需要的情况下将事物预先置零标准似乎源于这样的目标。