我偶然发现了一个奇怪的案例,我想我不能一个人解决它。 />
#include <stdio.h>
int main(){
char *texto = "O numero e %d\n";
int i = 10;
while(i){
printf(texto, i--);
}
return 0;
}
IDA生产的组件如下:
mov eax, [esp+28]
lea edx, [eax-1] ; The part i don't understand
mov [esp+28], edx
mov [esp+4], eax
mov eax, [esp+18h]
mov [esp], eax ; char *
call _printf
我能理解的是它存储了eax中的旧值并推送到堆栈(我故意没有打开优化),然后推送格式。
虽然发生在中间,但它执行
i--
,但是我不明白它是如何工作的。因此它得到的是eax-1
的地址并存储在edx
中,然后将其存储在i
中,但是eax
并不保存地址,而是一个值。#1 楼
您所看到的是编译器喜欢使用的一种效率技巧。内部,CPU在数字和地址之间没有区别-32位整数和指针是同一回事。 (或者,如果使用的是较新的体系结构,则为64位,但是由于您的寄存器名称以
e
开头,因此您使用的是32位)。操作数本身。用C术语,您可以将[eax-1]视为*(eax-1),并且lea
向其添加了lea
运算符,因此&
类似于lea edx, [eax-1]
。当然,这与edx = &(*(eax-1))
相同。使用指令序列
eax-1
或mov edx, eax; sub edx, 1
,编译器可以完成完全相同的操作。那么,为什么它使用mov edx, eax; dec edx
指令呢?同样,当在后续操作中两次使用同一寄存器时,流水线也会出现问题。这意味着,在较旧的处理器上,使用lea
比其他处理器快几个周期,并且在编译器中实现起来并不难,因此这是编译器传统上所做的。“(需要)
lea
使用单独的硬件”已经不再存在了,流水线比以前更加智能,因此我怀疑这些天是否会有所作为。但是它仍然在编译器中,不会被从编译器中删除,因为没有充分的理由。评论
了解&(*(eax-1))可以解决此问题。感谢您提供完整而深入的答案!
–krystalgamer
16-3-29在14:12
即使在今天,它也减小了代码大小。那不是胜利吗?
– John Dvorak
16 Mar 29 '16 at 14:23
@JanDvorak:不会将EAX存储到[ESP + 4],然后使用“ dec eax”并将EAX存储到[ESP + 28]保存更多代码吗?我知道现代CPU的“ inc”和“ dec”比“ add”要慢,但是我认为它们仍然是单字节指令,不是吗?
–超级猫
16 Mar 29 '16 at 15:46
@Jan:现代编译器倾向于在最小的挑衅下积极地进行内联,因此,在超级膨胀中,这里和那里的微不足道的节省将被忽略。而且,CPU现在有太多的ALU,几乎没有人会注意到lea不使用任何ALU。但是,lea不用理会这些标志,它可以减少寄存器压力(在x86模式下总是有问题!),并且可以缩短依赖链。不喜欢什么;-)
– DarthGizka
16-3-29在15:47
实际上,它仍然有很大的不同。在这种情况下,生成是愚蠢的,因为常量为1,但是在大多数情况下,它比mov / add对甚至移动/添加/添加三元组要好得多。
–约书亚
16 Mar 29 '16 at 20:06
#2 楼
根据Intel,LEA指令为:该指令计算第二个操作数的有效地址
(源操作数)并将其存储在第一个操作数中(目标
/>操作数)。源操作数是用一种处理器寻址模式指定的存储器地址(偏移量部分)。 destination
操作数是一个通用寄存器。因此,
lea edx, [eax-1]
计算[eax-1]
的地址,即eax-1,因为[]表示操作数应为作为地址处理。该地址之后将存储在edx
中。评论
我一直在搞乱寄存器的地址。.现在我知道它减去1。谢谢!
–krystalgamer
16 Mar 29 '16 at 12:06
评论
lea edx,[blah-1]本质上是mov edx,blah-1(后者显然不是有效的命令)