Dla mnie to po prostu funky MOV. Jaki jest jego cel i kiedy powinienem go używać?
Dla mnie to po prostu funky MOV. Jaki jest jego cel i kiedy powinienem go używać?
Odpowiedzi:
Jak zauważyli inni, LEA (adres efektywnego ładowania) jest często używany jako „sztuczka” do wykonywania pewnych obliczeń, ale to nie jest jego główny cel. Zestaw instrukcji x86 został zaprojektowany do obsługi języków wysokiego poziomu, takich jak Pascal i C, gdzie tablice - szczególnie tablice int lub małe struktury - są powszechne. Rozważmy na przykład strukturę reprezentującą współrzędne (x, y):
struct Point
{
int xcoord;
int ycoord;
};
Teraz wyobraź sobie zdanie takie jak:
int y = points[i].ycoord;
gdzie points[]
jest tablica Point
. Przy założeniu, że podstawa tablicy jest już EBX
i zmienna i
w EAX
i xcoord
a ycoord
mają po 32 bitów (to ycoord
jest przy przesunięciu 4 bajty struktury), stwierdzenie można zestawiać do:
MOV EDX, [EBX + 8*EAX + 4] ; right side is "effective address"
które wylądują y
w EDX
. Współczynnik skali wynosi 8, ponieważ każdy Point
ma rozmiar 8 bajtów. Teraz rozważ to samo wyrażenie, którego użyto z operatorem „adres” i:
int *p = &points[i].ycoord;
W tym przypadku nie chcesz wartości ycoord
, ale jej adres. Tam właśnie LEA
pojawia się (adres efektywny). Zamiast a MOV
kompilator może generować
LEA ESI, [EBX + 8*EAX + 4]
który załaduje adres ESI
.
mov
instrukcję i pominąć nawiasy? MOV EDX, EBX + 8*EAX + 4
MOV
z pośrednim źródłem, z tą różnicą, że działa tylko pośrednio, a nie MOV
. W rzeczywistości nie odczytuje z obliczonego adresu, po prostu go oblicza.
Z „Zen of Assembly” Abrasha:
LEA
, jedyna instrukcja, która wykonuje obliczenia adresowania pamięci, ale tak naprawdę nie adresuje pamięci.LEA
akceptuje standardowy operand adresowania pamięci, ale robi jedynie przechowywanie obliczonego przesunięcia pamięci w określonym rejestrze, którym może być dowolny rejestr ogólnego przeznaczenia.Co nam to daje? Dwie rzeczy, które
ADD
nie zapewniają:
- możliwość wykonania dodawania za pomocą dwóch lub trzech operandów, oraz
- możliwość przechowywania wyniku w dowolnym rejestrze; nie tylko jeden z operandów źródłowych.
I LEA
nie zmienia flag.
Przykłady
LEA EAX, [ EAX + EBX + 1234567 ]
oblicza EAX + EBX + 1234567
(to trzy operandy)LEA EAX, [ EBX + ECX ]
oblicza EBX + ECX
bez nadpisywania wyniku.LEA EAX, [ EBX + N * EBX ]
(N może wynosić 1,2,4,8).Inny przypadek użycia jest przydatny w pętlach: różnica między LEA EAX, [ EAX + 1 ]
i INC EAX
polega na tym, że ten drugi się zmienia, EFLAGS
ale pierwszy nie; to zachowuje CMP
stan.
LEA EAX, [ EAX + EBX + 1234567 ]
oblicza sumę EAX
, EBX
a 1234567
(to trzy argumenty). LEA EAX, [ EBX + ECX ]
oblicza EBX + ECX
bez nadpisywania wyniku. Trzecią rzeczą, której LEA
się używa (nie wymienionej przez Franka), jest mnożenie przez stałą (przez dwa, trzy, pięć lub dziewięć), jeśli używasz jej w podobny sposób LEA EAX, [ EBX + N * EBX ]
( N
może być 1,2,4,8). Inny przypadek użycia jest przydatny w pętlach: różnica między LEA EAX, [ EAX + 1 ]
i INC EAX
polega na tym, że ten drugi się zmienia, EFLAGS
ale pierwszy nie; to zachowuje CMP
stan
LEA
można użyć do ... (patrz „LEA (ładowanie adresu efektywnego) jest często używany jako„ sztuczka ”do wykonania pewnych obliczeń” w popularnej odpowiedzi IJ Kennedy'ego powyżej)
Inną ważną cechą LEA
instrukcji jest to, że nie zmienia ona kodów warunków, takich jak CF
i ZF
, podczas obliczania adresu za pomocą instrukcji arytmetycznych, takich jak ADD
lub MUL
robi. Ta funkcja zmniejsza poziom zależności między instrukcjami, a tym samym umożliwia dalszą optymalizację kompilatora lub harmonogramu sprzętowego.
lea
czasem przydatne jest, aby kompilator (lub koder ludzki) wykonywał matematykę bez blokowania wyniku flagi. Ale lea
nie jest szybszy niż add
. Większość instrukcji x86 zapisuje flagi. Wysokowydajne implementacje x86 muszą zmienić nazwę EFLAGS lub w inny sposób uniknąć zagrożenia zapisu po zapisie, aby normalny kod działał szybko, więc instrukcje unikające zapisywania flag nie są z tego powodu lepsze. ( częściowe flagi mogą powodować problemy, patrz instrukcja INC vs ADD 1: Czy to ważne? )
Pomimo wszystkich wyjaśnień, LEA jest operacją arytmetyczną:
LEA Rt, [Rs1+a*Rs2+b] => Rt = Rs1 + a*Rs2 + b
Po prostu jego nazwa jest wyjątkowo głupia jak na operację shift + add. Powód tego został już wyjaśniony w najwyżej ocenianych odpowiedziach (tj. Został zaprojektowany do bezpośredniego mapowania odniesień do pamięci wysokiego poziomu).
LEA
na AGU, ale na zwykłych liczbach całkowitych ALU. W dzisiejszych czasach trzeba bardzo uważnie zapoznać się ze specyfikacją procesora, aby dowiedzieć się, „gdzie
LEA
podaje adres, który wynika z dowolnego trybu adresowania związanego z pamięcią. To nie jest operacja zmiany i dodania.
Może to kolejna rzecz dotycząca instrukcji LEA. Możesz także użyć LEA do szybkiego pomnożenia rejestrów przez 3, 5 lub 9.
LEA EAX, [EAX * 2 + EAX] ;EAX = EAX * 3
LEA EAX, [EAX * 4 + EAX] ;EAX = EAX * 5
LEA EAX, [EAX * 8 + EAX] ;EAX = EAX * 9
LEA EAX, [EAX*3]
?
shl
instrukcji shift w lewo do pomnożenia rejestrów przez 2,4,8,16 ... jest to szybsze i krótsze. Ale do mnożenia przez liczby różniące się potęgą 2 zwykle używamy mul
instrukcji, które są bardziej pretensjonalne i wolniejsze.
lea eax,[eax*3]
Tłumaczy się na ekwiwalent lea eax,[eax+eax*2]
.
lea
jest skrótem „efektywnego adresu ładowania”. Ładuje adres odwołania do lokalizacji przez operand źródłowy do operandu docelowego. Możesz na przykład użyć go do:
lea ebx, [ebx+eax*8]
aby przesunąć elementy ebx
wskaźnika eax
dalej (w tablicy 64-bit / element) za pomocą jednej instrukcji. Zasadniczo korzystasz ze złożonych trybów adresowania obsługiwanych przez architekturę x86, aby efektywnie manipulować wskaźnikami.
Największy powód, dla którego używasz LEA
ponadMOV
jest to, że musisz wykonać arytmetykę w rejestrach, których używasz do obliczania adresu. W efekcie można efektywnie wykonać „dowolną” kombinację arytmetyki wskaźnika na kilku rejestrach w połączeniu.
To, co jest naprawdę mylące, to fakt, że zwykle piszesz LEA
coś takiego, MOV
ale tak naprawdę nie dereferentujesz pamięci. Innymi słowy:
MOV EAX, [ESP+4]
Spowoduje to przeniesienie zawartości tego, na co ESP+4
wskazuje EAX
.
LEA EAX, [EBX*8]
Spowoduje to przeniesienie efektywnego adresu EBX * 8
do EAX, a nie tego, co znajduje się w tej lokalizacji. Jak widać, możliwe jest także pomnożenie przez dwa czynniki (skalowanie), podczas gdy a MOV
ogranicza się do dodawania / odejmowania.
LEA
dzieje.
8086 ma dużą rodzinę instrukcji, które akceptują operand rejestru i adres efektywny, wykonują pewne obliczenia w celu obliczenia przesuniętej części tego adresu efektywnego i wykonują pewne operacje obejmujące rejestr i pamięć, do których odnosi się obliczony adres. To było dość proste, aby jedna z instrukcji w tej rodzinie zachowywała się tak, jak powyżej, z wyjątkiem pomijania faktycznej operacji pamięci. To instrukcje:
mov ax,[bx+si+5]
lea ax,[bx+si+5]
zostały wdrożone prawie identycznie wewnętrznie. Różnica polega na pominięciu kroku. Obie instrukcje działają mniej więcej tak:
temp = fetched immediate operand (5)
temp += bx
temp += si
address_out = temp (skipped for LEA)
trigger 16-bit read (skipped for LEA)
temp = data_in (skipped for LEA)
ax = temp
Jeśli chodzi o to, dlaczego Intel uważał tę instrukcję za wartą włączenia, nie jestem do końca pewien, ale fakt, że jej wdrożenie było tanie, byłby dużym czynnikiem. Innym czynnikiem byłby fakt, że asembler Intela pozwalał na definiowanie symboli w stosunku do rejestru BP. Jeśli fnord
został zdefiniowany jako symbol względny BP (np. BP + 8), można powiedzieć:
mov ax,fnord ; Equivalent to "mov ax,[BP+8]"
Jeśli ktoś chce użyć czegoś takiego jak stosw do przechowywania danych pod adresem względnym BP, jest w stanie powiedzieć
mov ax,0 ; Data to store
mov cx,16 ; Number of words
lea di,fnord
rep movs fnord ; Address is ignored EXCEPT to note that it's an SS-relative word ptr
było wygodniejsze niż:
mov ax,0 ; Data to store
mov cx,16 ; Number of words
mov di,bp
add di,offset fnord (i.e. 8)
rep movs fnord ; Address is ignored EXCEPT to note that it's an SS-relative word ptr
Zauważ, że zapomnienie o „offsecie” świata spowodowałoby dodanie zawartości DI [BP + 8] zamiast wartości 8. Ups
Jak wspomniano w istniejących odpowiedziach, LEA
ma tę zaletę, że wykonuje arytmetykę adresowania pamięci bez dostępu do pamięci, zapisując wynik arytmetyki do innego rejestru zamiast prostej instrukcji dodawania. Prawdziwą korzyścią wynikającą z wydajności jest to, że nowoczesny procesor ma osobną jednostkę LEA ALU i port do efektywnego generowania adresu (w tym LEA
i inny adres odniesienia pamięci), co oznacza, że operacje arytmetyczne LEA
i inne normalne operacje arytmetyczne na ALU mogą być wykonywane równolegle w jednym rdzeń.
Zapoznaj się z tym artykułem o architekturze Haswell, aby uzyskać szczegółowe informacje na temat jednostki LEA: http://www.realworldtech.com/haswell-cpu/4/
Innym ważnym punktem, który nie jest wspomniany w innych odpowiedziach, jest LEA REG, [MemoryAddress]
instrukcja PIC (kod niezależny od pozycji), który koduje względny adres komputera w tej instrukcji w celu odniesienia MemoryAddress
. Różni się to od tego, MOV REG, MemoryAddress
który koduje względny adres wirtualny i wymaga relokacji / łatania we współczesnych systemach operacyjnych (np. ASLR jest wspólną cechą). Więc LEA
może być użyty do konwersji takiego non PIC PIC.
lea
na jednej lub więcej takich samych ALU, które wykonują inne instrukcje arytmetyczne (ale generalnie mniej z nich niż inne arytmetyki). Na przykład wspomniany procesor Haswella może wykonywać add
lub sub
większość innych podstawowych operacji arytmetycznych na czterech różnych jednostkach ALU, ale może wykonywać tylko lea
na jednej (złożonej lea
) lub dwóch (prostej lea
). Co ważniejsze, te dwie lea
zdolne do pracy jednostki ALU to po prostu dwie z czterech, które mogą wykonywać inne instrukcje, więc nie ma korzyści z równoległości, jak twierdzono.
Można użyć instrukcji LEA, aby uniknąć czasochłonnego obliczania efektywnych adresów przez CPU. Jeśli adres jest używany wielokrotnie, bardziej efektywne jest przechowywanie go w rejestrze zamiast obliczania adresu efektywnego za każdym razem, gdy jest używany.
[esi]
jest rzadko tańszy niż powiedzieć [esi + 4200]
i tylko rzadko jest tańszy niż [esi + ecx*8 + 4200]
.
[esi]
nie jest tańszy niż [esi + ecx*8 + 4200]
. Ale po co zawracać sobie głowę porównywaniem? Nie są równoważne. Jeśli chcesz, aby to pierwsze wyznaczyło tę samą lokalizację pamięci co drugie, potrzebujesz dodatkowych instrukcji: musisz dodać do esi
wartości ecx
pomnożonej przez 8. Uh, och, mnożenie spowoduje zatkanie flag procesora! Następnie musisz dodać 4200. Te dodatkowe instrukcje zwiększają rozmiar kodu (zajmowanie miejsca w pamięci podręcznej instrukcji, cykle do pobrania).
[esi + 4200]
wielokrotnie w sekwencji instrukcji, lepiej najpierw załadować efektywny adres do rejestru i użyć tego. Na przykład, zamiast pisać add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200]
, powinieneś preferować lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi]
, co rzadko jest szybsze. Przynajmniej taka jest prosta interpretacja tej odpowiedzi.
[esi]
i [esi + 4200]
(lub [esi + ecx*8 + 4200]
jest to, że jest to uproszczenie, jakie proponuje OP (jak rozumiem): że N instrukcji o tym samym złożonym adresie jest przekształcanych w N instrukcji z prostym adresowaniem (jeden reg) plus jeden lea
, ponieważ złożone adresowanie jest „czasochłonne”. W rzeczywistości jest wolniejsze nawet na współczesnych procesorach x86, ale tylko pod względem opóźnień, co wydaje się mało prawdopodobne w przypadku kolejnych instrukcji o tym samym adresie
lea
więc zwiększy to presję w tym przypadku. Ogólnie rzecz biorąc, przechowywanie półproduktów jest przyczyną presji rejestru, a nie rozwiązaniem tego problemu - ale myślę, że w większości sytuacji jest to pranie. @Kaz
Instrukcja LEA (Load Effective Address) to sposób na uzyskanie adresu, który wynika z dowolnego z trybów adresowania pamięci procesora Intel.
To znaczy, jeśli mamy taki ruch danych:
MOV EAX, <MEM-OPERAND>
przenosi zawartość wyznaczonej lokalizacji pamięci do rejestru docelowego.
Jeśli zastąpimy MOV
By LEA
, a następnie adres miejsca pamięci jest obliczana w ten sam sposób przez <MEM-OPERAND>
wypowiedzi adresowania. Ale zamiast zawartości lokalizacji pamięci, dostajemy samą lokalizację do miejsca docelowego.
LEA
nie jest konkretną instrukcją arytmetyczną; jest to sposób na przechwycenie efektywnego adresu wynikającego z dowolnego z trybów adresowania pamięci procesora.
Na przykład możemy użyć LEA
tylko na prosty adres bezpośredni. W ogóle nie jest zaangażowana arytmetyka:
MOV EAX, GLOBALVAR ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR ; fetch the address of GLOBALVAR into EAX.
To jest ważne; możemy to przetestować po znaku zachęty Linux:
$ as
LEA 0, %eax
$ objdump -d a.out
a.out: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 8d 04 25 00 00 00 00 lea 0x0,%eax
Tutaj nie ma dodanej wartości skalowanej ani przesunięcia. Zero zostaje przeniesione do EAX. Możemy to zrobić za pomocą MOV z natychmiastowym operandem.
To jest powód, dla którego ludzie, którzy uważają, że nawiasy LEA
są zbyteczne, są w błędzie; nawiasy klamrowe nie sąLEA
składniowe, ale są częścią trybu adresowania.
LEA jest prawdziwa na poziomie sprzętowym. Wygenerowana instrukcja koduje aktualny tryb adresowania, a procesor wykonuje go do momentu obliczenia adresu. Następnie przenosi ten adres do miejsca docelowego zamiast generowania odwołania do pamięci. (Ponieważ obliczanie adresu trybu adresowania w dowolnej innej instrukcji nie ma wpływu na flagi procesora,LEA
nie ma wpływu na flagi procesora).
Porównaj z ładowaniem wartości z adresu zero:
$ as
movl 0, %eax
$ objdump -d a.out | grep mov
0: 8b 04 25 00 00 00 00 mov 0x0,%eax
To bardzo podobne kodowanie, rozumiesz? Tylko 8d
od LEA
zmienił się8b
.
Oczywiście to LEA
kodowanie jest dłuższe niż przesunięcie natychmiastowego zera do EAX
:
$ as
movl $0, %eax
$ objdump -d a.out | grep mov
0: b8 00 00 00 00 mov $0x0,%eax
Nie ma jednak powodu, LEA
aby wykluczać tę możliwość tylko dlatego, że istnieje krótsza alternatywa; po prostu łączy się w ortogonalny sposób z dostępnymi trybami adresowania.
Oto przykład.
// compute parity of permutation from lexicographic index
int parity (int p)
{
assert (p >= 0);
int r = p, k = 1, d = 2;
while (p >= k) {
p /= d;
d += (k << 2) + 6; // only one lea instruction
k += 2;
r ^= p;
}
return r & 1;
}
Przy opcji -O (optymalizacja) jako opcji kompilatora, gcc znajdzie instrukcję lea dla wskazanej linii kodu.
Wygląda na to, że wiele odpowiedzi jest już kompletnych. Chciałbym dodać jeszcze jeden przykładowy kod pokazujący, jak instrukcja lea i move działają inaczej, gdy mają ten sam format wyrażenia.
Krótko mówiąc, instrukcja lea i instrukcja mov mogą być używane z nawiasami otaczającymi argument src instrukcji. Gdy są one ujęte w () , wyrażenie w () jest obliczane w ten sam sposób; jednak dwie instrukcje interpretują obliczoną wartość w operandzie src w inny sposób.
Bez względu na to, czy wyrażenie jest używane z lea czy mov, wartość src jest obliczana jak poniżej.
D (Rb, Ri, S) => (Reg [Rb] + S * Reg [Ri] + D)
Jednak gdy jest używane z instrukcją mov, próbuje uzyskać dostęp do wartości wskazanej przez adres wygenerowany przez powyższe wyrażenie i zapisać ją w miejscu docelowym.
W przeciwieństwie do tego, gdy instrukcja lea jest wykonywana z powyższym wyrażeniem, ładuje wygenerowaną wartość tak, jak jest do miejsca docelowego.
Poniższy kod wykonuje instrukcję lea i instrukcję mov z tym samym parametrem. Jednak, aby uchwycić różnicę, dodałem moduł obsługi sygnału na poziomie użytkownika, aby wychwycić błąd segmentacji spowodowany dostępem do niewłaściwego adresu w wyniku instrukcji mov.
Przykładowy kod
#define _GNU_SOURCE 1 /* To pick up REG_RIP */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>
uint32_t
register_handler (uint32_t event, void (*handler)(int, siginfo_t*, void*))
{
uint32_t ret = 0;
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
ret = sigaction(event, &act, NULL);
return ret;
}
void
segfault_handler (int signum, siginfo_t *info, void *priv)
{
ucontext_t *context = (ucontext_t *)(priv);
uint64_t rip = (uint64_t)(context->uc_mcontext.gregs[REG_RIP]);
uint64_t faulty_addr = (uint64_t)(info->si_addr);
printf("inst at 0x%lx tries to access memory at %ld, but failed\n",
rip,faulty_addr);
exit(1);
}
int
main(void)
{
int result_of_lea = 0;
register_handler(SIGSEGV, segfault_handler);
//initialize registers %eax = 1, %ebx = 2
// the compiler will emit something like
// mov $1, %eax
// mov $2, %ebx
// because of the input operands
asm("lea 4(%%rbx, %%rax, 8), %%edx \t\n"
:"=d" (result_of_lea) // output in EDX
: "a"(1), "b"(2) // inputs in EAX and EBX
: // no clobbers
);
//lea 4(rbx, rax, 8),%edx == lea (rbx + 8*rax + 4),%edx == lea(14),%edx
printf("Result of lea instruction: %d\n", result_of_lea);
asm volatile ("mov 4(%%rbx, %%rax, 8), %%edx"
:
: "a"(1), "b"(2)
: "edx" // if it didn't segfault, it would write EDX
);
}
Wynik wykonania
Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed
=d
aby poinformować kompilator, że wynik jest w EDX, zapisując a mov
. Pominięto również deklarację wczesnego clobbera na wyjściu. To pokazuje, co próbujesz zademonstrować, ale jest także mylącym złym przykładem wbudowanego asm, który się zepsuje, jeśli zostanie użyty w innych kontekstach. To zła rzecz dla odpowiedzi na przepełnienie stosu.
%%
na wszystkich nazwach rejestrów w Extended asm, użyj ograniczeń wejściowych. jak asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));
. Pozwolenie kompilatorowi na zarejestrowanie się oznacza, że nie musisz też deklarować clobberów. Nadmiernie komplikujesz rzeczy przez xor-zerowanie, zanim mov-instant również nadpisze cały rejestr.
mov 4(%ebx, %eax, 8), %edx
jest nieważny? W każdym razie tak, mov
ponieważ sensowne byłoby napisanie, "a"(1ULL)
aby poinformować kompilator, że masz wartość 64-bitową, a zatem musi upewnić się, że jest rozszerzony, aby wypełnić cały rejestr. W praktyce będzie nadal używany mov $1, %eax
, ponieważ pisanie EAX-zero rozszerza się do RAX, chyba że masz dziwną sytuację otaczającego kodu, w której kompilator wiedział, że RAX = 0xff00000001
czy coś. Bo lea
nadal używasz 32-bitowego rozmiaru argumentu, więc wszelkie zbłąkane wysokie bity w rejestrach wejściowych nie mają wpływu na wynik 32-bitowy.
LEA: tylko instrukcja „arytmetyczna”.
MOV przesyła dane między operandami, ale lea tylko oblicza
mov eax, offset GLOBALVAR
zamiast tego. Państwo może używać Lea, ale jest nieco większy niż rozmiar kodu mov r32, imm32
i działa na mniejszej liczbie portów, gdyż nadal przechodzi proces obliczania adresu . lea reg, symbol
jest użyteczny tylko w wersji 64-bitowej dla LEA zależnego od RIP, gdy potrzebujesz PIC i / lub adresów spoza niskich 32 bitów. W 32 lub 16-bitowym kodzie nie ma przewagi. LEA jest instrukcją arytmetyczną, która ujawnia zdolność CPU do dekodowania / obliczania trybów adresowania.
imul eax, edx, 1
nie oblicza: po prostu kopiuje edx do eax. Ale tak naprawdę uruchamia twoje dane przez multiplikator z 3 opóźnieniami cyklu. Lub to rorx eax, edx, 0
po prostu kopiuje (obraca o zero).
Wszystkie normalne instrukcje „obliczania”, takie jak dodawanie mnożenia, wyłączanie lub ustawianie flag statusu jak zero, znak. Jeśli używasz skomplikowanego adresu, AX xor:= mem[0x333 +BX + 8*CX]
flagi są ustawiane zgodnie z operacją xor.
Teraz możesz użyć adresu wiele razy. Ładowanie takiego adresu do rejestru nigdy nie ma na celu ustawiania flag statusu i na szczęście tak nie jest. Fraza „ładuj efektywny adres” uświadamia programiście. Stąd pochodzi ten dziwny wyraz.
Oczywiste jest, że gdy procesor będzie mógł użyć skomplikowanego adresu do przetworzenia jego zawartości, będzie mógł go obliczyć do innych celów. Rzeczywiście można go użyć do wykonania transformacji x <- 3*x+1
w jednej instrukcji. Jest to ogólna zasada w programowaniu asemblera: postępuj zgodnie z instrukcjami, jakkolwiek kołyszesz łódź.
Liczy się tylko to, czy konkretna transformacja zawarta w instrukcji jest dla Ciebie przydatna.
Dolna linia
MOV, X| T| AX'| R| BX|
i
LEA, AX'| [BX]
mają taki sam wpływ na AX, ale nie na flagi stanu. (To jest notacja ciasdis .)
call lbl
lbl: pop rax
technicznie „działa” jako sposób na uzyskanie wartości rip
, ale sprawisz, że przewidywanie gałęzi będzie bardzo niezadowolone.
Wybacz mi, jeśli ktoś już wspomniał, ale w dniach x86, kiedy segmentacja pamięci była nadal istotna, możesz nie uzyskać takich samych wyników z tych dwóch instrukcji:
LEA AX, DS:[0x1234]
i
LEA AX, CS:[0x1234]
seg:off
pary. Baza segmentów nie ma wpływu na LEA; obie instrukcje zostaną (nieefektywnie) wprowadzone 0x1234
do AX. x86 niestety nie ma łatwego sposobu na obliczenie pełnego adresu liniowego (efektywna + podstawa segmentu) do rejestru lub pary rejestrów.