temp = fetched immediate operand (5)
temp += bx
temp += si
address_out = temp (skipped for LEA)
trigger 16-bit read (skipped for LEA)
temp = data_in (skipped for LEA)
ax = temp
至于为何英特尔认为这一点指令值得包括,I 'm not exactly sure, but the fact that it was cheap to implement would have been a big factor. Another factor would have been the fact that Intel' s汇编程序允许相对于BP寄存器定义符号 . 如果将 fnord 定义为BP相对符号(例如BP 8),可以说:
mov ax,fnord ; Equivalent to "mov ax,[BP+8]"
如果有人想使用像stosw这样的东西将数据存储到BP相对地址,那就可以说了
mov ax,0 ; Data to store
mov cx,16 ; Number of words
lea di,fnord
rep movs fnord ; Address is ignored EXCEPT to note that it's an SS-relative word ptr
比以下更方便:
mov ax,0 ; Data to store
mov cx,16 ; Number of words
mov di,bp
add di,offset fnord (i.e. 8)
rep movs fnord ; Address is ignored EXCEPT to note that it's an SS-relative word ptr
// compute parity of permutation from lexicographic index
int parity (int p)
{
assert (p >= 0);
int r = p, k = 1, d = 2;
while (p >= k) {
p /= d;
d += (k << 2) + 6; // only one lea instruction
k += 2;
r ^= p;
}
return r & 1;
}
14 回答
正如其他人所指出的那样,LEA(加载有效地址)通常被用作进行某些计算的“技巧”,但这不是它的主要目的 . x86指令集旨在支持Pascal和C等高级语言,其中数组 - 特别是int或小结构数组 - 很常见 . 例如,考虑一个表示(x,y)坐标的结构:
现在想象一下如下声明:
其中
points[]
是Point
的数组 . 假设数组的基数已经在EBX
中,变量i
在EAX
中,并且xcoord
和ycoord
每个都是32位(因此ycoord
在结构中偏移4个字节),可以将此语句编译为:这将在
EDX
落地y
. 比例因子为8是因为每个Point
的大小为8个字节 . 现在考虑与"address of"运算符一起使用的相同表达式:在这种情况下,您不需要
ycoord
的值,而是它的地址 . 这就是LEA
(加载有效地址)进来的地方 . 编译器可以生成MOV
而不是MOV
.这将加载
ESI
中的地址 .来自Abrash的"Zen of Assembly":
并且
LEA
不会改变标志 .例子
LEA EAX, [ EAX + EBX + 1234567 ]
计算EAX + EBX + 1234567
(这是三个操作数)LEA EAX, [ EBX + ECX ]
计算EBX + ECX
而不覆盖结果 .乘以常数(乘以二,三,五或九),如果你像_814585那样使用它(N可以是1,2,4,8) .
其他用例在循环中很方便:
LEA EAX, [ EAX + 1 ]
和INC EAX
之间的区别在于后者改变EFLAGS
但前者没有改变 . 这保留了CMP
州 .LEA
指令的另一个重要特性是,它不会改变条件代码,例如CF
和ZF
,而是通过算术指令(如ADD
或MUL
)计算地址 . 此功能降低了指令之间的依赖性级别,从而为编译器或硬件调度程序的进一步优化腾出了空间 .尽管有各种解释,LEA是一个算术运算:
只是它的名字对于移位添加操作来说极其愚蠢 . 其原因已经在最高评级的答案中解释(即它被设计为直接映射高级别的内存参考) .
也许只是关于LEA指令的另一件事 . 您还可以使用LEA将快速乘法寄存器设置为3,5或9 .
lea
是"load effective address"的缩写 . 它将源操作数的位置引用的地址加载到目标操作数 . 例如,您可以使用它来:使用单个指令进一步移动
ebx
指针eax
项(在64位/元素数组中) . 基本上,您可以从x86架构支持的复杂寻址模式中受益,从而有效地操作指针 .在
MOV
上使用LEA
的最大原因是,如果需要对用于计算地址的寄存器执行算术运算 . 实际上,您可以有效地对几个寄存器中的指针算术执行相应的操作"free."真正令人困惑的是你通常会像_814606那样写一个
LEA
但你实际上并没有解除引用内存 . 换一种说法:MOV EAX, [ESP+4]
这会将
ESP+4
指向的内容移动到EAX
.LEA EAX, [EBX*8]
这会将有效地址
EBX * 8
移动到EAX中,而不是在该位置找到的地址 . 正如您所看到的,也可以将因子乘以2(缩放),而MOV
仅限于加/减 .8086具有大型指令系列,其接受寄存器操作数和有效地址,执行一些计算以计算该有效地址的偏移部分,并执行涉及由计算地址引用的寄存器和存储器的一些操作 . 除了跳过实际的内存操作之外,让该系列中的一个指令表现如上所述是相当简单的 . 这个,说明:
内部几乎完全相同 . 差异是跳过的步骤 . 两条指令的工作方式如下:
至于为何英特尔认为这一点指令值得包括,I 'm not exactly sure, but the fact that it was cheap to implement would have been a big factor. Another factor would have been the fact that Intel' s汇编程序允许相对于BP寄存器定义符号 . 如果将
fnord
定义为BP相对符号(例如BP 8),可以说:如果有人想使用像stosw这样的东西将数据存储到BP相对地址,那就可以说了
比以下更方便:
注意,忘记世界“偏移”将导致位置[BP 8]的内容而不是值8被添加到DI . 哎呀 .
正如现有的答案所提到的,
LEA
具有在不访问存储器的情况下执行存储器寻址算法的优点,将算术结果保存到不同的寄存器而不是简单形式的加法指令 . 真正的基本性能优势是现代处理器具有单独的LEA ALU单元和端口,用于生成有效地址(包括LEA
和其他存储器参考地址),这意味着LEA
中的算术运算和ALU中的其他正常算术运算可以并行完成在一个核心 .有关LEA单元的一些详细信息,请查看Haswell架构的这篇文章:http://www.realworldtech.com/haswell-cpu/4/
另一个重要的一点是在其他答案中没有提到的是
LEA REG, [MemoryAddress]
指令是PIC(位置无关代码),它对该指令中的PC相对地址进行编码以引用MemoryAddress
. 这与MOV REG, MemoryAddress
不同,后者编码相对虚拟地址,需要在现代操作系统中重定位/修补(如ASLR是常见功能) . 因此LEA
可用于将此非PIC转换为PIC .LEA指令可用于避免CPU对有效地址进行耗时的计算 . 如果重复使用地址,则将其存储在寄存器中更有效,而不是每次使用时计算有效地址 .
这是一个例子 .
使用-O(optimize)作为编译器选项,gcc将找到指定代码行的lea指令 .
LEA(加载有效地址)指令是获取任何Intel处理器的存储器寻址模式产生的地址的一种方法 .
也就是说,如果我们有这样的数据移动:
它将指定内存位置的内容移动到目标寄存器中 .
如果我们用
LEA
替换MOV
,那么<MEM-OPERAND>
寻址表达式将以完全相同的方式计算内存位置的地址 . 但是,我们将位置本身放入目的地,而不是内存位置的内容 .LEA
不是特定的算术指令;它是一种拦截处理器的任何一种存储器寻址模式产生的有效地址的方法 .例如,我们可以在一个简单的直接地址上使用
LEA
. 根本没有涉及算术:这是有效的;我们可以在Linux提示符下测试它:
这里,没有添加缩放值,也没有偏移 . 零被移入EAX . 我们也可以使用带有立即操作数的MOV .
这就是为什么认为
LEA
中的括号多余的人严重错误的原因;括号不是LEA
语法,但是是寻址模式的一部分 .LEA在硬件层面是真实的 . 生成的指令对实际寻址模式进行编码,处理器将其执行到计算地址的点 . 然后它将该地址移动到目标而不是生成内存引用 . (由于任何其他指令中寻址模式的地址计算对CPU标志没有影响,
LEA
对CPU标志没有影响 . )与从地址零加载值对比:
这是一个非常相似的编码,看到了吗?只是
LEA
的8d
已更改为8b
.当然,这个
LEA
编码比将立即零移动到EAX
更长:LEA
没有理由排除这种可能性,只是因为有一个较短的选择;它只是以正交方式与可用的寻址模式相结合 .LEA:只是一个“算术”指令..
MOV在操作数之间传输数据,但是lea只是计算
它是因为你编写代码
你可以简单地写