LEA指令的目的是什么?

发布于 2024-08-09 12:59:55 字数 44 浏览 4 评论 0原文

对我来说,这似乎只是一部时髦的 MOV。它的用途是什么?我应该何时使用它?

For me, it just seems like a funky MOV. What's its purpose and when should I use it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(17

耶耶耶 2024-08-16 12:59:55

正如其他人指出的那样,LEA(加载有效地址)经常被用作进行某些计算的“技巧”,但这不是它的主要目的。 x86 指令集旨在支持 Pascal 和 C 等高级语言,其中数组(尤其是整数数组或小型结构体)很常见。例如,考虑一个表示 (x, y) 坐标的结构:

struct Point
{
     int xcoord;
     int ycoord;
};

现在想象这样一个语句:

int y = points[i].ycoord;

其中 points[] 是一个 Point 数组。假设数组的基数已在 EBX 中,变量 iEAX 中,并且 xcoord 和 < code>ycoord 各为 32 位(因此 ycoord 位于结构中的偏移 4 个字节处),该语句可以编译为:

MOV EDX, [EBX + 8*EAX + 4]    ; right side is "effective address"

它将把 y 放入EDX。比例因子为 8 是因为每个点的大小为 8 字节。现在考虑与“地址”运算符 & 一起使用的相同表达式:

int *p = &points[i].ycoord;

在这种情况下,您不需要 ycoord 的值,而是它的地址。这就是 LEA(加载有效地址)的用武之地。编译器可以生成

LEA ESI, [EBX + 8*EAX + 4]

将地址加载到 ESI 中的文件,而不是 MOV

As others have pointed out, LEA (load effective address) is often used as a "trick" to do certain computations, but that's not its primary purpose. The x86 instruction set was designed to support high-level languages like Pascal and C, where arrays—especially arrays of ints or small structs—are common. Consider, for example, a struct representing (x, y) coordinates:

struct Point
{
     int xcoord;
     int ycoord;
};

Now imagine a statement like:

int y = points[i].ycoord;

where points[] is an array of Point. Assuming the base of the array is already in EBX, and variable i is in EAX, and xcoord and ycoord are each 32 bits (so ycoord is at offset 4 bytes in the struct), this statement can be compiled to:

MOV EDX, [EBX + 8*EAX + 4]    ; right side is "effective address"

which will land y in EDX. The scale factor of 8 is because each Point is 8 bytes in size. Now consider the same expression used with the "address of" operator &:

int *p = &points[i].ycoord;

In this case, you don't want the value of ycoord, but its address. That's where LEA (load effective address) comes in. Instead of a MOV, the compiler can generate

LEA ESI, [EBX + 8*EAX + 4]

which will load the address in ESI.

任性一次 2024-08-16 12:59:55

来自 Abrash 的“组装之禅”

LEA,唯一执行内存寻址计算但并不实际寻址内存的指令。 LEA 接受标准内存寻址操作数,但仅将计算出的内存偏移量存储在指定寄存器中,该寄存器可以是任何通用寄存器。

这给我们带来了什么? ADD 不提供两件事:

  1. 能够使用两个或三个操作数执行加法,并且
  2. 能够将结果存储在任何寄存器中;不仅仅是源操作数之一。

并且 LEA 不会更改标志。

示例

  • LEA EAX, [ EAX + EBX + 1234567 ] 计算 EAX + EBX + 1234567 (这是三个操作数)
  • LEA EAX, [ EBX + ECX ] 计算EBX + ECX,而不用结果覆盖其中任何一个。
  • 乘以常数(乘以二、三、五或九),如果您像LEA EAX,[ EBX + N * EBX ]一样使用它(N可以是1,2,4,8)。

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

From the "Zen of Assembly" by Abrash:

LEA, the only instruction that performs memory addressing calculations but doesn't actually address memory. LEA accepts a standard memory addressing operand, but does nothing more than store the calculated memory offset in the specified register, which may be any general purpose register.

What does that give us? Two things that ADD doesn't provide:

  1. the ability to perform addition with either two or three operands, and
  2. the ability to store the result in any register; not just one of the source operands.

And LEA does not alter the flags.

Examples

  • LEA EAX, [ EAX + EBX + 1234567 ] calculates EAX + EBX + 1234567 (that's three operands)
  • LEA EAX, [ EBX + ECX ] calculates EBX + ECX without overriding either with the result.
  • multiplication by constant (by two, three, five or nine), if you use it like LEA EAX, [ EBX + N * EBX ] (N can be 1,2,4,8).

Other usecase is handy in loops: the difference between LEA EAX, [ EAX + 1 ] and INC EAX is that the latter changes EFLAGS but the former does not; this preserves CMP state.

不奢求什么 2024-08-16 12:59:55

LEA指令的另一个重要特点是,在通过算术指令计算地址时,它不会改变CFZF等条件码就像ADDMUL一样。此功能降低了指令之间的依赖性级别,从而为编译器或硬件调度程序的进一步优化腾出了空间。

Another important feature of the LEA instruction is that it does not alter the condition codes such as CF and ZF, while computing the address by arithmetic instructions like ADD or MUL does. This feature decreases the level of dependency among instructions and thus makes room for further optimization by the compiler or hardware scheduler.

尽管有所有的解释,LEA 是一种算术运算:

LEA Rt, [Rs1+a*Rs2+b] =>  Rt = Rs1 + a*Rs2 + b

只是它的名字对于移位+加法运算来说是极其愚蠢的。其原因已经在最高评价的答案中得到了解释(即它被设计为直接映射高级内存引用)。

Despite all the explanations, LEA is an arithmetic operation:

LEA Rt, [Rs1+a*Rs2+b] =>  Rt = Rs1 + a*Rs2 + b

It's just that its name is extremelly stupid for a shift+add operation. The reason for that was already explained in the top rated answers (i.e. it was designed to directly map high level memory references).

<逆流佳人身旁 2024-08-16 12:59:55

也许这只是 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

Maybe just another thing about LEA instruction.
You can also use LEA for fast multiplying registers by 3, 5 or 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
郁金香雨 2024-08-16 12:59:55

lea是“加载有效地址”的缩写。它将源操作数的位置引用的地址加载到目标操作数。例如,您可以使用它来:

lea ebx, [ebx+eax*8]

使用一条指令进一步移动 ebx 指针 eax 项(在 64 位/元素数组中)。基本上,您可以受益于 x86 架构支持的复杂寻址模式来有效地操作指针。

lea is an abbreviation of "load effective address". It loads the address of the location reference by the source operand to the destination operand. For instance, you could use it to:

lea ebx, [ebx+eax*8]

to move ebx pointer eax items further (in a 64-bit/element array) with a single instruction. Basically, you benefit from complex addressing modes supported by x86 architecture to manipulate pointers efficiently.

清风挽心 2024-08-16 12:59:55

使用 LEA 而不是 MOV 的最大原因是,如果您需要对用于计算地址的寄存器执行算术运算。实际上,您可以“免费”有效地组合对多个寄存器执行相当于指针算术的操作。

真正令人困惑的是,您通常会像 MOV 一样编写 LEA,但实际上并没有取消引用内存。换句话说:

MOV EAX, [ESP+4]

这会将 ESP+4 指向的内容移动到 EAX 中。

LEA EAX, [EBX*8]

这会将有效地址 EBX * 8 移动到 EAX 中,而不是在该位置找到的内容。正如您所看到的,它可以乘以两倍(缩放),而 MOV 仅限于加/减。

The biggest reason that you use LEA over a MOV is if you need to perform arithmetic on the registers that you are using to calculate the address. Effectively, you can perform what amounts to pointer arithmetic on several of the registers in combination effectively for "free."

What's really confusing about it is that you typically write an LEA just like a MOV but you aren't actually dereferencing the memory. In other words:

MOV EAX, [ESP+4]

This will move the content of what ESP+4 points to into EAX.

LEA EAX, [EBX*8]

This will move the effective address EBX * 8 into EAX, not what is found in that location. As you can see, also, it is possible to multiply by factors of two (scaling) while a MOV is limited to adding/subtracting.

故事未完 2024-08-16 12:59:55

8086 有一大堆指令,它们接受寄存器操作数和有效地址,执行一些计算以计算该有效地址的偏移部分,并执行涉及计算地址引用的寄存器和内存的一些操作。除了跳过实际的内存操作之外,让该系列中的指令之一表现如上是相当简单的。因此,指令:

mov ax,[bx+si+5]
lea ax,[bx+si+5]

在内部实现几乎相同。区别在于跳过了一个步骤。两条指令的工作原理都是这样的:

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

至于为什么英特尔认为这条指令值得包含在内,我不太确定,但它的实施成本低廉这一事实将是一个重要因素。另一个因素是英特尔的汇编器允许相对于 BP 寄存器定义符号。如果 fnord 被定义为 BP 相关符号(例如 BP+8),人们可以说:

mov ax,fnord  ; Equivalent to "mov ax,[BP+8]"

如果有人想使用类似 < code>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

注意,忘记世界“偏移量”会导致位置 [BP+8],而不是值 8,被添加到 DI。哎呀。

The 8086 has a large family of instructions that accept a register operand and an effective address, perform some computations to compute the offset part of that effective address, and perform some operation involving the register and the memory referred to by the computed address. It was fairly simple to have one of the instructions in that family behave as above except for skipping that actual memory operation. Thus, the instructions:

mov ax,[bx+si+5]
lea ax,[bx+si+5]

were implemented almost identically internally. The difference is a skipped step. Both instructions work something like:

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

As for why Intel thought this instruction was worth including, 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 assembler allowed symbols to be defined relative to the BP register. If fnord was defined as a BP-relative symbol (e.g. BP+8), one could say:

mov ax,fnord  ; Equivalent to "mov ax,[BP+8]"

If one wanted to use something like stosw to store data to a BP-relative address, being able to say

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

was more convenient than:

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

Note that forgetting the world "offset" would cause the contents of location [BP+8], rather than the value 8, to be added to DI. Oops.

在梵高的星空下 2024-08-16 12:59:55

LEA(加载有效地址)指令是一种获取来自任何 Intel 处理器内存寻址模式的地址的方法。

也就是说,如果我们有这样的数据移动:

MOV EAX, <MEM-OPERAND>

它将指定内存位置的内容移动到目标寄存器中。

如果我们将 MOV 替换为 LEA,则内存位置的地址将通过 以完全相同的方式计算> 寻址表达式。但我们不是将内存位置的内容放入目的地,而是将该位置本身放入目的地。

LEA 不是一个特定的算术指令;它是一种拦截由任何一种处理器的内存寻址模式产生的有效地址的方法。

例如,我们可以在一个简单的直接地址上使用LEA。根本不涉及算术:

MOV EAX, GLOBALVAR   ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR   ; fetch the address of GLOBALVAR into EAX.

这是有效的;我们可以在Linux提示符下测试一下:

$ as
LEA 0, %eax
$ objdump -d a.out

a.out:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:
   0:   8d 04 25 00 00 00 00    lea    0x0,%eax

这里没有添加缩放值,也没有偏移量。零被移入 EAX。我们也可以使用带有立即数操作数的 MOV 来做到这一点。

这就是为什么那些认为LEA中的括号是多余的人是严重错误的;括号不是 LEA 语法,而是寻址模式的一部分。

LEA 在硬件层面是真实的。生成的指令对实际寻址模式进行编码,处理器执行该指令直至计算地址。然后,它将该地址移动到目的地,而不是生成内存引用。 (由于任何其他指令中寻址模式的地址计算对 CPU 标志没有影响,因此 LEA 对 CPU 标志没有影响。)

与从地址零加载值相比:

$ as
movl 0, %eax
$ objdump -d a.out | grep mov
   0:   8b 04 25 00 00 00 00    mov    0x0,%eax

这是一种非常相似的编码, 看?只是LEA8d 已更改为8b

当然,这种LEA编码比将立即零移入EAX要长:

$ as
movl $0, %eax
$ objdump -d a.out | grep mov
   0:   b8 00 00 00 00          mov    $0x0,%eax

LEA没有理由排除这种可能性,尽管只是因为有一个更短的替代方案;它只是以正交方式与可用的寻址模式组合。

The LEA (Load Effective Address) instruction is a way of obtaining the address which arises from any of the Intel processor's memory addressing modes.

That is to say, if we have a data move like this:

MOV EAX, <MEM-OPERAND>

it moves the contents of the designated memory location into the target register.

If we replace the MOV by LEA, then the address of the memory location is calculated in exactly the same way by the <MEM-OPERAND> addressing expression. But instead of the contents of the memory location, we get the location itself into the destination.

LEA is not a specific arithmetic instruction; it is a way of intercepting the effective address arising from any one of the processor's memory addressing modes.

For instance, we can use LEA on just a simple direct address. No arithmetic is involved at all:

MOV EAX, GLOBALVAR   ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR   ; fetch the address of GLOBALVAR into EAX.

This is valid; we can test it at the Linux prompt:

$ as
LEA 0, %eax
$ objdump -d a.out

a.out:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:
   0:   8d 04 25 00 00 00 00    lea    0x0,%eax

Here, there is no addition of a scaled value, and no offset. Zero is moved into EAX. We could do that using MOV with an immediate operand also.

This is the reason why people who think that the brackets in LEA are superfluous are severely mistaken; the brackets are not LEA syntax but are part of the addressing mode.

LEA is real at the hardware level. The generated instruction encodes the actual addressing mode and the processor carries it out to the point of calculating the address. Then it moves that address to the destination instead of generating a memory reference. (Since the address calculation of an addressing mode in any other instruction has no effect on CPU flags, LEA has no effect on CPU flags.)

Contrast with loading the value from address zero:

$ as
movl 0, %eax
$ objdump -d a.out | grep mov
   0:   8b 04 25 00 00 00 00    mov    0x0,%eax

It's a very similar encoding, see? Just the 8d of LEA has changed to 8b.

Of course, this LEA encoding is longer than moving an immediate zero into EAX:

$ as
movl $0, %eax
$ objdump -d a.out | grep mov
   0:   b8 00 00 00 00          mov    $0x0,%eax

There is no reason for LEA to exclude this possibility though just because there is a shorter alternative; it's just combining in an orthogonal way with the available addressing modes.

゛清羽墨安 2024-08-16 12:59:55

正如现有答案提到的,LEA 的优点是无需访问内存即可执行内存寻址算术,将算术结果保存到不同的寄存器,而不是简单形式的加法指令。真正的底层性能优势在于,现代处理器具有独立的 LEA ALU 单元和端口,用于有效地址生成(包括 LEA 和其他内存引用地址),这意味着 LEA 中的算术运算code> 和 ALU 中的其他正常算术运算可以在一个内核中并行完成。

有关 LEA 单元的一些详细信息,请查看 Haswell 架构的这篇文章:
http://www.realworldtech.com/haswell-cpu/4/

另一个重要的其他答案中没有提到的一点是LEA REG,[MemoryAddress]指令是PIC(位置无关代码),它对该指令中的PC相对地址进行编码以引用MemoryAddress。这与 MOV REG, MemoryAddress 不同,MOV REG, MemoryAddress 编码相对虚拟地址,并且需要在现代操作系统中重新定位/修补(例如 ASLR 是常见功能)。因此LEA可用于将此类非PIC转换为PIC。

As the existing answers mentioned, LEA has the advantages of performing memory addressing arithmetic without accessing memory, saving the arithmetic result to a different register instead of the simple form of add instruction. The real underlying performance benefit is that modern processor has a separate LEA ALU unit and port for effective address generation (including LEA and other memory reference address), this means the arithmetic operation in LEA and other normal arithmetic operation in ALU could be done in parallel in one core.

Check this article of Haswell architecture for some details about LEA unit:
http://www.realworldtech.com/haswell-cpu/4/

Another important point which is not mentioned in other answers is LEA REG, [MemoryAddress] instruction is PIC (position independent code) which encodes the PC relative address in this instruction to reference MemoryAddress. This is different from MOV REG, MemoryAddress which encodes relative virtual address and requires relocating/patching in modern operating systems (like ASLR is common feature). So LEA can be used to convert such non PIC to PIC.

傲影 2024-08-16 12:59:55

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

It seems that lots of answers already complete, I'd like to add one more example code for showing how the lea and move instruction work differently when they have the same expression format.

To make a long story short, lea instruction and mov instructions both can be used with the parentheses enclosing the src operand of the instructions. When they are enclosed with the (), the expression in the () is calculated in the same way; however, two instructions will interpret the calculated value in the src operand in a different way.

Whether the expression is used with the lea or mov, the src value is calculated as below.

D ( Rb, Ri, S ) => (Reg[Rb]+S*Reg[Ri]+ D)

However, when it is used with the mov instruction, it tries to access the value pointed to by the address generated by the above expression and store it to the destination.

In contrast of it, when the lea instruction is executed with the above expression, it loads the generated value as it is to the destination.

The below code executes the lea instruction and mov instruction with the same parameter. However, to catch the difference, I added a user-level signal handler to catch the segmentation fault caused by accessing a wrong address as a result of mov instruction.

Example code

#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
    );
}

Execution result

Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed
一桥轻雨一伞开 2024-08-16 12:59:55

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

The LEA instruction can be used to avoid time consuming calculations of effective addresses by the CPU. If an address is used repeatedly it is more effective to store it in a register instead of calculating the effective address every time it is used.

忆梦 2024-08-16 12:59:55

LEA 与 MOV(回复原来的问题)

LEA 不是一个时髦的 MOV。当您使用MOV时,它会计算地址并访问内存。 LEA 只是计算地址,它并不实际访问内存。这就是区别。

在 8086 及更高版本中,LEA 仅将最多两个源寄存器和一个立即值设置到目标寄存器。例如,lea bp, [bx+si+3]bxsi 加 3 的和设置为 bp 寄存器。您不能通过MOV 实现此计算,将结果保存到寄存器中。

80386处理器引入了一系列缩放模式,其中可以将索引寄存器值乘以有效的缩放因子以获得位移。有效的比例因子为 1、2、4 和 8。因此,您可以使用 lea ebp, [ebx+esi*8+3] 等指令。

LDS& LES(可选进一步阅读)

LEA相比,有指令LDSLES,相反,将值从内存加载到一对寄存器:一个段寄存器(DSES)和一个通用寄存器。其他寄存器也有版本:用于 FSGS< 的 LFSLGSLSS分别是 /code> 和 SS 段寄存器(在 80386 中引入)。

因此,这些指令加载“远”指针 - 一个由 16 位段选择器和 16 位(或 32 位,取决于模式)偏移量组成的指针,因此总远指针大小为 32 位16位模式下为48位,32位模式下为48位。

这些是 16 位模式的方便指令,无论是 16 位实模式还是 16 位保护模式。

在32位模式下,不需要这些指令,因为操作系统将所有段基址设置为零(平面内存模型),因此不需要加载段寄存器。我们只使用32位指针,而不是48位。

在64位模式下,这些指令没有实现。它们的操作码会产生访问冲突中断(异常)。自从英特尔实施 VEX - “矢量扩展 - (AVX),英特尔采用了 LDSLES 操作码,并开始将它们用于 VEX 前缀。正如 Peter Cordes 指出的那样,这就是为什么在 32 位模式下只能访问 x/ymm0..7(引用):“VEX 前缀经过精心设计,仅与 32 位模式下的 LDS 和 LES 的无效编码重叠,其中 R̅ X̅ B̅ 都是1. 这就是为什么 VEX 前缀中的一些位被反转的原因”。

LEA vs MOV (reply to the original question)

LEA is not a funky MOV. When you use MOV, it calculates the address and accesses the memory. LEA just calculates the address, it doesn't actually access memory. This is the difference.

In 8086 and later, LEA just sets a sum of up to two source registers and an immediate value to a destination register. For example, lea bp, [bx+si+3] sets to the bp register the sum of bx plus si plus 3. You cannot achieve this calculation to save the result to a register with MOV.

The 80386 processor introduced a series of scaling modes, in which the index register value can be multiplied by a valid scaling factor to obtain the displacement. The valid scale factors are 1, 2, 4, and 8. Therefore, you can use instructions like lea ebp, [ebx+esi*8+3].

LDS & LES (optional further reading)

In contrast to LEA, there are instructions LDS and LES, that, to the contrary, load values from memory to the pair of registers: one segment register (DS or ES) and one general register. There are also versions for the other registers: LFS, LGS and LSS for FS, GS and SS segment registers, respectively (introduced in 80386).

So, these instructions load "far" pointer - a pointer consisting of a 16-bit segment selector and a 16-bit (or a 32-bit, depending on the mode) offset, so the total far pointer size was 32-bit in 16-bit mode and 48-bit in 32-bit mode.

These are handy instructions for 16-bit mode, be it 16-bit real mode or 16-bit protected mode.

Under 32-bit mode, there is no need in these instructions since OSes set all segment bases to zero (flat memory model), so there is no need to load segment registers. We just use 32-bit pointers, not 48.

Under 64-bit modes, these instructions are not implemented. Their opcodes give access violation interrupt (exception). Since Intel's implementation of VEX - "vector extensions - (AVX), Intel took their opcodes of LDS and LES and started using them for VEX prefixes. As Peter Cordes pointed out, that is why only x/ymm0..7 are accessible in 32-bit mode (quote): "the VEX prefixes were carefully designed to only overlap with invalid encodings of LDS and LES in 32-bit mode, where R̅ X̅ B̅ are all 1. That's why some of the bits are inverted in VEX prefixes".

无法回应 2024-08-16 12:59:55

这是一个例子。

// 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;
}

使用 -O(优化)作为编译器选项,gcc 将为指定的代码行找到 lea 指令。

Here is an example.

// 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;
}

With -O (optimize) as compiler option, gcc will find the lea instruction for the indicated code line.

复古式 2024-08-16 12:59:55

LEA :只是一个“算术”指令。MOV

在操作数之间传输数据,但 lea 只是计算

LEA : just an "arithmetic" instruction..

MOV transfers data between operands but lea is just calculating

人间☆小暴躁 2024-08-16 12:59:55

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

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

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

底线

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

LEA, AX'| [BX]

对 AX 有相同的效果,但对状态标志没有影响。
(这是 ciasdis 表示法。)

All normal "calculating" instructions like adding multiplication, exclusive or set the status flags like zero, sign. If you use a complicated address, AX xor:= mem[0x333 +BX + 8*CX] the flags are set according to the xor operation.

Now you may want to use the address multiple times. Loading such an addres into a register is never intended to set status flags and luckily it doesn't. The phrase "load effective address" makes the programmer aware of that. That is where the weird expression comes from.

It is clear that once the processor is capable of using the complicated address to process its content, it is capable of calculating it for other purposes. Indeed it can be used to perform a transformation x <- 3*x+1 in one instruction. This is a general rule in assembly programming: Use the instructions however it rocks your boat.
The only thing that counts is whether the particular transformation embodied by the instruction is useful for you.

Bottom line

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

and

LEA, AX'| [BX]

have the same effect on AX but not on the status flags.
(This is ciasdis notation.)

噩梦成真你也成魔 2024-08-16 12:59:55

如果有人已经提到过,请原谅我,但如果有人想知道 x86 的糟糕旧时光,当时内存分段仍然相关:你总是会从这两条指令中得到相同的结果:

LEA AX, DS:[0x1234]

并且

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.html - 8086 的架构师写了一本关于指令集的书,现在在他的网站上免费。关于 LEA 的部分提到了他的一些设计意图。

Forgive me if someone already mentioned, but in case anyone's wondering about the bad old days of x86 when memory segmentation was still relevant: you will always get the same results from these two instructions:

LEA AX, DS:[0x1234]

and

LEA AX, CS:[0x1234]

The "effective address" is just the offset part of the seg:off logical address. In this case, 0x1234.

LEA does not add the segment base. That would defeat one of the original use-cases, for doing address math to get a pointer (offset) you could actually dereference. Such as lea bx, [array + si]. If that added DS base to give a linear address, a later mov ax, [bx] would add the DS base again.
Also, the 20-bit result would often not fit in a 16-bit register.

See https://www.stevemorse.org/8086/index.html - the architect of 8086 wrote a book about the instruction set, and it's now free on his web site. The section on LEA mentions some of his design intent.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文