为什么GCC集合的数组初始化会首先用零填充整个东西,包括非零元素?

Las*_*sie 7 c++ x86 assembly gcc compiler-optimization

为什么gcc用零而不是仅剩余的96个整数填充整个数组?非零初始值设定项都在数组的开头。

void *sink;
void bar() {
    int a[100]{1,2,3,4};
    sink = a;             // a escapes the function
    asm("":::"memory");   // and compiler memory barrier
    // forces the compiler to materialize a[] in memory instead of optimizing away
}
Run Code Online (Sandbox Code Playgroud)

MinGW8.1和gcc9.2都使asm像这样(Godbolt编译器资源管理器)。

# gcc9.2 -O3 -m32 -mno-sse
bar():
    push    edi                       # save call-preserved EDI which rep stos uses
    xor     eax, eax                  # eax=0
    mov     ecx, 100                  # repeat-count = 100
    sub     esp, 400                  # reserve 400 bytes on the stack
    mov     edi, esp                  # dst for rep stos
        mov     DWORD PTR sink, esp       # sink = a
    rep stosd                         # memset(a, 0, 400) 

    mov     DWORD PTR [esp], 1        # then store the non-zero initializers
    mov     DWORD PTR [esp+4], 2      # over the zeroed part of the array
    mov     DWORD PTR [esp+8], 3
    mov     DWORD PTR [esp+12], 4
 # memory barrier empty asm statement is here.

    add     esp, 400                  # cleanup the stack
    pop     edi                       # and restore caller's EDI
    ret
Run Code Online (Sandbox Code Playgroud)

(启用SSE后,它将使用movdqa加载/存储功能复制所有4个初始化程序)

为什么GCC不能像Clang那样只对最后96个元素进行lea edi, [esp+16]memset和with rep stosd 这是错过的优化,还是以这种方式更有效?(C实际上调用memset而不是内联rep stos


编者注:该问题最初具有未优化的编译器输出,其工作方式相同,但at处的低效率代码-O0无法证明任何事情。但事实证明,即使在时,GCC也错过了此优化-O3

将指针传递a给非内联函数将是迫使编译器实现的另一种方式a[],但是在32位代码中会导致汇编的大量混乱。(堆栈args会导致push,而push与存储中的数据混合到堆栈中以初始化数组。)

使用volatile a[100]{1,2,3,4}获取GCC来创建然后复制该数组,这是很疯狂的。通常,volatile这有助于查看编译器如何初始化局部变量或将其布置在堆栈上。

vla*_*sch 2

理论上你的初始化可能是这样的:

int a[100] = {
  [3] = 1,
  [5] = 42,
  [88] = 1,
};
Run Code Online (Sandbox Code Playgroud)

因此,从缓存和可优化性的角度来看,首先将整个内存块清零,然后设置各个值可能会更有效。

行为变化可能取决于:

  • 目标架构
  • 目标操作系统
  • 数组长度
  • 初始化比率(显式初始化值/长度)
  • 初始化值的位置

当然,在您的情况下,初始化是在数组的开头压缩的,并且优化将是微不足道的。

所以看起来 gcc 在这里采用的是最通用的方法。看起来缺少优化。