LEA指令的目的是什么?
对我来说,这似乎只是一部时髦的 MOV。它的用途是什么?我应该何时使用它?
For me, it just seems like a funky MOV. What's its purpose and when should I use it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
对我来说,这似乎只是一部时髦的 MOV。它的用途是什么?我应该何时使用它?
For me, it just seems like a funky MOV. What's its purpose and when should I use it?
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
接受
或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
发布评论
评论(17)
正如其他人指出的那样,LEA(加载有效地址)经常被用作进行某些计算的“技巧”,但这不是它的主要目的。 x86 指令集旨在支持 Pascal 和 C 等高级语言,其中数组(尤其是整数数组或小型结构体)很常见。例如,考虑一个表示 (x, y) 坐标的结构:
现在想象这样一个语句:
其中
points[]
是一个Point
数组。假设数组的基数已在EBX
中,变量i
在EAX
中,并且xcoord
和 < code>ycoord 各为 32 位(因此ycoord
位于结构中的偏移 4 个字节处),该语句可以编译为:它将把
y
放入EDX
。比例因子为 8 是因为每个点的大小为 8 字节。现在考虑与“地址”运算符 & 一起使用的相同表达式:在这种情况下,您不需要 ycoord 的值,而是它的地址。这就是
LEA
(加载有效地址)的用武之地。编译器可以生成将地址加载到
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:
Now imagine a statement like:
where
points[]
is an array ofPoint
. Assuming the base of the array is already inEBX
, and variablei
is inEAX
, andxcoord
andycoord
are each 32 bits (soycoord
is at offset 4 bytes in the struct), this statement can be compiled to:which will land
y
inEDX
. The scale factor of 8 is because eachPoint
is 8 bytes in size. Now consider the same expression used with the "address of" operator &:In this case, you don't want the value of
ycoord
, but its address. That's whereLEA
(load effective address) comes in. Instead of aMOV
, the compiler can generatewhich will load the address in
ESI
.来自 Abrash 的“组装之禅”:
并且
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:
And
LEA
does not alter the flags.Examples
LEA EAX, [ EAX + EBX + 1234567 ]
calculatesEAX + EBX + 1234567
(that's three operands)LEA EAX, [ EBX + ECX ]
calculatesEBX + ECX
without overriding either with the result.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 ]
andINC EAX
is that the latter changesEFLAGS
but the former does not; this preservesCMP
state.LEA
指令的另一个重要特点是,在通过算术指令计算地址时,它不会改变CF
和ZF
等条件码就像ADD
或MUL
一样。此功能降低了指令之间的依赖性级别,从而为编译器或硬件调度程序的进一步优化腾出了空间。Another important feature of the
LEA
instruction is that it does not alter the condition codes such asCF
andZF
, while computing the address by arithmetic instructions likeADD
orMUL
does. This feature decreases the level of dependency among instructions and thus makes room for further optimization by the compiler or hardware scheduler.尽管有所有的解释,LEA 是一种算术运算:
只是它的名字对于移位+加法运算来说是极其愚蠢的。其原因已经在最高评价的答案中得到了解释(即它被设计为直接映射高级内存引用)。
Despite all the explanations, LEA is an arithmetic operation:
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).
也许这只是 LEA 教学的另一件事。
您还可以使用 LEA 将寄存器快速乘以 3、5 或 9。
Maybe just another thing about LEA instruction.
You can also use LEA for fast multiplying registers by 3, 5 or 9.
lea
是“加载有效地址”的缩写。它将源操作数的位置引用的地址加载到目标操作数。例如,您可以使用它来:使用一条指令进一步移动
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:to move
ebx
pointereax
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.使用
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 aMOV
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 aMOV
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 intoEAX
.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 aMOV
is limited to adding/subtracting.8086 有一大堆指令,它们接受寄存器操作数和有效地址,执行一些计算以计算该有效地址的偏移部分,并执行涉及计算地址引用的寄存器和内存的一些操作。除了跳过实际的内存操作之外,让该系列中的指令之一表现如上是相当简单的。因此,指令:
在内部实现几乎相同。区别在于跳过了一个步骤。两条指令的工作原理都是这样的:
至于为什么英特尔认为这条指令值得包含在内,我不太确定,但它的实施成本低廉这一事实将是一个重要因素。另一个因素是英特尔的汇编器允许相对于
BP
寄存器定义符号。如果fnord
被定义为BP
相关符号(例如BP+8
),人们可以说:如果有人想使用类似 < code>stosw 将数据存储到 BP 相对地址,可以说
比:
注意,忘记世界“偏移量”会导致位置
[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:
were implemented almost identically internally. The difference is a skipped step. Both instructions work something like:
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. Iffnord
was defined as aBP
-relative symbol (e.g.BP+8
), one could say:If one wanted to use something like
stosw
to store data to a BP-relative address, being able to saywas more convenient than:
Note that forgetting the world "offset" would cause the contents of location
[BP+8]
, rather than the value 8, to be added toDI
. Oops.LEA(加载有效地址)指令是一种获取来自任何 Intel 处理器内存寻址模式的地址的方法。
也就是说,如果我们有这样的数据移动:
它将指定内存位置的内容移动到目标寄存器中。
如果我们将
MOV
替换为LEA
,则内存位置的地址将通过
以完全相同的方式计算> 寻址表达式。但我们不是将内存位置的内容放入目的地,而是将该位置本身放入目的地。LEA
不是一个特定的算术指令;它是一种拦截由任何一种处理器的内存寻址模式产生的有效地址的方法。例如,我们可以在一个简单的直接地址上使用
LEA
。根本不涉及算术:这是有效的;我们可以在Linux提示符下测试一下:
这里没有添加缩放值,也没有偏移量。零被移入 EAX。我们也可以使用带有立即数操作数的 MOV 来做到这一点。
这就是为什么那些认为
LEA
中的括号是多余的人是严重错误的;括号不是LEA
语法,而是寻址模式的一部分。LEA 在硬件层面是真实的。生成的指令对实际寻址模式进行编码,处理器执行该指令直至计算地址。然后,它将该地址移动到目的地,而不是生成内存引用。 (由于任何其他指令中寻址模式的地址计算对 CPU 标志没有影响,因此
LEA
对 CPU 标志没有影响。)与从地址零加载值相比:
这是一种非常相似的编码, 看?只是
LEA
的8d
已更改为8b
。当然,这种
LEA
编码比将立即零移入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:
it moves the contents of the designated memory location into the target register.
If we replace the
MOV
byLEA
, 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:This is valid; we can test it at the Linux prompt:
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 notLEA
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:
It's a very similar encoding, see? Just the
8d
ofLEA
has changed to8b
.Of course, this
LEA
encoding is longer than moving an immediate zero intoEAX
: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.正如现有答案提到的,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 (includingLEA
and other memory reference address), this means the arithmetic operation inLEA
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 referenceMemoryAddress
. This is different fromMOV REG, MemoryAddress
which encodes relative virtual address and requires relocating/patching in modern operating systems (like ASLR is common feature). SoLEA
can be used to convert such non PIC to PIC.似乎很多答案已经完成,我想再添加一个示例代码,以展示 lea 和 move 指令在具有相同的表达式格式时如何不同地工作。
长话短说,lea 指令和 mov 指令都可以与指令的 src 操作数括起来的括号一起使用。当它们被()括起来时,()中的表达式以同样的方式计算;然而,两条指令将以不同的方式解释 src 操作数中的计算值。
无论该表达式与 lea 还是 mov 一起使用,src 值的计算方式如下。
但是,当它与 mov 指令一起使用时,它会尝试访问由上述表达式生成的地址所指向的值,并且将其存储到目的地。
与此相反,当使用上述表达式执行 lea 指令时,它将生成的值按原样加载到目的地。
下面的代码使用相同的参数执行 lea 指令和 mov 指令。然而,为了捕捉差异,我添加了一个用户级信号处理程序来捕捉由于 mov 指令访问错误地址而导致的分段错误。
示例代码
执行结果
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.
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
Execution result
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.
LEA 与 MOV(回复原来的问题)
LEA
不是一个时髦的MOV
。当您使用MOV
时,它会计算地址并访问内存。LEA
只是计算地址,它并不实际访问内存。这就是区别。在 8086 及更高版本中,
LEA
仅将最多两个源寄存器和一个立即值设置到目标寄存器。例如,lea bp, [bx+si+3]
将bx
加si
加 3 的和设置为 bp 寄存器。您不能通过MOV
实现此计算,将结果保存到寄存器中。80386处理器引入了一系列缩放模式,其中可以将索引寄存器值乘以有效的缩放因子以获得位移。有效的比例因子为 1、2、4 和 8。因此,您可以使用
lea ebp, [ebx+esi*8+3]
等指令。LDS& LES(可选进一步阅读)
与
LEA
相比,有指令LDS
和LES
,相反,将值从内存加载到一对寄存器:一个段寄存器(DS
或ES
)和一个通用寄存器。其他寄存器也有版本:用于FS
、GS< 的
LFS
、LGS
和LSS
分别是 /code> 和SS
段寄存器(在 80386 中引入)。因此,这些指令加载“远”指针 - 一个由 16 位段选择器和 16 位(或 32 位,取决于模式)偏移量组成的指针,因此总远指针大小为 32 位16位模式下为48位,32位模式下为48位。
这些是 16 位模式的方便指令,无论是 16 位实模式还是 16 位保护模式。
在32位模式下,不需要这些指令,因为操作系统将所有段基址设置为零(平面内存模型),因此不需要加载段寄存器。我们只使用32位指针,而不是48位。
在64位模式下,这些指令没有实现。它们的操作码会产生访问冲突中断(异常)。自从英特尔实施 VEX - “矢量扩展 - (AVX),英特尔采用了
LDS
和LES
操作码,并开始将它们用于 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 funkyMOV
. When you useMOV
, 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 ofbx
plussi
plus 3. You cannot achieve this calculation to save the result to a register withMOV
.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 instructionsLDS
andLES
, that, to the contrary, load values from memory to the pair of registers: one segment register (DS
orES
) and one general register. There are also versions for the other registers:LFS
,LGS
andLSS
forFS
,GS
andSS
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
andLES
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".这是一个例子。
使用 -O(优化)作为编译器选项,gcc 将为指定的代码行找到 lea 指令。
Here is an example.
With -O (optimize) as compiler option, gcc will find the lea instruction for the indicated code line.
LEA :只是一个“算术”指令。MOV
在操作数之间传输数据,但 lea 只是计算
LEA : just an "arithmetic" instruction..
MOV transfers data between operands but lea is just calculating
所有正常的“计算”指令,例如加法、异或设置状态标志,例如零、符号。如果您使用复杂的地址,
AX xor:= mem[0x333 +BX + 8*CX]
标志根据异或运算设置。现在您可能想多次使用该地址。将这样的地址加载到寄存器中绝不是为了设置状态标志,幸运的是它不会。短语“加载有效地址”使程序员意识到这一点。这就是奇怪表情的由来。
显然,一旦处理器能够使用复杂的地址来处理其内容,它就能够将其计算用于其他目的。事实上,它可以用于在一条指令中执行转换
x <- 3*x+1
。这是汇编编程中的一般规则:使用指令,但它会破坏你的船。唯一重要的是指令所体现的特定转换是否对您有用。
底线
和
对 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
and
have the same effect on AX but not on the status flags.
(This is ciasdis notation.)
如果有人已经提到过,请原谅我,但如果有人想知道 x86 的糟糕旧时光,当时内存分段仍然相关:你总是会从这两条指令中得到相同的结果:
并且
“有效地址”只是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:
and
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 latermov 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.