为什么 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 输出中有杂散的 nop
s。 (可能是因为我不会浪费时间查看未优化的编译器输出)。
通常 nop
s 内部函数用于对齐分支目标,包括 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
没有调试或性能优势。(请参阅 x86 tag wiki, esp. Agner Fog's microarchitecture guide 中的性能指南链接以了解是什么让代码变快在当前的 CPU 上。)
我的猜测是它纯粹是 gcc 内部的产物。 nop
在 gcc -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
如果您想查看经过完全优化的非矢量化版本,那么这很方便。
给定以下 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 输出中有杂散的 nop
s。 (可能是因为我不会浪费时间查看未优化的编译器输出)。
通常 nop
s 内部函数用于对齐分支目标,包括 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
没有调试或性能优势。(请参阅 x86 tag wiki, esp. Agner Fog's microarchitecture guide 中的性能指南链接以了解是什么让代码变快在当前的 CPU 上。)
我的猜测是它纯粹是 gcc 内部的产物。 nop
在 gcc -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
如果您想查看经过完全优化的非矢量化版本,那么这很方便。