对我来说,这就像一部时髦的电影。它的用途是什么?我应该什么时候使用它?
当前回答
在MOV上使用LEA的最大原因是,如果需要对用于计算地址的寄存器执行算术运算。实际上,您可以在几个寄存器上组合有效地“免费”执行相当于指针运算的操作
真正令人困惑的是,您通常会像MOV一样编写LEA,但实际上并没有取消对内存的引用。换句话说:
移动EAX,[ESP+4]
这将把ESP+4点的内容移动到EAX中。
LEA-EAX,[EBX*8]
这将把有效地址EBX*8移动到EAX,而不是在该位置找到的地址。正如您所看到的,当MOV仅限于加法/减法时,也可以乘以2的因子(缩放)。
其他回答
LEA指令可用于避免CPU对有效地址进行耗时的计算。如果地址被重复使用,则将其存储在寄存器中而不是每次使用时计算有效地址更有效。
正如其他人所指出的,LEA(负载有效地址)经常被用作进行某些计算的“技巧”,但这并不是它的主要目的。x86指令集是为支持Pascal和C等高级语言而设计的,在这些语言中,数组特别是int数组或小型结构是常见的。例如,考虑表示(x,y)坐标的结构:
struct Point
{
int xcoord;
int ycoord;
};
现在想象一下这样的陈述:
int y = points[i].ycoord;
其中points[]是Point的数组。假设数组的基已经在EBX中,变量i在EAX中,xcoord和ycoord各为32位(因此ycoord在结构中的偏移量为4字节),则该语句可以编译为:
MOV EDX, [EBX + 8*EAX + 4] ; right side is "effective address"
其将在EDX中降落y。比例因子为8是因为每个点的大小为8字节。现在考虑与“address of”运算符使用的相同表达式&:
int *p = &points[i].ycoord;
在这种情况下,您不需要ycoord的值,而是需要它的地址。这就是LEA(加载有效地址)的作用
LEA ESI, [EBX + 8*EAX + 4]
这将在ESI中加载地址。
LEA指令的另一个重要特征是它不会改变条件代码(如CF和ZF),而通过ADD或MUL等算术指令计算地址则会改变。这一特性降低了指令之间的依赖程度,从而为编译器或硬件调度器的进一步优化腾出了空间。
似乎很多答案都已经完成了,我想再添加一个示例代码,说明当lea和move指令具有相同的表达式格式时,它们的工作方式是如何不同的。
长话短说,lea指令和mov指令都可以用括号括住指令的src操作数。当它们用()括起来时,()中的表达式的计算方法相同;但是,两条指令将以不同的方式解释src操作数中的计算值。
无论表达式与lea还是mov一起使用,src值的计算如下。
D(Rb,Ri,S)=>(Reg[Rb]+S*Reg[Ri]+D)
但是,当它与mov指令一起使用时,它会尝试访问由上述表达式生成的地址所指向的值,并将其存储到目标。
与此相反,当lea指令使用上述表达式执行时,它会将生成的值原样加载到目标。
下面的代码使用相同的参数执行lea指令和mov指令。然而,为了捕捉差异,我添加了一个用户级信号处理程序,以捕捉由于mov指令访问错误地址而导致的分段错误。
示例代码
#define _GNU_SOURCE 1 /* To pick up REG_RIP */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>
uint32_t
register_handler(uint32_t event, void (*handler)(int, siginfo_t *, void *))
{
uint32_t ret = 0;
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
ret = sigaction(event, &act, NULL);
return ret;
}
void segfault_handler(int signum, siginfo_t *info, void *priv)
{
ucontext_t *context = (ucontext_t *)(priv);
uint64_t rip = (uint64_t)(context->uc_mcontext.gregs[REG_RIP]);
uint64_t faulty_addr = (uint64_t)(info->si_addr);
printf("inst at 0x%lx tries to access memory at %ld, but failed\n",
rip, faulty_addr);
exit(1);
}
int main(void)
{
int result_of_lea = 0;
register_handler(SIGSEGV, segfault_handler);
// initialize registers %eax = 1, %ebx = 2
// the compiler will emit something like
// mov $1, %eax
// mov $2, %ebx
// because of the input operands
asm("lea 4(%%rbx, %%rax, 8), %%edx \t\n"
: "=d"(result_of_lea) // output in EDX
: "a"(1), "b"(2) // inputs in EAX and EBX
: // no clobbers
);
// lea 4(rbx, rax, 8),%edx == lea (rbx + 8*rax + 4),%edx == lea(14),%edx
printf("Result of lea instruction: %d\n", result_of_lea);
asm volatile("mov 4(%%rbx, %%rax, 8), %%edx"
:
: "a"(1), "b"(2)
: "edx" // if it didn't segfault, it would write EDX
);
}
执行结果
Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed
如果有人已经提到了,请原谅我,但如果有人想知道x86糟糕的旧时代,内存分割仍然是相关的:您将始终从以下两条指令中得到相同的结果:
LEA AX, DS:[0x1234]
and
LEA AX, CS:[0x1234]
“有效地址”只是seg:off逻辑地址的偏移部分。在本例中,0x1234。
LEA不添加段基础。这将击败最初的一个用例,即进行地址计算以获得指针(偏移量),实际上可以取消引用。例如lea bx,[array+si]。如果添加了DS基以给出线性地址,则稍后的mov ax,[bx]将再次添加DS基。此外,20位结果通常不适合16位寄存器。
看见https://www.stevemorse.org/8086/index.html8086的建筑师写了一本关于指令集的书,现在在他的网站上免费。关于LEA的部分提到了他的一些设计意图。
推荐文章
- ARM架构与x86有何不同?
- 为什么GCC在实现整数除法时使用奇数乘法?
- 基指针和堆栈指针到底是什么?他们指的是什么?
- 汇编代码vs机器代码vs目标代码?
- 什么是回跳线?它是如何工作的?
- 提交到App Store的问题:不支持架构x86
- “switch”比“if”快吗?
- 为什么Java在连续整数上的切换与添加的情况下运行得更快?
- 多核汇编语言是什么样子的?
- 如何在没有操作系统的情况下运行程序?
- 是否可以“反编译”Windows .exe?或者至少看看大会?
- 使用GCC产生可读的程序集?
- 如何用SSE4.2和AVX指令编译Tensorflow ?
- 为什么这段代码在对循环携带的加法进行强度降低的乘法运算后执行得更慢?
- 在Intel sandybridge系列cpu中对管道的程序进行反优化