为什么 x86-64 上的 GCC 在函数内部插入一个 NOP?

Why does GCC on x86-64 insert a NOP inside of a function?

给定以下 C 函数:

void go(char *data) {
    char name[64];
    strcpy(name, data);
}

x86-64 上的 GCC 5 和 6 编译(普通 gcc -c -g -o 后跟 objdump)到:

0000000000000000 <go>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 50             sub    [=11=]x50,%rsp
   8:   48 89 7d b8             mov    %rdi,-0x48(%rbp)
   c:   48 8b 55 b8             mov    -0x48(%rbp),%rdx
  10:   48 8d 45 c0             lea    -0x40(%rbp),%rax
  14:   48 89 d6                mov    %rdx,%rsi
  17:   48 89 c7                mov    %rax,%rdi
  1a:   e8 00 00 00 00          callq  1f <go+0x1f>
  1f:   90                      nop
  20:   c9                      leaveq 
  21:   c3                      retq   

GCC 是否有任何理由在 1f 处插入 90/nop,或者这只是没有打开优化时可能发生的副作用?

注意:这个问题与大多数其他问题不同,因为它询问函数体内的 nop,而不是外部填充。

测试的编译器版本:GCC Debian 5.3.1-14 (5.3.1) 和 Debian 6-20160313-1 (6.0.0)

这很奇怪,我以前从未注意到 -O0 处的 asm 输出中有杂散的 nops。 (可能是因为我不会浪费时间查看未优化的编译器输出)。

通常 nops 内部函数用于对齐分支目标,包括 the question Brian linked. (Also see -falign-loops in the gcc docs 中的函数入口点,它在 -Os 以外的优化级别默认打开)。 =36=]


在这种情况下,nop 是空函数的编译器噪声的一部分:

void go(void) {
    //char name[64];
    //strcpy(name, data);
}
    push    rbp
    mov     rbp, rsp
    nop                     # only present for gcc5, not gcc 4.9.3
    pop     rbp
    ret

See that code in the Godbolt Compiler Explorer 因此您可以检查 asm 的其他编译器版本和编译选项。

(技术上不是噪音,但 -O0 启用 -fno-omit-frame-pointer,并且在 -O0 甚至空函数设置和拆除堆栈帧。)


当然,nop 不存在于任何非零优化级别。 在问题的代码中 nop 没有调试或性能优势。(请参阅 tag wiki, esp. Agner Fog's microarchitecture guide 中的性能指南链接以了解是什么让代码变快在当前的 CPU 上。)

我的猜测是它纯粹是 gcc 内部的产物nopgcc -S asm 输出中作为 nop 存在,而不是作为 .p2align 指令。 gcc 本身不计算机器代码字节,它只是在某些点使用对齐指令来对齐重要的分支目标。只有汇编程序知道达到给定对齐实际需要多大的 nop

默认值 -O0 告诉 gcc 您希望它编译速度快,不要 生成好的代码。这意味着 asm 输出比其他 -O 级别告诉您更多关于 gcc 内部的信息,而关于如何优化或其他任何内容的信息很少。

如果你正在尝试学习 asm,那么查看 -Og 中的代码会更有趣,例如(针对调试进行优化)。

如果您想了解 gcc 或 clang 在编写代码方面的表现,您应该查看 -O3 -march=native(或 -O2 -mtune=intel,或您构建项目时使用的任何设置)。不过,弄清楚在 -O3 所做的优化是学习一些 asm 技巧的好方法。 -fno-tree-vectorize 如果您想查看经过完全优化的非矢量化版本,那么这很方便。