我写了以下程序:
#include <stdio.h>
int main()
{
int i = 0;
for (; i < 4; i++)
{
printf("%i",i);
}
return 0;
}
我使用gcc test.c -o test.o
对其进行了编译,然后使用objdump -d -Mintel test.o
对其进行了反汇编。我得到的汇编代码(至少是相关部分)如下:
0804840c <main>:
804840c: 55 push ebp
804840d: 89 e5 mov ebp,esp
804840f: 83 e4 f0 and esp,0xfffffff0
8048412: 83 ec 20 sub esp,0x20
8048415: c7 44 24 1c 00 00 00 mov DWORD PTR [esp+0x1c],0x0
804841c: 00
804841d: eb 19 jmp 8048438 <main+0x2c>
804841f: 8b 44 24 1c mov eax,DWORD PTR [esp+0x1c]
8048423: 89 44 24 04 mov DWORD PTR [esp+0x4],eax
8048427: c7 04 24 e8 84 04 08 mov DWORD PTR [esp],0x80484e8
804842e: e8 bd fe ff ff call 80482f0 <printf@plt>
8048433: 83 44 24 1c 01 add DWORD PTR [esp+0x1c],0x1
8048438: 83 7c 24 1c 03 cmp DWORD PTR [esp+0x1c],0x3
804843d: 7e e0 jle 804841f <main+0x13>
804843f: b8 00 00 00 00 mov eax,0x0
8048444: c9 leave
8048445: c3 ret
我注意到,虽然我的比较操作是i < 4
,但汇编代码是(在反汇编之后)i <= 3
。为什么会这样?为什么会使用JLE
代替JL
?
答案 0 :(得分:6)
向上计数且持续限制的循环非常常见。编译器有两个选项来实现循环终止检查 - JLE
和JL
。虽然这两种方式看起来完全相同,但请考虑以下几点。
正如您在反汇编列表中所看到的,常量(在您的情况下为3
)以1个字节编码。如果你的循环计数为256而不是4,那么对CMP
指令使用这种有效的编码将是impossible,并且编译器必须使用“更大”的编码。所以JLE
提供了代码密度的微小改进(由于缓存,最终有利于提高性能)。
答案 1 :(得分:1)
它会JLE因为它将值移动了一个。
if (x < 4) {
// ran when x is 3, 2, 1, 0, -1, ... MIN_INT.
}
在逻辑上等同于
if (x <= 3) {
// ran when x is 3, 2, 1, 0, -1, ... MIN_INT.
}
为什么编译器选择一个内部表示而不是另一个内部表示通常是优化的问题,但实际上很难知道优化是否是真正的驱动因素。在任何情况下,像这样的功能等同物是反向映射不是100%准确的原因。有很多方法可以编写对相同输入具有相同效果的条件。