В чем разница между MOV и LEA?

139

Я хотел бы знать, в чем разница между этими инструкциями:

MOV AX, [TABLE-ADDR]

и

LEA AX, [TABLE-ADDR]
Нэвин
источник
5
дубликат: stackoverflow.com/questions/1658294/…
Ник Дандулакис
8
спасибо ник. Прежде всего, я бы не нашел ответ на этот вопрос, посмотрев на эту ссылку. Здесь я искал конкретную информацию, обсуждение по предоставленной вами ссылке носит более общий характер.
Навин
3
Я проголосовал @ Dup's dup много лет назад, но сейчас vtc'd. Поразмыслив, я был слишком поспешен, и теперь с Навином, что а) другой вопрос не отвечает «в чем разница» и б) это полезный вопрос. Прошу прощения за мою ошибку - если бы я только мог отменить vtc ...
Рубен Бартелинк
1
LEA vs add: stackoverflow.com/questions/6323027/lea-or-add-instruction
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
Связанный: Использование LEA на значениях, которые не являются адресами / указателями? говорит о других применениях LEA, для произвольной математики.
Питер Кордес

Ответы:

169
  • LEA означает загрузить эффективный адрес
  • MOV означает значение нагрузки

Короче говоря, LEAзагружает указатель на элемент, к которому вы обращаетесь, тогда как MOV загружает фактическое значение по этому адресу.

Цель LEAсостоит в том, чтобы позволить человеку выполнить нетривиальный расчет адреса и сохранить результат [для дальнейшего использования]

LEA ax, [BP+SI+5] ; Compute address of value

MOV ax, [BP+SI+5] ; Load value at that address

Там, где задействованы только константы, MOV(с помощью константных вычислений ассемблера) иногда может совпасть с простейшими случаями использования LEA. Это полезно, если у вас есть расчеты из нескольких частей с несколькими базовыми адресами и т. Д.

Рубен Бартелинк
источник
6
+1 спасибо за понятное объяснение, помог мне ответить на другой вопрос.
legends2k
Меня смущает, что в имени lea есть «load», а люди говорят, что он «загружает» вычисленный адрес в регистр, потому что все входы для вычисления местоположения в памяти являются либо непосредственными значениями, либо регистрами. AFAICT lea выполняет только вычисления, ничего не загружает, где загрузка означает касание памяти?
Джозеф Гарвин
2
@josephGarvin IIRC термин fetch будет применяться к этому аспекту; Загрузка - это то, как вы заменяете значение в регистре чем-то с нуля. Например, LAHFэто: загрузить флаги в регистр AH . В CIL CLR (который является абстрактной машиной на основе стека более высокого уровня, термин load относится к размещению значения в условном стеке и обычно l..., а sэквивалент ... делает обратное). Эти примечания: cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html ) предполагают, что действительно существуют архитектуры, к которым относится ваше различие.
Рубен Бартелинк
все это напоминает мне slideshare.net/pirhilton/… ;)
Рубен Бартелинк
45

В синтаксисе NASM:

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

В синтаксисе MASM используйте OFFSET varдля получения mov-немедленного вместо загрузки.

Амит Сингх Томар
источник
3
только в синтаксисе NASM. В синтаксисе MASM mov eax, var- это загрузка, такая же, как mov eax, [var]и вы должны использовать, mov eax, OFFSET varчтобы использовать метку в качестве непосредственной константы.
Питер Кордес
1
Понятно, просто и демонстрирует то, что я пытался подтвердить. Спасибо.
JayArby
1
Обратите внимание, что во всех этих примерах leaэто худший выбор, за исключением 64-битного режима для относительной RIP-адресации. mov r32, imm32работает на нескольких портах. lea eax, [edx*4]является копией и сдвигом, который не может быть выполнен в одной инструкции иначе, но в том же регистре LEA просто требует больше байтов для кодирования, потому что [eax*4]требует a disp32=0. (Однако он работает на портах, отличных от сменных .) См. Agner.org/optimize и stackoverflow.com/tags/x86/info .
Питер Кордес
29

Инструкция MOV reg, addr означает чтение переменной, хранящейся по адресу addr, в регистр reg. Инструкция LEA reg, addr означает чтение адреса (а не переменной, хранящейся по адресу) в регистр reg.

Другой формой инструкции MOV является MOV reg, immdata, что означает считывание непосредственных данных (т.е. постоянных) immdata в регистр reg. Обратите внимание, что если addr в reg LEA, addr является просто константой (то есть с фиксированным смещением), то эта инструкция LEA по существу точно такая же, что и эквивалентная команда MOV reg, immdata, которая загружает ту же константу, что и непосредственные данные.

Билл Форстер
источник
11

Если вы укажете только литерал, разницы нет. У LEA больше способностей, и вы можете прочитать о них здесь:

http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-1.html#HEADING1-136

Ларс Д
источник
Я предполагаю, за исключением того, что в ассемблере GNU это не так, когда речь идет о метках в сегменте .bss? AFAIR ты не сможешь, leal TextLabel, LabelFromBssSegmentкогда получишь что-то. как .bss .lcomm LabelFromBssSegment, 4бы тебе пришлось movl $TextLabel, LabelFromBssSegment, не так ли?
JSmyth
@JSmyth: Это только потому, что leaтребует назначения регистра, но movможет иметь imm32источник и назначение памяти. Это ограничение, конечно, не специфично для ассемблера GNU.
Питер Кордес
1
Кроме того, этот ответ в основном неправильный, потому что вопрос о том MOV AX, [TABLE-ADDR], что является нагрузкой. Так что есть большая разница. Эквивалентная инструкцияmov ax, OFFSET table_addr
Питер Кордес
10

Это зависит от используемого ассемблера, потому что

mov ax,table_addr

в MASM работает как

mov ax,word ptr[table_addr]

Таким образом, он загружает первые байты из, table_addrа не смещение в table_addr. Вы должны использовать вместо

mov ax,offset table_addr

или

lea ax,table_addr

который работает так же.

leaверсия также отлично работает, если table_addrлокальная переменная, например

some_procedure proc

local table_addr[64]:word

lea ax,table_addr
Бартош Войчик
источник
Большое
5
Разница между инструкциями x86 MOV и LEA определенно НЕ зависит от ассемблера.
IJ Кеннеди
4

Ни один из предыдущих ответов не дошел до сути моей путаницы, поэтому я хотел бы добавить свой собственный.

Чего мне не хватало, так это того, что leaоперации трактуют использование скобок иначе, чем как mov.

Подумайте о С. Допустим, у меня есть массив, longкоторый я называю array. Теперь выражение array[i]выполняет разыменование, загружая значение из памяти по адресу array + i * sizeof(long)[1].

С другой стороны, рассмотрим выражение &array[i]. Это все еще содержит подвыражение array[i], но разыменование не выполняется! Значение array[i]изменилось. Это больше не означает выполнение запроса, а действует как своего рода спецификация , сообщающая, &какой адрес памяти мы ищем. Если хотите, вы можете думать о том, что вы можете &«отменить» разыменование.

Поскольку два варианта использования во многом похожи, они имеют общий синтаксис array[i], но наличие или отсутствие &изменений в интерпретации этого синтаксиса. Без &, это разыменование и фактически читает из массива. С &этим нет. Значение array + i * sizeof(long)по-прежнему рассчитывается, но оно не разыменовывается.

Ситуация очень похожа с movи lea. С mov, происходит разыменование, которое не происходит с lea. Это несмотря на использование скобок, которые встречаются в обоих. Например, movq (%r8), %r9и leaq (%r8), %r9. С movэти круглые скобки означают «разыменования»; с lea, они не делают. Это похоже на то, как array[i]означает «разыменование» только тогда, когда нет &.

Пример в порядке.

Рассмотрим код

movq (%rdi, %rsi, 8), %rbp

Это загружает значение в ячейке памяти %rdi + %rsi * 8в регистр %rbp. То есть: получить значение в регистре %rdiи значение в регистре %rsi. Умножьте последнее на 8, а затем добавьте его к первому. Найдите значение в этом месте и поместите его в реестр %rbp.

Этот код соответствует строке C x = array[i];, где arrayстановится %rdiи iстановится %rsiи xстановится %rbp. 8Длина типа данных , содержащихся в массиве.

Теперь рассмотрим похожий код, который использует lea:

leaq (%rdi, %rsi, 8), %rbp

Так же, как использование movqсоответствует разыменованию, использование leaqздесь соответствует не разыменованию. Эта линия сборки соответствует линии C x = &array[i];. Напомним, что &значение array[i]разыменования меняется с простого указания местоположения. Аналогично, использование leaqизменяет значение (%rdi, %rsi, 8)разыменования на указание местоположения.

Семантика этой строки кода следующая: получить значение в регистре %rdiи значение в регистре %rsi. Умножьте последнее на 8, а затем добавьте его к первому. Поместите это значение в реестр %rbp. Никакой нагрузки из памяти не происходит, только арифметические операции [2].

Обратите внимание, что единственное различие между моими описаниями leaqи movqзаключается в том movq, что разыменование происходит, а leaqне -. Фактически, чтобы написать leaqописание, я в основном скопировал + вставил описание movq, а затем удалил «Найти значение в этом месте».

Подводя итог: movqпротив leaqэто сложно, потому что они по- разному относятся к использованию скобок, как в (%rsi)и (%rdi, %rsi, 8). В movq(и во всех других инструкциях, кроме lea) эти скобки обозначают подлинное разыменование, тогда как в leaqних нет и есть чисто удобный синтаксис.


[1] Я сказал, что когда arrayэто массив long, выражение array[i]загружает значение из адреса array + i * sizeof(long). Это правда, но есть тонкость, которая должна быть решена. Если я напишу код C

long x = array[5];

это не то же самое, что печатать

long x = *(array + 5 * sizeof(long));

Кажется, это должно быть основано на моих предыдущих заявлениях, но это не так.

То, что происходит, заключается в том, что добавление указателя С имеет хитрость. Скажем, у меня есть указатель, pуказывающий на значения типа T. Выражение p + iже не среднее «положение в pплюс iбайт». Вместо этого выражение на p + i самом деле означает «позиция в pплюс i * sizeof(T)байты».

Удобство в том, что для получения «следующего значения» нам просто нужно написать p + 1вместо p + 1 * sizeof(T).

Это означает, что код C на long x = array[5];самом деле эквивалентен

long x = *(array + 5)

потому что C автоматически Умножьте 5на sizeof(long).

Итак, в контексте этого вопроса StackOverflow, как это все относится? Это означает, что когда я говорю «адрес array + i * sizeof(long)», я не имею в виду, что « array + i * sizeof(long)» следует интерпретировать как выражение C. Я делаю умножение sizeof(long)самостоятельно, чтобы сделать мой ответ более явным, но понимаю, что из-за этого это выражение не должно читаться как C. Так же, как обычная математика, использующая синтаксис C.

[2] Примечание: поскольку все leaэто арифметические операции, его аргументы не обязательно должны ссылаться на действительные адреса. По этой причине он часто используется для выполнения чистой арифметики со значениями, которые не могут быть разыменованы. Например, ccс -O2оптимизацией переводит

long f(long x) {
  return x * 5;
}

в следующее (ненужные строки удалены):

f:
  leaq (%rdi, %rdi, 4), %rax  # set %rax to %rdi + %rdi * 4
  ret
Quelklef
источник
1
Да, хорошее объяснение, более подробно, чем другие ответы, и да, &оператор Си - хорошая аналогия. Возможно, стоит отметить, что LEA является особым случаем, в то время как MOV подобен любой другой инструкции, которая может занять память или зарегистрировать операнд. например, add (%rdi), %eaxпросто использует режим адресации для адресации памяти, такой же, как MOV. Также связано: использование LEA для значений, которые не являются адресами / указателями? далее это объяснение: LEA - это то, как вы можете использовать поддержку HW CPU для математики адресов для выполнения произвольных вычислений.
Питер Кордес
«получить значение в %rdi» - это странно сформулировано. Вы имеете в виду, что значение в реестре rdi должно быть использовано. Использование «at», кажется, означает разыменование памяти там, где его нет.
ЕСМ
@PeterCordes Спасибо! Я добавил к ответу, что это особый случай.
Quelklef
1
@ecm Хорошая мысль; Я этого не заметил. Я изменил это сейчас, спасибо! :)
Quelklef
FYI, короче фразировка , что устраняет проблему ЕСМ отметил, включает в себя: «значение из %rdi » или «значения в %rdi ». Ваше «значение в регистре %rdi» длинное, но хорошее и, возможно, может помочь кому-то изо всех сил пытаться понять регистры и память.
Питер Кордес
2

В основном ... "Перейдите в REG ... после вычисления ...", это также хорошо для других целей :)

если вы просто забудете, что значение является указателем, вы можете использовать его для оптимизации / минимизации кода ... что угодно ..

MOV EBX , 1
MOV ECX , 2

;//with 1 instruction you got result of 2 registers in 3rd one ...
LEA EAX , [EBX+ECX+5]

EAX = 8

Первоначально это было бы:

MOV EAX, EBX
ADD EAX, ECX
ADD EAX, 5
Остап
источник
Да, leaэто инструкция сдвига и добавления, которая использует машинное кодирование и синтаксис с операндом памяти, потому что аппаратное обеспечение уже знает, как декодировать ModR / M + SIB + disp0 / 8/32.
Питер Кордес
1

Как указано в других ответах:

  • MOVзахватит данные по адресу в скобках и поместит эти данные в операнд-адресат.
  • LEAвыполнит вычисление адреса в скобках и поместит этот вычисленный адрес в операнд назначения. Это происходит без фактического выхода в память и получения данных. Проделанная работа LEAзаключается в расчете «эффективного адреса».

Поскольку память может быть адресована несколькими различными способами (см. Примеры ниже), LEAиногда используется для добавления или умножения регистров вместе без использования явного ADDили MULинструкции (или эквивалентной).

Поскольку все показывают примеры в синтаксисе Intel, вот некоторые из них в синтаксисе AT & T:

MOVL 16(%ebp), %eax       /* put long  at  ebp+16  into eax */
LEAL 16(%ebp), %eax       /* add 16 to ebp and store in eax */

MOVQ (%rdx,%rcx,8), %rax  /* put qword at  rcx*8 + rdx  into rax */
LEAQ (%rdx,%rcx,8), %rax  /* put value of "rcx*8 + rdx" into rax */

MOVW 5(%bp,%si), %ax      /* put word  at  si + bp + 5  into ax */
LEAW 5(%bp,%si), %ax      /* put value of "si + bp + 5" into ax */

MOVQ 16(%rip), %rax       /* put qword at rip + 16 into rax                 */
LEAQ 16(%rip), %rax       /* add 16 to instruction pointer and store in rax */

MOVL label(,1), %eax      /* put long at label into eax            */
LEAL label(,1), %eax      /* put the address of the label into eax */
Сэр рандом
источник
Вам никогда не нужен lea label, %eaxабсолютный [disp32]режим адресации. Используйте mov $label, %eaxвместо этого. Да, это работает, но это менее эффективно (больший машинный код и работает на меньшем количестве исполнительных блоков). Поскольку вы упоминаете AT & T, использовать LEA для значений, которые не являются адресами / указателями? использует AT & T, и в моем ответе есть несколько других примеров AT & T.
Питер Кордес
1

Давайте разберемся с примером.

mov eax, [ebx] и

lea eax, [ebx] Предположим, что значение в ebx равно 0x400000. Затем mov перейдет по адресу 0x400000 и скопирует 4 байта данных, представленных в eax-регистр. В то время как lea скопирует адрес 0x400000 в eax. Итак, после выполнения каждой инструкции значение eax в каждом случае будет (при условии, что в памяти 0x400000 содержится 30).

eax = 30 (в случае mov) eax = 0x400000 (в случае lea) Для определения mov скопируйте данные из rm32 в место назначения (mov dest rm32), а lea (загрузите эффективный адрес) скопирует адрес в пункт назначения (mov dest rm32) ).

Luftatako
источник
0

LEA (Load Effective Address) является инструкцией сдвига и добавления. Он был добавлен в 8086, потому что там есть оборудование для декодирования и расчета режимов адресации.

jojasicek
источник
0

MOV может делать то же самое, что и LEA [метка], но инструкция MOV содержит эффективный адрес внутри самой инструкции как непосредственную константу (вычисляемую заранее ассемблером). LEA использует PC-относительный для вычисления эффективного адреса во время выполнения инструкции.

Мишель Сэйд
источник
Это верно только для 64-битного режима (где относительная адресация для ПК была новой); в других режимах lea [labelэто бессмысленная трата байтов против более компактных mov, поэтому вы должны указать условия, о которых вы говорите. Кроме того, для некоторых ассемблеров [label]неправильный синтаксис для режима RIP-относительной адресации. Но да, это точно. Как загрузить адрес функции или метки в регистр в GNU Assembler, объясняется более подробно.
Питер Кордес
-1

Разница тонкая, но важная. Инструкция MOV является «MOVe», по сути, копией адреса, обозначаемого меткой TABLE-ADDR. Инструкция LEA - это «Эффективный адрес загрузки», который является косвенной инструкцией, что означает, что TABLE-ADDR указывает на область памяти, в которой находится адрес для загрузки.

Эффективное использование LEA эквивалентно использованию указателей на таких языках, как C, поэтому это мощная инструкция.

Гильермо Филлипс
источник
7
Я думаю, что этот ответ в лучшем случае сбивает с толку. «Инструкция LEA - это« Эффективный адрес загрузки », который является косвенной инструкцией, что означает, что TABLE-ADDR указывает на область памяти, в которой находится адрес для загрузки». На самом деле LEA будет загружать адрес, а не содержимое адреса. Я думаю, что на самом деле спрашивающий должен быть уверен, что MOV и LEA могут совпадать, и в некоторых обстоятельствах делать одно и то же
Билл Форстер