为什么使用JLE而不是JL?

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


3
这可能是代码生成,以鼓励后续的优化,比如循环展开。但由于未进行优化,请避免查看未优化的代码。 - Hans Passant
这实际上是gcc代码生成的选择(您需要深入挖掘代码的树形表示),但它将var < N测试转换为[MinVar..N-1]的范围测试,这就是3的来源。如果有任何安慰,clang测试为4,但将测试放在循环的开头 - Anya Shenanigans
2个回答

8
循环计数向上,限制常数的情况非常普遍。编译器有两个选项来实现循环终止检查 - JLEJL。虽然这两种方式看起来完全相同,但请考虑以下内容。
如您在反汇编列表中所见,常量(在您的情况下为3)被编码为1个字节。如果您的循环计数到256而不是4,则使用这样有效的编码来进行CMP指令将是不可能的,编译器将不得不使用“更大”的编码。因此,JLE提供了代码密度的微小改进(这对于缓存而言最终有益于性能)。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接