因此,我编写了以下C代码:

#include <stdio.h>


int main() {
    int i = 1;

    while(i) {
        printf("in loop\n");
        i++;

        if(i == 10) {
            break;
        }
    }

    return 0;
}


用gcc编译(Ubuntu / Linaro 4.7.2-2ubuntu1)4.7.2,它反汇编为:

   0x000000000040051c <+0>: push   %rbp
   0x000000000040051d <+1>: mov    %rsp,%rbp
   0x0000000000400520 <+4>: sub    q4312078qx10,%rsp
   0x0000000000400524 <+8>: movl   q4312078qx1,-0x4(%rbp)
   0x000000000040052b <+15>:    jmp    0x400541 <main+37>
   0x000000000040052d <+17>:    mov    q4312078qx400604,%edi
   0x0000000000400532 <+22>:    callq  0x4003f0 <puts@plt>
   0x0000000000400537 <+27>:    addl   q4312078qx1,-0x4(%rbp)
   0x000000000040053b <+31>:    cmpl   q4312078qxa,-0x4(%rbp)
   0x000000000040053f <+35>:    je     0x400549 <main+45>
   0x0000000000400541 <+37>:    cmpl   q4312078qx0,-0x4(%rbp)
   0x0000000000400545 <+41>:    jne    0x40052d <main+17>
   0x0000000000400547 <+43>:    jmp    0x40054a <main+46>
   0x0000000000400549 <+45>:    nop
   0x000000000040054a <+46>:    mov    q4312078qx0,%eax
   0x000000000040054f <+51>:    leaveq 
   0x0000000000400550 <+52>:    retq  


为什么+45上出现nop?为什么+35上的je不能直接跳到+46?

#1 楼

它可能用于功能对齐。现在,它在0x400550上返回,可以除以8。如果在0x40054f上返回,则未对齐。不过只是个推测。

评论


我用Google搜索函数对齐方式,并在SO上找到了这篇文章,它似乎在更详细地回答了我的问题。感谢您的帮助。

– pyCtrl_
13-10-4在15:32

抱歉,我不认为这是函数对齐。这仅适用于功能开始,而不适用于结束。它也不是-falign-labels(按照falign-labels的描述插入了nop,但是不使用调整后的地址)。我宁愿认为这是由于编译器为更长的操作码表示保留​​了一些字节而没有清理掉。

–杂件
13年10月13日在14:07

是的,这似乎是未优化的代码。尝试将-O3或-Os生成的代码与-O0生成的代码进行比较。

– microtherion
13年10月13日在16:44

#2 楼


大多数微处理器都以对齐的16字节或32字节块为单位获取代码。
如果重要的子例程条目或跳转标签恰好在16字节块的
端附近,则微处理器提取该代码块时,只会得到一些有用的代码字节。它可能还必须
获取下一个16个字节,然后才能解码标签之后的前一个
指令。可以通过将重要的子例程条目和循环条目对齐16来避免此问题。对齐8
可以确保至少可以通过首个
指令读取加载8个字节的代码,这可能会如果指令很小,就足够了。



通过Agner Fog使用汇编语言优化子例程。 PDF

评论


您能否指出OP拆卸中的这种更好的对齐方式?我不明白为什么在打no的地方会有所改善。

–杂件
13-10-19在0:47

#3 楼

NOP插入的另一个原因是由于管道调度。如果分支预测需要一个周期来确定它是否正确(以及是否不冲洗管道),那么在将结果提交给寄存器之前,您需要一个周期延迟。

在跳跃等于NOP的特定示例中,在我看来,处理器需要一个周期来确定其是否获得正确的答案,并根据需要调整管道。

深入研究代码并了解正在发生的事情。 :)