对我来说,这就像一部时髦的电影。它的用途是什么?我应该什么时候使用它?


当前回答

LEA指令的另一个重要特征是它不会改变条件代码(如CF和ZF),而通过ADD或MUL等算术指令计算地址则会改变。这一特性降低了指令之间的依赖程度,从而为编译器或硬件调度器的进一步优化腾出了空间。

其他回答

也许只是LEA指令的另一件事。您还可以使用LEA将寄存器快速乘以3、5或9。

LEA EAX, [EAX * 2 + EAX]   ;EAX = EAX * 3
LEA EAX, [EAX * 4 + EAX]   ;EAX = EAX * 5
LEA EAX, [EAX * 8 + EAX]   ;EAX = EAX * 9

所有正常的“计算”指令,如加法、异或设置状态标志,如零、符号。如果使用复杂的地址AX-xor:=mem[0x33+BX+8*CX],则标志将根据xor操作设置。

现在您可能需要多次使用该地址。将这样的地址加载到寄存器中并不是为了设置状态标志,幸运的是,它并没有。短语“加载有效地址”使程序员意识到这一点。这就是奇怪表情的由来。

很明显,一旦处理器能够使用复杂的地址来处理其内容,它就能够将其计算用于其他目的。实际上,它可以用于在一条指令中执行转换x<-3*x+1。这是汇编程序设计中的一条一般规则:无论它如何摇晃你的船,都要使用指令。唯一重要的是指令所体现的特定转换是否对您有用。

要旨

MOV, X| T| AX'| R| BX|

and

LEA, AX'| [BX]

对AX具有相同的影响,但对状态标志没有影响。(这是ciasdis符号。)

LEA指令可用于避免CPU对有效地址进行耗时的计算。如果地址被重复使用,则将其存储在寄存器中而不是每次使用时计算有效地址更有效。

来自Abrash的“装配禅”:

LEA,唯一执行内存寻址计算但实际上不寻址内存的指令。LEA接受标准内存寻址操作数,但只会将计算出的内存偏移量存储在指定寄存器中,该寄存器可以是任何通用寄存器。这给了我们什么?ADD没有提供的两件事:使用两个或三个操作数执行加法的能力,以及将结果存储在任何寄存器中的能力;而不仅仅是源操作数之一。

执法机关不改变旗帜。

示例

LEA EAX,[EAX+EBX+1234567]计算EAX+EBX+134567(即三个操作数)LEA EAX,[EBX+ECX]计算EBX+ECX,而不使用结果覆盖两者。乘以常数(乘以2、3、5或9),如果你像LEA EAX那样使用,[EBX+N*EBX](N可以是1,2,4,8)。

其他用例在循环中很方便:LEA EAX、[EAX+1]和INC EAX之间的区别在于后者更改EFLAGS,但前者不更改;这保持了CMP状态。

似乎很多答案都已经完成了,我想再添加一个示例代码,说明当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