为什么 GCC 包含一个“空”的 XOR

1 c assembly gcc x86-64 undefined-behavior

我有以下一段代码:

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

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

        return v;
}
Run Code Online (Sandbox Code Playgroud)

生成(gcc -O2):

f:
        xor     eax, eax
        xor     edx, edx          ; this line is questionable
        ret
Run Code Online (Sandbox Code Playgroud)

其中 clang 输出(clang -O2):

f:                                      # @f
        xor     eax, eax
        ret
Run Code Online (Sandbox Code Playgroud)

问题

  • 有没有理由GCC插入XOR那里?
  • 如果没有充分的理由:我可以以某种方式摆脱它吗?

笔记

Pet*_*des 8

您读取了一个部分未初始化的 struct 对象以返回它,即使调用者不使用返回值,它也(可以说是)当场未定义行为。

16 字节结构在 x86-64 System V ABI 中的 RDX:RAX 中返回(任何更大的结构,它将通过让调用者传递指向返回值对象的指针来返回)。 GCC 将未初始化的部分归零,clang 将留下任何垃圾。

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


您所看到的最简单的形式是返回一个未初始化的 plainint
GCC 和 clang 代码生成之间的区别相同:

int foo(){
    int x;
    return x;
}
Run Code Online (Sandbox Code Playgroud)

神马

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


# GCC 10.2 -O2
foo:
        xor     eax, eax        # return 0
        ret
Run Code Online (Sandbox Code Playgroud)

这里 clang "gets to" 省略了整个指令。当然,这是未定义的行为(读取未初始化的对象),因此该标准实际上允许任何事情,包括ud2(保证引发非法指令异常),或者甚至忽略ret此代码路径无法访问的假设,即函数永远不会叫做。或者返回0xdeadbeef,或者调用任何其他函数,如果你有一个恶意的 DeathStation 9000 C 实现。