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

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

MOV AX, [TABLE-ADDR]

а также

LEA AX, [TABLE-ADDR]

person naveen    schedule 09.11.2009    source источник
comment
дубликат: stackoverflow.com/questions/1658294/   -  person Nick Dandoulakis    schedule 09.11.2009
comment
спасибо ник. Во-первых, я бы не нашел ответа на этот вопрос, заглянув по этой ссылке. Здесь я искал конкретную информацию, обсуждение в предоставленной вами ссылке носит более общий характер.   -  person naveen    schedule 09.11.2009
comment
Много лет назад я проголосовал за дуплика @ Ника, но vtc проголосовал за него только сейчас. Поразмыслив, я был слишком поспешным и теперь с наивным, что а) другой вопрос не дает ответа, в чем разница, и б) это полезный вопрос. Приношу извинения за мою ошибку - если бы я только мог отменить vtc ...   -  person Ruben Bartelink    schedule 31.07.2012
comment
LEA против добавления: stackoverflow.com/questions/6323027/lea-or- дополнительная инструкция   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 10.05.2015
comment
Связанный: Использование LEA для значений, не являющихся адресами / указателями? рассказывает о других способах использования LEA для произвольной математики.   -  person Peter Cordes    schedule 14.11.2019


Ответы (12)


  • 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. Это полезно, если у вас есть многоэлементный расчет с несколькими базовыми адресами и т. Д.

person Ruben Bartelink    schedule 09.11.2009
comment
+1 спасибо за четкое объяснение, помог мне ответить на другой вопрос. - person legends2k; 13.09.2014
comment
Меня смущает то, что у lea есть загрузка в имени, и люди говорят, что он загружает вычисленный адрес в регистр, потому что все входные данные для вычисления местоположения в памяти являются либо непосредственными значениями, либо регистрами. AFAICT Lea выполняет только вычисления, он ничего не загружает, где загрузка означает касание памяти? - person Joseph Garvin; 25.06.2017
comment
@josephGarvin IIRC термин выборка будет применяться к этому аспекту; Загрузка - это просто то, как вы заменяете значение в регистре чем-то с нуля. например LAHF: Загрузить ФЛАГИ в регистр AH. В CIL CLR (который представляет собой абстрактную машину на основе стека более высокого уровня, термин load относится к помещению значения в условный стек и обычно равен _2 _..., а эквивалент _3 _... обратное). Эти примечания: cs.umd.edu/class/sum2003 /cmsc311/Notes/Mips/load.html) свидетельствует о том, что действительно существуют архитектуры, в которых применяется ваше различие. - person Ruben Bartelink; 25.06.2017
comment
все это напоминает мне slideshare.net / pirhilton /;) - person Ruben Bartelink; 25.06.2017

В синтаксисе 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, чтобы получить немедленное перемещение вместо загрузки.

person Amit Singh Tomar    schedule 27.06.2013
comment
только в синтаксисе NASM. В синтаксисе MASM mov eax, var - это загрузка, то же самое, что и mov eax, [var], и вы должны использовать mov eax, OFFSET var, чтобы использовать метку как непосредственную константу. - person Peter Cordes; 27.05.2016
comment
Ясно, просто и демонстрирует то, что я пытался подтвердить. Спасибо. - person JayArby; 23.01.2017
comment
Обратите внимание, что во всех этих примерах lea - худший выбор, за исключением 64-битного режима для относительной адресации RIP. mov r32, imm32 работает на большем количестве портов. lea eax, [edx*4] - это копирование и сдвиг, которое не может быть выполнено в одной инструкции иначе, но в том же регистре LEA просто требуется больше байтов для кодирования, потому что [eax*4] требует disp32=0. (Однако он работает на разных портах, а не на сменах.) См. agner.org/optimize и stackoverflow.com/tags/x86/info. - person Peter Cordes; 16.04.2018

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

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

person Bill Forster    schedule 09.11.2009

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

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

Подумайте о C. Допустим, у меня есть массив 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));

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

Что происходит, так это то, что добавление указателя C имеет хитрость. Скажем, у меня есть указатель 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
person Quelklef    schedule 18.02.2020
comment
Да, хорошее объяснение, более подробно, чем другие ответы, и да, оператор C & - хорошая аналогия. Возможно, стоит отметить, что LEA - это особый случай, а MOV, как и любая другая инструкция, может принимать операнд памяти или регистр. например add (%rdi), %eax просто использует режим адресации для адресации памяти, как и MOV. Также по теме: Использование LEA для значений, которые не являются адресами / указателями? продолжает это объяснение: LEA - это то, как вы можете использовать поддержку аппаратного обеспечения процессора для адресной математики для произвольных вычислений. - person Peter Cordes; 18.02.2020
comment
получить значение %rdi - это странная формулировка. Вы имеете в виду, что следует использовать значение в регистре rdi. Ваше использование at, похоже, означает разыменование памяти там, где ее нет. - person ecm; 20.02.2020
comment
@PeterCordes Спасибо! К ответу я добавил, что это особый случай. - person Quelklef; 20.02.2020
comment
@ecm Хорошее замечание; Я этого не заметил. Я изменил это сейчас, спасибо! :) - person Quelklef; 20.02.2020
comment
К вашему сведению, более короткая формулировка, которая устраняет проблему, указанную ecm, включает: значение of %rdi или значение in %rdi. Ваше значение в регистре %rdi длинное, но прекрасное и, возможно, может помочь кому-то, кто пытается понять регистры и память. - person Peter Cordes; 21.02.2020
comment
Я привел в порядок / улучшил (ИМО) некоторые части этого. Не стесняйтесь откатывать назад или удалять все, что, по вашему мнению, сбивает с толку или отвлекает новичков, на случай, если я ошибся. - person Peter Cordes; 21.02.2020
comment
Хорошее обновление; большая часть этого улучшения по сравнению с моим редактированием. Но обратите внимание, что array + i * sizeof(long) в C не &array[i]. Математика указателя C масштабируется по ширине типа неявно: array[i] буквально определено в стандарте C как эквивалент *(array + i). т.е. array + i - правильное выражение C. Говоря об asm, вы хотите показать явное масштабирование индекса по ширине типа для получения байтового смещения (если вы не упростите использование char), но для этого вам, вероятно, следует избегать использования синтаксиса C. В противном случае вы показываете &array[i*sizeof(*array)] - person Peter Cordes; 21.02.2020
comment
@PeterCordes Я много раз думал о том, писать ли мне array + i или array + i * sizeof(long). Я решил сделать последнее, поскольку мы находимся в контексте asm и поскольку я никогда не использую выражение array + i в операторе C. Однако, как вы отметили, это все еще действующий синтаксис C. Добавлю сноску. - person Quelklef; 21.02.2020
comment
Последний трюк действительно ужасен ... Компиляторы действительно отлично справляются с тем, чтобы сделать exe эффективным. - person Sourav Kannantha B; 02.04.2021

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

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

person Lars D    schedule 09.11.2009
comment
Полагаю, за исключением того, что в ассемблере GNU это не так, когда дело касается меток в сегменте .bss? AFAIR вы действительно не можете leal TextLabel, LabelFromBssSegment когда у вас есть что-л. как .bss .lcomm LabelFromBssSegment, 4, вам нужно movl $TextLabel, LabelFromBssSegment, не так ли? - person JSmyth; 18.02.2013
comment
@JSmyth: Это только потому, что lea требует адресата регистра, но mov может иметь imm32 источник и адресат памяти. Это ограничение, конечно, не относится к ассемблеру GNU. - person Peter Cordes; 28.02.2018
comment
Кроме того, этот ответ в основном неверен, потому что вопрос касается MOV AX, [TABLE-ADDR], что является нагрузкой. Так что есть большая разница. Эквивалентная инструкция mov ax, OFFSET table_addr - person Peter Cordes; 28.02.2018
comment
Ссылка мертвая. - person Ryan1729; 08.11.2020

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

mov ax,table_addr

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

mov ax,word ptr[table_addr]

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

mov ax,offset table_addr

or

lea ax,table_addr

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

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

some_procedure proc

local table_addr[64]:word

lea ax,table_addr
person Bartosz Wójcik    schedule 09.11.2009
comment
Большое спасибо, просто я не могу отметить более одного ответа :( - person naveen; 09.11.2009
comment
Разница между инструкциями x86 MOV и LEA определенно НЕ зависит от ассемблера. - person I. J. Kennedy; 18.06.2012

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

  • 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 */
person Sir Random    schedule 21.11.2019
comment
Вы никогда не хотите lea label, %eax для абсолютного [disp32] режима адресации. Вместо этого используйте mov $label, %eax. Да, это работает, но менее эффективно (машинный код больше и работает на меньшем количестве исполнительных модулей). Поскольку вы упоминаете AT&T, Использование LEA для значений, не являющихся адресами / указателями? использует AT&T, и в моем ответе есть некоторые другие примеры AT&T. - person Peter Cordes; 22.11.2019

В основном ... "Переместитесь в 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
person Ostap    schedule 19.10.2017
comment
Ага, lea - это инструкция сдвига и добавления, которая использует операнд памяти машинное кодирование и синтаксис, потому что оборудование уже знает, как декодировать ModR / M + SIB + disp0 / 8/32. - person Peter Cordes; 28.02.2018

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

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 ).

person Luftatako    schedule 24.11.2019

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

person Michel Sayde    schedule 22.06.2020
comment
Это верно только для 64-битного режима (где относительная адресация ПК была новой); в других режимах lea [label - это бессмысленная трата байтов по сравнению с более компактным mov, поэтому вам следует указать условия, о которых вы говорите. Кроме того, для некоторых ассемблеров [label] неправильный синтаксис для режима адресации, относящейся к RIP. Но да, это так. Как загрузить адрес функции или метку в регистр в GNU Assembler объясняется более подробно. - person Peter Cordes; 22.06.2020

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

person jojasicek    schedule 21.11.2019

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

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

person Guillermo Phillips    schedule 09.11.2009
comment
Я думаю, что этот ответ в лучшем случае сбивает с толку. Инструкция LEA - это «Load Effective Address», которая является косвенной инструкцией, что означает, что TABLE-ADDR указывает на ячейку памяти, в которой находится адрес для загрузки. Фактически LEA загрузит адрес, а не его содержимое. Я думаю, что на самом деле нужно заверить спрашивающего, что MOV и LEA могут перекрываться и в некоторых обстоятельствах делать одно и то же. - person Bill Forster; 09.11.2009