12

我写了以下程序:

#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

4

2 回答 2

8

向上计数并具有恒定限制的循环非常常见。编译器有两个选项来实现循环终止检查 -JLEJL. 虽然这两种方式看起来完全等价,但请考虑以下内容。

正如您在反汇编列表中看到的那样,常量(3在您的情况下)被编码为 1 个字节。如果您的循环计数为 256 而不是 4,则不可能CMP指令使用如此有效的编码,编译器将不得不使用“更大”的编码。因此JLE在代码密度方面提供了边际改进(由于缓存,这最终有利于性能)。

于 2013-10-24T18:38:29.920 回答
1

它将是 JLE,因为它将值移动了 1。

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% 准确的原因。有很多方法可以编写对相同输入具有相同效果的条件。

于 2013-10-24T18:36:06.903 回答