编译为 C++ 与 C 时,GCC 代码生成的巨大差异

Big differences in GCC code generation when compiling as C++ vs C

我一直在玩 x86-64 汇编,试图了解更多可用的各种 SIMD 扩展(MMX、SSE、AVX)。

为了查看 GCC 如何将不同的 C 或 C++ 结构转换为机器代码,我一直在使用 Compiler Explorer,这是一个极好的工具。

在我的一个 'play sessions' 期间,我想看看 GCC 如何优化整数数组的简单 运行 时间初始化。在这种情况下,我尝试将数字 0 到 2047 写入 2048 个无符号整数数组。

代码如下所示:

unsigned int buffer[2048];

void setup()
{
  for (unsigned int i = 0; i < 2048; ++i)
  {
    buffer[i] = i;
  }
}

如果我启用优化和 AVX-512 指令 -O3 -mavx512f -mtune=intel GCC 6.3 会生成一些非常聪明的代码:)

setup():
        mov     eax, OFFSET FLAT:buffer
        mov     edx, OFFSET FLAT:buffer+8192
        vmovdqa64       zmm0, ZMMWORD PTR .LC0[rip]
        vmovdqa64       zmm1, ZMMWORD PTR .LC1[rip]
.L2:
        vmovdqa64       ZMMWORD PTR [rax], zmm0
        add     rax, 64
        cmp     rdx, rax
        vpaddd  zmm0, zmm0, zmm1
        jne     .L2
        ret
buffer:
        .zero   8192
.LC0:
        .long   0
        .long   1
        .long   2
        .long   3
        .long   4
        .long   5
        .long   6
        .long   7
        .long   8
        .long   9
        .long   10
        .long   11
        .long   12
        .long   13
        .long   14
        .long   15
.LC1:
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16
        .long   16

但是,当我测试如果使用 GCC C 编译器通过添加标志 -x c 编译相同的代码会生成什么时,我真的很惊讶。

我期望类似的结果,如果不完全相同,但 C 编译器似乎生成 更复杂并且可能也慢得多的机器代码。生成的程序集太大,无法完整粘贴到此处,但可以通过以下 this link.

在 godbolt.org 查看

生成的代码片段,第 58 行到第 83 行,如下所示:

.L2:
        vpbroadcastd    zmm0, r8d
        lea     rsi, buffer[0+rcx*4]
        vmovdqa64       zmm1, ZMMWORD PTR .LC1[rip]
        vpaddd  zmm0, zmm0, ZMMWORD PTR .LC0[rip]
        xor     ecx, ecx
.L4:
        add     ecx, 1
        add     rsi, 64
        vmovdqa64       ZMMWORD PTR [rsi-64], zmm0
        cmp     ecx, edi
        vpaddd  zmm0, zmm0, zmm1
        jb      .L4
        sub     edx, r10d
        cmp     r9d, r10d
        lea     eax, [r8+r10]
        je      .L1
        mov     ecx, eax
        cmp     edx, 1
        mov     DWORD PTR buffer[0+rcx*4], eax
        lea     ecx, [rax+1]
        je      .L1
        mov     esi, ecx
        cmp     edx, 2
        mov     DWORD PTR buffer[0+rsi*4], ecx
        lea     ecx, [rax+2]

如您所见,这段代码有很多复杂的移动和跳跃,总的来说感觉像是执行简单数组初始化的一种非常复杂的方法。

为什么生成的代码差别这么大?

与 C 编译器相比,GCC C++ 编译器在优化在 C 和 C++ 中均有效的代码方面是否总体上更好?

额外的代码用于处理错位,因为使用的指令 vmovdqa64 需要 64 字节对齐。

我的测试表明,即使标准没有,gcc 也允许另一个模块中的定义在 C 模式下覆盖此处的定义。该定义可能仅符合基本对齐要求(4 字节),因此编译器不能依赖更大的对齐。从技术上讲,gcc 为这个暂定定义发出一个 .comm 汇编指令,而外部定义在 .data 部分使用普通符号。在链接期间,此符号优先于 .comm 符号。

请注意,如果您将程序更改为使用 extern unsigned int buffer[2048];,那么即使是 C++ 版本也会添加代码。反之,设为 static unsigned int buffer[2048]; 会将 C 版本变为优化版本。