Chciałbym wiedzieć, jaka jest różnica między tymi instrukcjami:
MOV AX, [TABLE-ADDR]
i
LEA AX, [TABLE-ADDR]
Chciałbym wiedzieć, jaka jest różnica między tymi instrukcjami:
MOV AX, [TABLE-ADDR]
i
LEA AX, [TABLE-ADDR]
Odpowiedzi:
LEA
oznacza Załaduj efektywny adresMOV
oznacza wartość obciążeniaKrótko mówiąc, LEA
ładuje wskaźnik do adresowanego elementu, podczas gdy MOV ładuje rzeczywistą wartość pod tym adresem.
Celem LEA
jest 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.
LAHF
to: 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 s
odpowiednik ... 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.
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 var
aby uzyskać natychmiastowe mov zamiast ładowania.
mov eax, var
jest to obciążenie, to samo co mov eax, [var]
i musisz mov eax, OFFSET var
użyć etykiety jako natychmiastowej stałej.
lea
jest to gorszy wybór, z wyjątkiem trybu 64-bitowego dla adresowania względnego RIP. mov r32, imm32
dział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 .
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.
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
leal TextLabel, LabelFromBssSegment
możesz, kiedy coś masz. jakbyś .bss .lcomm LabelFromBssSegment, 4
musiał movl $TextLabel, LabelFromBssSegment
, prawda?
lea
wymaga miejsca docelowego rejestru, ale mov
może mieć imm32
źródło i miejsce docelowe pamięci. To ograniczenie oczywiście nie jest specyficzne dla asemblera GNU.
MOV AX, [TABLE-ADDR]
obciążenia. Jest więc zasadnicza różnica. Równoważna instrukcja brzmimov ax, OFFSET table_addr
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_addr
NIE 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.
lea
wersja działa również dobrze, jeśli table_addr
jest zmienną lokalną np
some_procedure proc
local table_addr[64]:word
lea ax,table_addr
Żadna z poprzednich odpowiedzi nie doprowadziła do końca mojego zamieszania, więc chciałbym dodać własną.
Brakowało mi tego, że lea
operacje traktują użycie nawiasów inaczej niż w jaki mov
sposó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 mov
i lea
. W mov
przypadku 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), %r9
i leaq (%r8), %r9
. W przypadku mov
tych 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 * 8
do rejestru %rbp
. To znaczy: pobierz wartość w rejestrze %rdi
i 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 array
staje się %rdi
i i
staje się %rsi
i x
staje się %rbp
. Jest 8
to długość typu danych zawartego w tablicy.
Teraz rozważ podobny kod, który używa lea
:
leaq (%rdi, %rsi, 8), %rbp
Podobnie jak wykorzystaniu movq
odzwierciedlał dereferencing, użycie leaq
tutaj 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 leaq
zmiany 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 %rdi
i 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 leaq
i movq
polega na tym, że movq
dokonuje wyłuskiwania, a leaq
nie. Właściwie, aby napisać leaq
opis, po prostu skopiowałem + wkleiłem opis movq
, a następnie usunąłem „Znajdź wartość w tej lokalizacji”.
Podsumowując: movq
vs. leaq
jest 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 leaq
nich nie mają i są czysto wygodną składnią.
[1] Powiedziałem, że kiedy array
jest 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 p
wskazujący na wartości typu T
. Wyrażenie p + i
ma nie znaczyć „pozycja na p
plus i
bajtów”. Zamiast tego wyrażenie p + i
faktycznie oznacza „pozycję z p
plusem i * sizeof(T)
bajtów”.
Wygoda polega na tym, że aby uzyskać „następną wartość”, po prostu musimy p + 1
zamiast 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 5
przez 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 lea
robi 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 cc
z -O2
optymalizacją 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
&
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), %eax
po 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ń.
%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.
%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.
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
lea
jest 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.
Jak stwierdzono w innych odpowiedziach:
MOV
przechwyci dane na adres wewnątrz wsporników i miejscu, że dane do docelowego argumentu.LEA
wykona 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 LEA
polega na obliczaniu „efektywnego adresu”.Ponieważ pamięć może być adresowana na kilka różnych sposobów (patrz przykłady poniżej), LEA
czasami jest używana do dodawania lub mnożenia rejestrów razem bez użycia jawnej instrukcji ADD
lub MUL
instrukcji (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 */
lea label, %eax
absolutnego [disp32]
trybu adresowania. Użyj mov $label, %eax
zamiast 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.
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 ).
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.
lea [label
jest 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.
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.