Jaka jest różnica między MOV i LEA?

138

Chciałbym wiedzieć, jaka jest różnica między tymi instrukcjami:

MOV AX, [TABLE-ADDR]

i

LEA AX, [TABLE-ADDR]
naveen
źródło
5
duplicate: stackoverflow.com/questions/1658294/ ...
Nick Dandoulakis
8
dzięki nick. Po pierwsze, nie znalazłbym odpowiedzi na to pytanie, zaglądając do tego linku. Tutaj szukałem konkretnej informacji, dyskusja w podanym przez ciebie linku ma charakter bardziej genralny.
naveen
3
Głosowałem za dupkiem @ Nicka wieki temu, ale dopiero teraz. Po zastanowieniu byłem zbyt pochopny i teraz z naveenem, że a) drugie pytanie nie odpowiada „jaka jest różnica” i b) jest to przydatne pytanie. Przeprosiny dla naveen za mój błąd - gdybym tylko mógł cofnąć vtc ...
Ruben Bartelink.
1
LEA vs add: stackoverflow.com/questions/6323027/lea-or-add-instruction
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Powiązane: Używanie LEA na wartościach, które nie są adresami / wskaźnikami? mówi o innych zastosowaniach LEA do arbitralnej matematyki.
Peter Cordes

Odpowiedzi:

168
  • LEA oznacza Załaduj efektywny adres
  • MOV oznacza wartość obciążenia

Krótko mówiąc, LEAładuje wskaźnik do adresowanego elementu, podczas gdy MOV ładuje rzeczywistą wartość pod tym adresem.

Celem LEAjest umożliwienie wykonania nietrywialnego obliczenia adresu i zapisanie wyniku [do późniejszego wykorzystania]

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

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

Tam, gdzie w grę wchodzą tylko stałe MOV(poprzez stałe obliczenia asemblera), czasami może się wydawać, że nakładają się na najprostsze przypadki użycia LEA. Jest to przydatne, jeśli masz wieloczęściowe obliczenia z wieloma adresami podstawowymi itp.

Ruben Bartelink
źródło
6
+1 dzięki za jasne wyjaśnienie, pomogło mi odpowiedzieć na inne pytanie.
legends2k
Wprawia mnie w zakłopotanie, że lea ma w nazwie słowo „load”, a ludzie mówią, że „ładuje” obliczony adres do rejestru, ponieważ wszystkie dane wejściowe do obliczenia lokalizacji pamięci są albo wartościami bezpośrednimi, albo rejestrami. AFAICT lea wykonuje tylko obliczenia, niczego nie ładuje, gdzie ładowanie oznacza dotknięcie pamięci?
Joseph Garvin,
2
@josephGarvin IIRC termin pobieranie zostanie zastosowany do tego aspektu; Load to po prostu sposób, w jaki zamieniasz wartość w rejestrze na coś od podstaw. np. LAHFto: Załaduj FLAGI do rejestru AH . W CIL CLR (który jest maszyną abstrakcyjną opartą na stosie wyższego poziomu, termin load odnosi się do umieszczenia wartości na stosie pojęciowym i jest normalnie l..., a sodpowiednik ... odwrotnie). Te uwagi: cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html ) sugerują, że rzeczywiście istnieją architektury, w których twoje rozróżnienie ma zastosowanie.
Ruben Bartelink
to wszystko przypomina mi slideshare.net/pirhilton/… ;)
Ruben Bartelink
45

W składni 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

W składni MASM użyj, OFFSET varaby uzyskać natychmiastowe mov zamiast ładowania.

Amit Singh Tomar
źródło
3
tylko w składni NASM. W składni MASM mov eax, varjest to obciążenie, to samo co mov eax, [var]i musisz mov eax, OFFSET varużyć etykiety jako natychmiastowej stałej.
Peter Cordes
1
Jasne, proste i demonstruje to, co próbowałem potwierdzić. Dzięki.
JayArby,
1
Zauważ, że we wszystkich tych przykładach leajest to gorszy wybór, z wyjątkiem trybu 64-bitowego dla adresowania względnego RIP. mov r32, imm32działa na większej liczbie portów. lea eax, [edx*4]jest kopiowaniem i przesuwaniem, którego nie można wykonać w jednej instrukcji w inny sposób, ale w tym samym rejestrze LEA zajmuje po prostu więcej bajtów do kodowania, ponieważ [eax*4]wymaga rozszerzenia disp32=0. (Działa jednak na innych portach niż zmiany.) Zobacz agner.org/optimize i stackoverflow.com/tags/x86/info .
Peter Cordes,
29

Instrukcja MOV reg, addr oznacza odczyt zmiennej przechowywanej pod adresem addr do rejestru reg. Instrukcja LEA reg, addr oznacza odczyt adresu (a nie zmiennej przechowywanej pod adresem) do rejestru reg.

Inną formą instrukcji MOV jest MOV reg, immdata, co oznacza wczytanie natychmiastowych danych (tj. Stałych) immdata do rejestru reg. Zauważ, że jeśli addr w LEA reg, addr jest po prostu stałą (tj. Stałym przesunięciem), to ta instrukcja LEA jest zasadniczo dokładnie taka sama, jak równoważna instrukcja MOV reg, immdata, która ładuje tę samą stałą, co dane bezpośrednie.

Bill Forster
źródło
11

Jeśli podasz tylko literał, nie ma różnicy. LEA ma jednak więcej umiejętności, o których możesz przeczytać tutaj:

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

Lars D.
źródło
Myślę, że z wyjątkiem tego, że w asemblerze GNU nie jest to prawdą, jeśli chodzi o etykiety w segmencie .bss? AFAIR, naprawdę nie leal TextLabel, LabelFromBssSegmentmożesz, kiedy coś masz. jakbyś .bss .lcomm LabelFromBssSegment, 4musiał movl $TextLabel, LabelFromBssSegment, prawda?
JSmyth
@JSmyth: To tylko dlatego, że leawymaga miejsca docelowego rejestru, ale movmoże mieć imm32źródło i miejsce docelowe pamięci. To ograniczenie oczywiście nie jest specyficzne dla asemblera GNU.
Peter Cordes
1
Ta odpowiedź jest w zasadzie błędna, ponieważ pytanie dotyczy MOV AX, [TABLE-ADDR]obciążenia. Jest więc zasadnicza różnica. Równoważna instrukcja brzmimov ax, OFFSET table_addr
Peter Cordes,
10

To zależy od używanego asemblera, ponieważ

mov ax,table_addr

w MASM działa jako

mov ax,word ptr[table_addr]

Więc ładuje pierwsze bajty zi table_addrNIE przesunięcie do table_addr. Zamiast tego powinieneś użyć

mov ax,offset table_addr

lub

lea ax,table_addr

który działa tak samo.

leawersja działa również dobrze, jeśli table_addrjest zmienną lokalną np

some_procedure proc

local table_addr[64]:word

lea ax,table_addr
Bartosz Wójcik
źródło
wielkie dzięki, po prostu nie mogę oznaczyć więcej niż jednego jako odpowiedzi :(
naveen
5
Różnica między instrukcjami x86 MOV i LEA zdecydowanie NIE zależy od asemblera.
IJ Kennedy,
4

Żadna z poprzednich odpowiedzi nie doprowadziła do końca mojego zamieszania, więc chciałbym dodać własną.

Brakowało mi tego, że leaoperacje traktują użycie nawiasów inaczej niż w jaki movsposób.

Pomyśl o C. Powiedzmy, że mam tablicę long, którą nazywam array. Teraz wyrażenie array[i]wykonuje dereferencję, ładując wartość z pamięci pod adresem array + i * sizeof(long)[1].

Z drugiej strony rozważ wyrażenie &array[i]. To nadal zawiera wyrażenie podrzędne array[i], ale nie jest wykonywane żadne wyłuskiwanie! Znaczenie się array[i]zmieniło. Nie oznacza już szacunku, ale działa jako rodzaj specyfikacji , mówiącej, &jakiego adresu pamięci szukamy. Jeśli chcesz, możesz alternatywnie pomyśleć o &„anulowaniu” wyłuskiwania.

Ponieważ te dwa przypadki użycia są podobne pod wieloma względami, mają wspólną składnię array[i], ale istnienie lub brak &zmiany zmienia sposób interpretacji tej składni. Bez &tego jest to dereferencja i faktycznie czyta z tablicy. Tak &nie jest. Wartość array + i * sizeof(long)jest nadal obliczana, ale nie jest odwoływana.

Sytuacja jest bardzo podobna w przypadku movi lea. W movprzypadku występuje dereferencja, która nie ma miejsca w przypadku lea. Dzieje się tak pomimo użycia nawiasów, które występują w obu przypadkach. Na przykład movq (%r8), %r9i leaq (%r8), %r9. W przypadku movtych nawiasów oznacza „wyłuskiwanie”; z lea, nie robią. Jest to podobne do sposobu, w jaki array[i]oznacza „wyłuskiwanie” tylko wtedy, gdy nie ma &.

Przykład jest w porządku.

Rozważ kod

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

Spowoduje to załadowanie wartości z lokalizacji pamięci %rdi + %rsi * 8do rejestru %rbp. To znaczy: pobierz wartość w rejestrze %rdii wartość w rejestrze %rsi. Pomnóż tę ostatnią przez 8, a następnie dodaj ją do pierwszej. Znajdź wartość w tej lokalizacji i umieść ją w rejestrze %rbp.

Ten kod odpowiada linii C x = array[i];, gdzie arraystaje się %rdii istaje się %rsii xstaje się %rbp. Jest 8to długość typu danych zawartego w tablicy.

Teraz rozważ podobny kod, który używa lea:

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

Podobnie jak wykorzystaniu movqodzwierciedlał dereferencing, użycie leaqtutaj odpowiada nie dereferencji. Ta linia montażowa odpowiada linii C x = &array[i];. Przypomnijmy, że &zmienia to znaczenie array[i]z wyłuskiwania odwołań do prostego określenia lokalizacji. Podobnie użycie leaqzmiany zmienia znaczenie (%rdi, %rsi, 8)z wyłuskiwania odwołań do określania lokalizacji.

Semantyka tego wiersza kodu jest następująca: pobierz wartość w rejestrze %rdii wartość w rejestrze %rsi. Pomnóż tę ostatnią przez 8, a następnie dodaj ją do pierwszej. Umieść tę wartość w rejestrze %rbp. Nie jest wymagane żadne obciążenie z pamięci, tylko operacje arytmetyczne [2].

Zauważ, że jedyna różnica między moimi opisami opcji leaqi movqpolega na tym, że movqdokonuje wyłuskiwania, a leaqnie. Właściwie, aby napisać leaqopis, po prostu skopiowałem + wkleiłem opis movq, a następnie usunąłem „Znajdź wartość w tej lokalizacji”.

Podsumowując: movqvs. leaqjest trudne, ponieważ traktują użycie nawiasów tak jak w (%rsi)i (%rdi, %rsi, 8), inaczej. W movq(i we wszystkich innych instrukcjach z wyjątkiem lea) te nawiasy oznaczają autentyczną dereferencję, podczas gdy w leaqnich nie mają i są czysto wygodną składnią.


[1] Powiedziałem, że kiedy arrayjest tablicą long, wyrażenie array[i]ładuje wartość z adresu array + i * sizeof(long). To prawda, ale należy się zająć pewną subtelnością. Jeśli napiszę kod C.

long x = array[5];

to nie to samo, co pisanie

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

Wydaje się, że powinno być oparte na moich wcześniejszych wypowiedziach, ale tak nie jest.

Chodzi o to, że dodawanie wskaźnika C ma w sobie sztuczkę. Powiedzmy, że mam wskaźnik pwskazujący na wartości typu T. Wyrażenie p + ima nie znaczyć „pozycja na pplus ibajtów”. Zamiast tego wyrażenie p + i faktycznie oznacza „pozycję z pplusem i * sizeof(T)bajtów”.

Wygoda polega na tym, że aby uzyskać „następną wartość”, po prostu musimy p + 1zamiast tego pisać p + 1 * sizeof(T).

Oznacza to, że kod C long x = array[5];jest faktycznie odpowiednikiem

long x = *(array + 5)

ponieważ C automatycznie pomnoży 5przez sizeof(long).

Więc w kontekście tego pytania StackOverflow, jak to wszystko ma znaczenie? Oznacza to, że kiedy mówię „adres array + i * sizeof(long)”, ja nie myśli o „ array + i * sizeof(long)” należy interpretować jako wyrażenie C. Mnożę sizeof(long)samodzielnie, aby uściślić moją odpowiedź, ale rozumiem, że z tego powodu wyrażenie to nie powinno być odczytywane jako C. Tak jak zwykła matematyka, która używa składni C.

[2] Uwaga dodatkowa: ponieważ wszystko learobi jest operacjami arytmetycznymi, jego argumenty nie muszą w rzeczywistości odnosić się do poprawnych adresów. Z tego powodu jest często używany do wykonywania czystej arytmetyki na wartościach, które mogą nie być wyłuskiwane. Na przykład ccz -O2optymalizacją przekłada się

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

do następującego (usunięto nieistotne wiersze):

f:
  leaq (%rdi, %rdi, 4), %rax  # set %rax to %rdi + %rdi * 4
  ret
Quelklef
źródło
1
Tak, dobre wyjaśnienie, bardziej szczegółowe niż inne odpowiedzi, i tak, &operator C to dobra analogia. Być może warto zauważyć, że LEA jest przypadkiem specjalnym, podczas gdy MOV jest jak każda inna instrukcja, która może zająć operand pamięci lub rejestr. np. add (%rdi), %eaxpo prostu używa trybu adresowania do adresowania pamięci, tak samo jak MOV. Również powiązane: Używanie LEA na wartościach, które nie są adresami / wskaźnikami? kontynuuje to wyjaśnienie: LEA to sposób, w jaki można wykorzystać wsparcie sprzętowe procesora dla matematyki adresowej do wykonywania dowolnych obliczeń.
Peter Cordes
„Uzyskaj wartość w %rdi” - to dziwnie sformułowane. Masz na myśli, że należy użyć wartości w rejestrze rdi . Twoje użycie „at” wydaje się oznaczać wyłuskiwanie pamięci tam, gdzie jej nie ma.
ecm
@PeterCordes Thanks! Dodałem do odpowiedzi kwestię, że jest to szczególny przypadek.
Quelklef
1
@ecm Słuszna uwaga; Nie zauważyłem tego. Teraz to zmieniłem, dziękuję! :)
Quelklef
FYI, krótszy frazowania, że poprawki ECM Problem wskazał obejmuje: „wartość z %rdi ” lub „wartości w %rdi ”. Twoja „wartość w rejestrze %rdi” jest długa, ale w porządku i może pomóc komuś, kto ma problemy ze zrozumieniem rejestrów i pamięci.
Peter Cordes
2

Zasadniczo ... "Przenieś się do REG ... po obliczeniu ..." wydaje się być również przydatne do innych celów :)

jeśli po prostu zapomnisz, że wartość jest wskaźnikiem, możesz jej użyć do optymalizacji / minimalizacji kodu ... cokolwiek ...

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

pierwotnie byłoby to:

MOV EAX, EBX
ADD EAX, ECX
ADD EAX, 5
Ostap
źródło
Tak, leajest to instrukcja przesuwania i dodawania, która używa maszynowego kodowania i składni operandów pamięci, ponieważ sprzęt już wie, jak dekodować ModR / M + SIB + disp0 / 8/32.
Peter Cordes,
1

Jak stwierdzono w innych odpowiedziach:

  • MOVprzechwyci dane na adres wewnątrz wsporników i miejscu, że dane do docelowego argumentu.
  • LEAwykona obliczenie adresu wewnątrz nawiasów i umieści obliczony adres w operandzie docelowym. Dzieje się to bez wychodzenia do pamięci i pobierania danych. Praca wykonywana przez program LEApolega na obliczaniu „efektywnego adresu”.

Ponieważ pamięć może być adresowana na kilka różnych sposobów (patrz przykłady poniżej), LEAczasami jest używana do dodawania lub mnożenia rejestrów razem bez użycia jawnej instrukcji ADDlub MULinstrukcji (lub równoważnej).

Ponieważ wszyscy pokazują przykłady w składni Intela, oto kilka w składni 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 */
Sir Random
źródło
Nigdy nie potrzebujesz lea label, %eaxabsolutnego [disp32]trybu adresowania. Użyj mov $label, %eaxzamiast tego. Tak, działa, ale jest mniej wydajne (większy kod maszynowy i działa na mniejszej liczbie jednostek wykonawczych). Skoro wspomniałeś o AT&T, używaniu LEA na wartościach, które nie są adresami / wskaźnikami? używa AT&T, a moja odpowiedź zawiera kilka innych przykładów AT&T.
Peter Cordes
1

Zrozummy to na przykładzie.

mov eax, [ebx] i

lea eax, [ebx] Załóżmy, że wartość w ebx to 0x400000. Następnie mov przejdzie na adres 0x400000 i przekopiuje 4 bajty prezentowanych danych do rejestru eax, przy czym lea skopiuje adres 0x400000 do eax. Czyli po wykonaniu każdej instrukcji wartość eax w każdym przypadku będzie (zakładając, że w pamięci 0x400000 zawiera 30).

eax = 30 (w przypadku mov) eax = 0x400000 (w przypadku lea) W celu zdefiniowania mov skopiuj dane z rm32 do celu (mov dest rm32) a lea (załaduj efektywny adres) skopiuje adres do celu (mov dest rm32 ).

Luftatako
źródło
0

LEA (Load Effective Address) to instrukcja typu „przesuń i dodaj”. Został dodany do 8086, ponieważ sprzęt służy do dekodowania i obliczania trybów adresowania.

jojasicek
źródło
0

MOV może zrobić to samo co LEA [etykieta], ale instrukcja MOV zawiera efektywny adres wewnątrz samej instrukcji jako stałą natychmiastową (obliczoną z góry przez asemblera). LEA używa względem PC do obliczenia efektywnego adresu podczas wykonywania instrukcji.

Michel Sayde
źródło
Dotyczy to tylko trybu 64-bitowego (gdzie adresowanie w stosunku do komputera było nowe); w innych trybach lea [labeljest to bezcelowe marnowanie bajtów w porównaniu z bardziej zwartym mov, więc powinieneś określić warunki, o których mówisz. Ponadto dla niektórych asemblerów [label]składnia nie jest odpowiednia dla trybu adresowania względnego w protokole RIP. Ale tak, to prawda. Jak załadować adres funkcji lub etykiety do rejestru w GNU Assembler wyjaśnia bardziej szczegółowo.
Peter Cordes
-1

Różnica jest subtelna, ale ważna. Instrukcja MOV jest „MOVe” w rzeczywistości kopią adresu, który reprezentuje etykieta TABLE-ADDR. Instrukcja LEA jest „Load Effective Address”, która jest instrukcją pośrednią, co oznacza, że ​​TABLE-ADDR wskazuje miejsce w pamięci, w którym znajduje się adres do załadowania.

Skuteczne używanie LEA jest równoważne używaniu wskaźników w językach takich jak C, ponieważ jest to potężna instrukcja.

Guillermo Phillips
źródło
7
Myślę, że ta odpowiedź jest w najlepszym razie zagmatwana. „Instrukcja LEA jest 'Load Effective Address', która jest instrukcją pośrednią, co oznacza, że ​​TABLE-ADDR wskazuje na lokalizację pamięci, w której znajduje się adres do załadowania.” Właściwie LEA załaduje adres, a nie zawartość adresu. Myślę, że pytający musi być uspokojony, że MOV i LEA mogą się pokrywać i robić dokładnie to samo w niektórych okolicznościach
Bill Forster