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

时间:2016-04-15 11:55:16

标签: gcc assembly x86-64 nop

给出以下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    $0x50,%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是否有理由在90处插入nop / 1f,或者这只是在未启用优化时可能发生的副作用?

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

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

1 个答案:

答案 0 :(得分:9)

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

通常nop内部函数用于对齐分支目标,包括the question Brian linked中的函数入口点。 (另请参阅-falign-loops in the gcc docs,默认情况下会在-Os以外的优化级别启用。

在这种情况下,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没有调试或性能优势。(请参阅标记wiki中的效果指南链接,尤其是{ {3}}了解在当前CPU上使代码快速运行的原因。)

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

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

如果您正在尝试学习asm,那么查看-Og处的代码会更有趣(例如,优化调试)。

如果您正在尝试查看gcc或clang在制作代码时的表现,那么您应该查看-O3 -march=native(或-O2 -mtune=intel,或者您构建项目的任何设置)。尽管如此,在-O3中进行优化是一种很好的方法,但也是学习一些asm技巧的好方法。 -fno-tree-vectorize如果您希望看到除此之外的完全优化的非矢量化版本,则非常方便。