Команда LEA
Команда LEA в Ассемблере вычисляет эффективный адрес ИСТОЧНИКА и помещает его в ПРИЁМНИК. Синтаксис:
LEA ПРИЁМНИК, ИСТОЧНИК
После выполнения этой команды флаги не изменяются.
Обратите внимание, что ИСТОЧНИКОМ может быть только переменная (ячейка памяти), а ПРИЁМНИКОМ — только регистр (но не сегментный).
Что такое эффективный адрес
Прежде чем продолжить рассказ об инструкции LEA, напомню, что такое эффективный адрес.
Не люблю я иностранные слова — они только путаницу вносят. Но так уж повелось на Руси — если иностранные словечки не употребляешь, значит — лох. В итоге часто люди сами не понимают, что говорят, а исконно русские и понятные слова уже давно забыты и найти подходящую замену иностранному слову бывает непросто (даже мне при всём желании))).
Так вот, слово “эффективный” можно перевести на русский как “действенный”, “действующий”, “настоящий”. Что касается программистской терминологии, то в некоторых источниках вместо “эффективный адрес” встречается словосочетание “текущий адрес” или даже “виртуальный адрес”.
Слишком глубоко в адресацию погружаться не будем. Если вы совершенно далеки от этого, то можете изучить мою контрольную работу по этой теме университетских времён (эх, давно это было…)
Ну а если кратко, то эффективный (текущий) адрес — это
БАЗА + СМЕЩЕНИЕ + ИНДЕКС
где БАЗА — это базовый адрес, находящийся в регистре (при 16-разрядной адресации могут использоваться только регистры BX или BP); СМЕЩЕНИЕ (или ОТКЛОНЕНИЕ — displacement) — это константа (число со знаком), заданная в команде; ИНДЕКС — значение индексного регистра (при 16-разрядной адресации могут использоваться только регистры SI или DI).
Любая из частей эффективного адреса может отсутствовать (например, необязательно указывать СМЕЩЕНИЕ или ИНДЕКС), но обязательно должна присутствовать хотя бы одна часть (например, только БАЗА).
Вычисление эффективного адреса
Ну а теперь чуть подробнее о самой команде LEA. Как уже было сказано, она выполняет вычисление адреса в Ассемблере. В итоге в ПРИЁМНИК записывается адрес памяти (точнее, только смещение).
С помощью команды LEA можно вычислить адрес переменной, которая описана сложным способом адресации (например, по базе с индексированием, что часто используется при работе с массивами и строками).
Если адрес 32-разрядный, а ПРИЁМНИК — 16-разрядный, то старшая половина вычисленного адреса теряется. Если наоборот, ПРИЁМНИК — 32-разрядный, а адрес 16-разрядный, то вычисленное смещение дополняется нулями.
Команда LEA позволяет определить текущее смещение косвенного операнда любого типа. Так как при косвенной адресации может использоваться один или два регистра общего назначения, то приходится каким-то образом вычислять текущее смещение операнда во время выполнения программы.
Команду LEA также удобно применять для определения адреса параметра, находящегося в стеке. Например, если в процедуре определяется локальный массив, то для работы с ним часто необходимо загрузить его смещение в индексный регистр (что как раз таки можно сделать командой LEA).
Оператор OFFSET позволяет определить смещение только при компиляции, и в отличие от него команда LEA может сделать это во время выполнения программы. Хотя в остальных случаях обычно вместо LEA используют MOV и OFFSET, то есть
LEA ПРИЁМНИК, ИСТОЧНИК
это то же самое, что и
MOV ПРИЁМНИК, offset ИСТОЧНИК
При этом следует помнить об указанных выше ограничениях применения оператора OFFSET.
Ещё один пример:
После выполнения этой программы в AX будет записано значение 0104h. Команда LEA занимает 3 байта, команда RET занимает 1 байт. Мы начинаем с адреса 100h, поэтому адрес переменной Х — это 104h (100h + 3 + 1 = 104h). Команда LEA вычислила этот адрес и записала его в указанный регистр (в нашем случае в АХ).
Команда LEA в арифметических операциях
Инструкция LEA часто используется для арифметических операций, таких как умножение и сложение. Преимущество такого способа в том, что команда LEA занимает меньше места, чем команды арифметических операций. Кроме того, в отличие от последних, она не изменяет флаги. Примеры:
Обратите внимание на то, что адресацию со смещением, где используется знак умножения (*), не поддерживает эмулятор emu8086 (возможно, некоторые другие эмуляторы тоже). Поэтому в данном эмуляторе первый пример не будет работать правильно. Второй же пример (сложение), будет работать.
Напоследок, как всегда, о происхождении аббревиатуры LEA.
LEA — Load Effective Address (загрузить эффективный адрес).
What's the purpose of the LEA instruction?
For me, it just seems like a funky MOV. What’s its purpose and when should I use it?
![]()
17 Answers 17
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 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:
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 &:
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
which will load the address in ESI .
From the "Zen of Assembly" by Abrash:
- the ability to perform addition with either two or three operands, and
- the ability to store the result in any register; not just one of the source operands.
And LEA does not alter the flags.
- 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.
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.
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).
Maybe just another thing about LEA instruction. You can also use LEA for fast multiplying registers by 3, 5 or 9.
![]()
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 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.
![]()
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:
This will move the content of what ESP+4 points to into EAX .
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.
![]()
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. If fnord was defined as a BP -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 say
was 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 to DI . Oops.
![]()
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 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:
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 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:
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 :
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.
![]()
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.
![]()
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
Execution result
![]()
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.
Here is an example.
With -O (optimize) as compiler option, gcc will find the lea instruction for the indicated code line.
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".
LEA — Load Effective Address
Computes the effective address of the second operand (the source operand) and stores it in the first operand (destination operand). The source operand is a memory address (offset part) specified with one of the processors addressing modes; the destination operand is a general-purpose register. The address-size and operand-size attributes affect the action performed by this instruction, as shown in the following table. The operand-size attribute of the instruction is determined by the chosen register; the address-size attribute is determined by the attribute of the code segment.
| Operand Size | Address Size | Action Performed |
|---|---|---|
| 16 | 16 | 16-bit effective address is calculated and stored in requested 16-bit register destination. |
| 16 | 32 | 32-bit effective address is calculated. The lower 16 bits of the address are stored in the requested 16-bit register destination. |
| 32 | 16 | 16-bit effective address is calculated. The 16-bit address is zero-extended and stored in the requested 32-bit register destination. |
| 32 | 32 | 32-bit effective address is calculated and stored in the requested 32-bit register destination. |
Table 3-54. Non-64-bit Mode LEA Operation with Address and Operand Size Attributes
Different assemblers may use different algorithms based on the size attribute and symbolic reference of the source operand.
In 64-bit mode, the instruction’s destination operand is governed by operand size attribute, the default operand size is 32 bits. Address calculation is governed by address size attribute, the default address size is 64-bits. In 64-bit mode, address size of 16 bits is not encodable. See Table 3-55.
x86 Assembly/Data Transfer
Some of the most important and most frequently used instructions are those that move data. Without them, there would be no way for registers or memory to even have anything in them to operate on.
Contents
Data transfer instructions [ edit | edit source ]
Move [ edit | edit source ]
| mov src, dest | GAS Syntax |
| mov dest, src | Intel Syntax |
mov stands for move. Despite its name the mov instruction copies the src operand into the dest operand. After the operation both operands contain the same contents.
Operands [ edit | edit source ]
| src operand | dest operand | ||
|---|---|---|---|
| immediate value | register | memory | |
| Yes (into larger register) |
Yes (same size) |
Yes (register determines size of retrieved memory) |
register |
| Yes (up to 32-bit values) |
Yes | No | memory |
Modified flags [ edit | edit source ]
- No FLAGS are modified by this instruction
Example [ edit | edit source ]
Data swap [ edit | edit source ]
| xchg src, dest | GAS Syntax |
| xchg dest, src | Intel Syntax |
xchg stands for exchange. The xchg instruction swaps the src operand with the dest operand. It is like doing three mov operations:
- from dest to a temporary (another register),
- then from src to dest , and finally
- from the temporary storage to src ,
except that no register needs to be reserved for temporary storage.
Operands [ edit | edit source ]
Any combination of register or memory operands, except that at most one operand may be a memory operand. You cannot exchange two memory blocks.
Modified Flags [ edit | edit source ]
Application [ edit | edit source ]
If one of the operands is a memory address, then the operation has an implicit lock prefix, that is, the exchange operation is atomic. This can have a large performance penalty.
However, on some platforms exchanging two (non-partial) registers will trigger the register renamer. The register renamer is a unit in that merely renames registers, so no data actually have to be moved. This is super fast (branded as “zero-latency”). Renaming registers could be useful since
- some instructions either require certain operands to be located in specific register, but data will be needed later on,
- or encoding some opcodes is shorter if one of the operands is the accumulator register.
It is also worth noting that the common nop (no operation) instruction, 0x90 , is the opcode for xchgl %eax , %eax .
Data swap based on comparison [ edit | edit source ]
| cmpxchg arg2, arg1 | GAS Syntax |
| cmpxchg arg1, arg2 | Intel Syntax |
cmpxchg stands for compare and exchange. Exchange is misleading as no data are actually exchanged.
The cmpxchg instruction has one implicit operand: the al / ax / eax depending on the size of arg1 .
- The instruction compares arg1 to al / ax / eax .
- If they are equal, arg1 becomes arg2 . ( arg1 = arg2 )
- Otherwise, al / ax / eax becomes arg1 .
Unlike xchg there is no implicit lock prefix, and if the instruction is required to be atomic, lock has to be prefixed.
Operands [ edit | edit source ]
arg2 has to be a register. arg1 may be either a register or memory operand.
Modified flags [ edit | edit source ]
- ZF ≔ arg1 = ( al | ax | eax ) [depending on arg1 ’s size]
- CF , PF , AF , SF , OF are altered, too.
Application [ edit | edit source ]
The following example shows how to use the cmpxchg instruction to create a spin lock which will be used to protect the result variable. The last thread to grab the spin lock will get to set the final value of result :
In order to assemble, link and run the program we need to do the following:
Move with zero extend [ edit | edit source ]
| movz src, dest | GAS Syntax |
| movzx dest, src | Intel Syntax |
movz stands for move with zero extension. Like the regular mov the movz instruction copies data from the src operand to the dest operand, but the remaining bits in dest that are not provided by src are filled with zeros. This instruction is useful for copying a small, unsigned value to a bigger register.
Operands [ edit | edit source ]
Dest has to be a register, and src can be either another register or a memory operand. For this operation to make sense dest has to be larger than src .
Modified flags [ edit | edit source ]
Example [ edit | edit source ]
Move with sign extend [ edit | edit source ]
| movs src, dest | GAS Syntax |
| movsx dest, src | Intel Syntax |
movsx stands for move with sign extension. The movsx instruction copies the src operand in the dest operand and pads the remaining bits not provided by src with the sign bit (the MSB ) of src .
This instruction is useful for copying a signed small value to a bigger register.
Operands [ edit | edit source ]
Modified Flags [ edit | edit source ]
movsx does not modify any flags, either.
Example [ edit | edit source ]
Move String [ edit | edit source ]
The movsb instruction copies one byte from the memory location specified in esi to the location specified in edi . If the direction flag is cleared, then esi and edi are incremented after the operation. Otherwise, if the direction flag is set, then the pointers are decremented. In that case the copy would happen in the reverse direction, starting at the highest address and moving toward lower addresses until ecx is zero.
Operands [ edit | edit source ]
There are no explicit operands, but
- ecx determines the number of iterations,
- esi specifies the source address,
- edi the destination address, and
- DF is used to determine the direction (it can be altered by the cld and std instruction).
Modified flags [ edit | edit source ]
No flags are modified by this instruction.
Example [ edit | edit source ]
The movsw instruction copies one word (two bytes) from the location specified in esi to the location specified in edi . It basically does the same thing as movsb , except with words instead of bytes.
Modified flags
- No FLAGS are modified by this instruction
Load Effective Address [ edit | edit source ]
| lea src, dest | GAS Syntax |
| lea dest, src | Intel Syntax |
lea stands for load effective address. The lea instruction calculates the address of the src operand and loads it into the dest operand.
Operands [ edit | edit source ]
- Immediate
- Register
- Memory
- Register
Modified flags [ edit | edit source ]
- No FLAGS are modified by this instruction
Note [ edit | edit source ]
Load Effective Address calculates its src operand in the same way as the mov instruction does, but rather than loading the contents of that address into the dest operand, it loads the address itself.
lea can be used not only for calculating addresses, but also general-purpose unsigned integer arithmetic (with the caveat and possible advantage that FLAGS are unmodified). This can be quite powerful, since the src operand can take up to 4 parameters: base register, index register, scalar multiplier and displacement, e.g. [eax + edx*4 -4] (Intel syntax) or -4(%eax, %edx, 4) (GAS syntax). The scalar multiplier is limited to constant values 1, 2, 4, or 8 for byte, word, double word or quad word offsets respectively. This by itself allows for multiplication of a general register by constant values 2, 3, 4, 5, 8 and 9, as shown below (using NASM syntax):
Conditional Move [ edit | edit source ]
| cmovcc src, dest | GAS Syntax |
| cmovcc dest, src | Intel Syntax |
cmov stands for conditional move. It behaves like mov but the execution depends on various flags. There are following instruction available:
| … = 1 | … = 0 | |
|---|---|---|
| ZF | cmovz , cmove | cmovnz , cmovne |
| OF | cmovo | cmovno |
| SF | cmovs | cmovns |
| CF | cmovc , cmovb , cmovnae | cmovnc , cmovnb , cmovae |
| CF ∨ ZF | cmovbe | N/A |
| PF | cmovp , cmovpe | cmovnp , cmovpo |
| SF = OF | cmovge , cmovnl | cmovnge , cmovl |
| ZF ∨ SF ≠ OF | cmovng , cmovle | N/A |
| CF ∨ ZF | cmova | N/A |
| ¬ CF | SF = OF | |
| ¬ ZF | cmovnbe , cmova | cmovg , cmovnle |