为什么x86-64上的GCC在函数内部插入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是否有任何理由在1f处插入90/nop,或者这只是在未打开优化时可能发生的副作用?

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

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

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

通常nop的内部函数是对齐分支目标,包括Brian链接的问题中的函数入口点。(另请参阅gcc文档中的-falign-loops,在除-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

请在Godbolt编译器资源管理器中查看该代码,以便检查asm中的其他编译器版本和编译选项。

(从技术上讲,这不是噪音,但-O0启用-fno-omit-frame-pointer,在-O0时,甚至可以设置和拆除堆栈帧的空函数。)


当然,nop不存在于任何非零优化级别问题代码中的nop没有调试或性能优势(请参阅x86标签wiki中的性能指南链接,特别是Agner Fog的微体系结构指南,了解是什么使代码在当前CPU上快速运行。)

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

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

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

如果你想看看gcc或clang在编写代码方面做得有多好,你应该看看-O3 -march=native(或-O2 -mtune=intel,或者你构建项目时使用的任何设置)。不过,弄清楚在-O3中进行的优化是学习一些asm技巧的好方法。如果你想看到一个完全优化的非矢量化版本,-fno-tree-vectorize是很方便的。

相关内容

  • 没有找到相关文章

最新更新