What is the difference between MOV and LEA?
Solution 1
LEA
means Load Effective AddressMOV
means Load Value
In short, LEA
loads a pointer to the item you're addressing whereas MOV loads the actual value at that address.
The purpose of LEA
is to allow one to perform a non-trivial address calculation and store the result [for later usage]
LEA ax, [BP+SI+5] ; Compute address of value
MOV ax, [BP+SI+5] ; Load value at that address
Where there are just constants involved, MOV
(through the assembler's constant calculations) can sometimes appear to overlap with the simplest cases of usage of LEA
. Its useful if you have a multi-part calculation with multiple base addresses etc.
Solution 2
In NASM syntax:
mov eax, var == lea eax, [var] ; i.e. mov r32, imm32
lea eax, [var+16] == mov eax, var+16
lea eax, [eax*4] == shl eax, 2 ; but without setting flags
In MASM syntax, use OFFSET var
to get a mov-immediate instead of a load.
Solution 3
The instruction MOV reg,addr means read a variable stored at address addr into register reg. The instruction LEA reg,addr means read the address (not the variable stored at the address) into register reg.
Another form of the MOV instruction is MOV reg,immdata which means read the immediate data (i.e. constant) immdata into register reg. Note that if the addr in LEA reg,addr is just a constant (i.e. a fixed offset) then that LEA instruction is essentially exactly the same as an equivalent MOV reg,immdata instruction that loads the same constant as immediate data.
Solution 4
None of the previous answers quite got to the bottom of my own confusion, so I'd like to add my own.
What I was missing is that lea
operations treat the use of parentheses different than how mov
does.
Think of C. Let's say I have an array of long
that I call array
. Now the expression array[i]
performs a dereference, loading the value from memory at the address array + i * sizeof(long)
[1].
On the other hand, consider the expression &array[i]
. This still contains the sub-expression array[i]
, but no dereferencing is performed! The meaning of array[i]
has changed. It no longer means to perform a deference but instead acts as a kind of a specification, telling &
what memory address we're looking for. If you like, you could alternatively think of the &
as "cancelling out" the dereference.
Because the two use-cases are similar in many ways, they share the syntax array[i]
, but the existence or absence of a &
changes how that syntax is interpreted. Without &
, it's a dereference and actually reads from the array. With &
, it's not. The value array + i * sizeof(long)
is still calculated, but it is not dereferenced.
The situation is very similar with mov
and lea
. With mov
, a dereference occurs that does not happen with lea
. This is despite the use of parentheses that occurs in both. For instance, movq (%r8), %r9
and leaq (%r8), %r9
. With mov
, these parentheses mean "dereference"; with lea
, they don't. This is similar to how array[i]
only means "dereference" when there is no &
.
An example is in order.
Consider the code
movq (%rdi, %rsi, 8), %rbp
This loads the value at the memory location %rdi + %rsi * 8
into the register %rbp
. That is: get the value in the register %rdi
and the value in the register %rsi
. Multiply the latter by 8, and then add it to the former. Find the value at this location and place it into the register %rbp
.
This code corresponds to the C line x = array[i];
, where array
becomes %rdi
and i
becomes %rsi
and x
becomes %rbp
. The 8
is the length of the data type contained in the array.
Now consider similar code that uses lea
:
leaq (%rdi, %rsi, 8), %rbp
Just as the use of movq
corresponded to dereferencing, the use of leaq
here corresponds to not dereferencing. This line of assembly corresponds to the C line x = &array[i];
. Recall that &
changes the meaning of array[i]
from dereferencing to simply specifying a location. Likewise, the use of leaq
changes the meaning of (%rdi, %rsi, 8)
from dereferencing to specifying a location.
The semantics of this line of code are as follows: get the value in the register %rdi
and the value in the register %rsi
. Multiply the latter by 8, and then add it to the former. Place this value into the register %rbp
. No load from memory is involved, just arithmetic operations [2].
Note that the only difference between my descriptions of leaq
and movq
is that movq
does a dereference, and leaq
doesn't. In fact, to write the leaq
description, I basically copy+pasted the description of movq
, and then removed "Find the value at this location".
To summarize: movq
vs. leaq
is tricky because they treat the use of parentheses, as in (%rsi)
and (%rdi, %rsi, 8)
, differently. In movq
(and all other instruction except lea
), these parentheses denote a genuine dereference, whereas in leaq
they do not and are purely convenient syntax.
[1] I've said that when array
is an array of long
, the expression array[i]
loads the value from the address array + i * sizeof(long)
. This is true, but there's a subtlety that should be addressed. If I write the C code
long x = array[5];
this is not the same as typing
long x = *(array + 5 * sizeof(long));
It seems that it should be based on my previous statements, but it's not.
What's going on is that C pointer addition has a trick to it. Say I have a pointer p
pointing to values of type T
. The expression p + i
does not mean "the position at p
plus i
bytes". Instead, the expression p + i
actually means "the position at p
plus i * sizeof(T)
bytes".
The convenience of this is that to get "the next value" we just have to write p + 1
instead of p + 1 * sizeof(T)
.
This means that the C code long x = array[5];
is actually equivalent to
long x = *(array + 5)
because C will automatically multiply the 5
by sizeof(long)
.
So in the context of this StackOverflow question, how is this all relevant? It means that when I say "the address array + i * sizeof(long)
", I do not mean for "array + i * sizeof(long)
" to be interpreted as a C expression. I am doing the multiplication by sizeof(long)
myself in order to make my answer more explicit, but understand that due to that, this expression should not be read as C. Just as normal math that uses C syntax.
[2] Side note: because all lea
does is arithmetic operations, its arguments don't actually have to refer to valid addresses. For this reason, it's often used to perform pure arithmetic on values that may not be intended to be dereferenced. For instance, cc
with -O2
optimization translates
long f(long x) {
return x * 5;
}
into the following (irrelevant lines removed):
f:
leaq (%rdi, %rdi, 4), %rax # set %rax to %rdi + %rdi * 4
ret
Solution 5
If you only specify a literal, there is no difference. LEA has more abilities, though, and you can read about them here:
http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-1.html#HEADING1-136
Related videos on Youtube

naveen
ASP.NET Consultant, JavaScript Enthusiast, Lover, Husband, Father. In other words, yet another coder... Email: Click here
Updated on June 22, 2020Comments
-
naveen about 2 years
I would like to know what the difference between these instructions is:
MOV AX, [TABLE-ADDR]
and
LEA AX, [TABLE-ADDR]
-
Nick Dandoulakis almost 13 yearsduplicate: stackoverflow.com/questions/1658294/…
-
naveen almost 13 yearsthanks nick. First of all, I wouldn't have found an answer to this question by looking into that link. Here I was looking for a specific info, the discussion in the link you provided is more genral in nature.
-
-
Bill Forster almost 13 yearsI think this answer is confusing at best. "The LEA instruction is a 'Load Effective Address' which is an indirected instruction, which means that TABLE-ADDR points to a memory location at which the address to load is found." Actually LEA will load the address, not the contents of the address. I think actually the questioner needs to be reassured that MOV and LEA can overlap, and do exactly the same thing, in some circumstances
-
naveen almost 13 yearsthanks a lot, its just that i cannot mark more than one as answer :(
-
I. J. Kennedy about 10 yearsThe difference between the x86 instructions MOV and LEA most definitely does NOT depend on the assembler.
-
JSmyth over 9 yearsI guess, with the exception that in GNU assembler it's not true when it comes to labels in the .bss segment? AFAIR you can't really
leal TextLabel, LabelFromBssSegment
when you got smth. like.bss .lcomm LabelFromBssSegment, 4
, you would have tomovl $TextLabel, LabelFromBssSegment
, isn't it? -
legends2k about 8 years+1 thanks for the clear explanation, helped me answer another question.
-
Peter Cordes over 6 yearsin NASM syntax only. In MASM syntax,
mov eax, var
is a load, the same asmov eax, [var]
, and you have to usemov eax, OFFSET var
to use a label as an immediate constant. -
JayArby over 5 yearsClear, simple, and demonstrates what I was trying to confirm. Thanks.
-
Ruben Bartelink about 5 years@josephGarvin IIRC the term fetch would be applied to that aspect; Load is just how you replace the value in a register with something from scratch. e.g.
LAHF
is: Load FLAGS into AH register. In the CLR's CIL (which is a higher level stack based abstract machine, the term load refers to putting a value onto the notional stack and is normallyl
..., and thes
... equivalent does the inverse). These notes: cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html) suggest that there are indeed architectures where your distinction does apply. -
Ruben Bartelink about 5 yearsit all reminds me of slideshare.net/pirhilton/… ;)
-
Peter Cordes over 4 years@JSmyth: That's only because
lea
requires a register destination, butmov
can have animm32
source and a memory destination. This limitation is of course not specific to the GNU assembler. -
Peter Cordes over 4 yearsAlso, this answer is basically wrong because the question is asking about
MOV AX, [TABLE-ADDR]
, which is a load. So there is a major difference. The equivalent instruction ismov ax, OFFSET table_addr
-
Peter Cordes over 4 yearsNote that in all of these examples,
lea
is the worse choice except in 64-bit mode for RIP-relative addressing.mov r32, imm32
runs on more ports.lea eax, [edx*4]
is a copy-and-shift which can't be done in one instruction otherwise, but in the same register LEA just takes more bytes to encode because[eax*4]
requires adisp32=0
. (It runs on different ports than shifts, though.) See agner.org/optimize and stackoverflow.com/tags/x86/info. -
Peter Cordes almost 3 yearsYou never want
lea label, %eax
for an absolute[disp32]
addressing mode. Usemov $label, %eax
instead. Yes it works, but it's less efficient (larger machine code and runs on fewer execution units). Since you mention AT&T, Using LEA on values that aren't addresses / pointers? uses AT&T, and my answer there has some other AT&T examples. -
Peter Cordes over 2 yearsYup, good explanation, in more detail than the other answers, and yes C's
&
operator is a good analogy. Perhaps worth pointing out that LEA is the special case, while MOV is just like every other instruction that can take a memory or register operand. e.g.add (%rdi), %eax
just uses the addressing mode to address memory, same as MOV. Also related: Using LEA on values that aren't addresses / pointers? takes this explanation further: LEA is how you can use the CPU's HW support for address math to do arbitrary calculations. -
ecm over 2 years"get the value at
%rdi
" -- This is strangely worded. You mean that the value in the registerrdi
should be used. Your use of "at" seems to mean a memory dereference where there is none. -
Quelklef over 2 years@PeterCordes Thanks! I've added the point about it being a special case to the answer.
-
Quelklef over 2 years@ecm Good point; I didn't notice that. I've changed it now, thank you! :)
-
Peter Cordes over 2 yearsFYI, shorter phrasing that fixes the problem ecm pointed out includes: "the value of
%rdi
" or "the value in%rdi
". Your "value in the register%rdi
" is long but fine, and perhaps might help someone struggling to understand registers vs. memory. -
Peter Cordes over 2 yearsI tidied up / improved (IMO) some parts of this. Feel free to roll back or remove anything that you think is confusing or distracting for beginners in case I misjudged.
-
Peter Cordes over 2 yearsGood update; most of it is an improvement over my edit. But note that
array + i * sizeof(long)
in C isn't&array[i]
. C pointer math scales by the type width implicitly:array[i]
is literally defined in the C standard as being equivalent to*(array + i)
. i.e.array + i
is the right C expression. To talk about asm you want to show explicitly scaling the index by the type width to get a byte offset (unless you simplify by usingchar
), but to do that you should probably avoid using exactly C syntax. Otherwise you're showing&array[i*sizeof(*array)]
-
Quelklef over 2 years@PeterCordes I went back and forth on whether I should write
array + i
orarray + i * sizeof(long)
. I decided to do the latter since we're in the context of asm and since I never use the expressionarray + i
within a C statement. However, it still is valid C syntax, as you've pointed out. I'll add a footnote. -
Peter Cordes about 2 yearsThat's only true for 64-bit mode (where PC-relative addressing was new); in other modes
lea [label
is a pointless waste of bytes vs. a more compactmov
, so you should specify the conditions you're talking about. Also, for some assemblers[label]
isn't the right syntax for a RIP-relative addressing mode. But yes, that's accurate. How to load address of function or label into register in GNU Assembler explains in more detail. -
Ryan1729 almost 2 yearsThe link is dead.
-
Sourav Kannantha B over 1 yearThe last trick is really awsome.. Compilers really do a great job at making the exe efficient.