Voor mij lijkt het gewoon een funky MOV. Wat is het doel en wanneer moet ik het gebruiken?
Antwoord 1, autoriteit 100%
Zoals anderen al hebben opgemerkt, wordt LEA (laadeffectief adres) vaak gebruikt als een “truc” om bepaalde berekeningen uit te voeren, maar dat is niet het primaire doel. De x86-instructieset is ontworpen om talen op hoog niveau te ondersteunen, zoals Pascal en C, waar arrays & mdash; vooral arrays van ints of kleine structs & mdash; gebruikelijk zijn. Beschouw bijvoorbeeld een struct die (x, y) coördinaten vertegenwoordigt:
struct Point
{
int xcoord;
int ycoord;
};
Stel je nu een uitspraak voor als:
int y = points[i].ycoord;
waarbij points[]
een array is van Point
. Ervan uitgaande dat de basis van de array zich al in ebx
bevindt, en variabele i
in EAX
, en xcoord
en ycoord
zijn elk 32 bits (dus ycoord
heeft een offset van 4 bytes in de struct), deze verklaring kan worden gecompileerd tot:
MOV EDX, [EBX + 8*EAX + 4] ; right side is "effective address"
die y
zal landen in EDX
. De schaalfactor van 8 is omdat elk Point
8 bytes groot is. Beschouw nu dezelfde uitdrukking die wordt gebruikt met de operator “adres van” &:
int *p = &points[i].ycoord;
In dit geval wil je niet de waarde van ycoord
, maar het adres. Dat is waar LEA
(laad effectief adres) om de hoek komt kijken. In plaats van een MOV
kan de compiler
LEA ESI, [EBX + 8*EAX + 4]
die het adres in ESI
laadt.
Antwoord 2, autoriteit 68%
Van de "Zen of Assembly" door Abrash:
LEA
, de enige instructie die geheugenadresseringsberekeningen uitvoert, maar het geheugen niet daadwerkelijk adresseert.LEA
accepteert een standaard geheugenadresseringsoperand, maar doet niets meer dan de berekende geheugenoffset op te slaan in het opgegeven register, dat een willekeurig register voor algemene doeleinden kan zijn.Wat levert dat ons op? Twee dingen die
ADD
niet biedt:
- de mogelijkheid om optellingen uit te voeren met twee of drie operanden, en
- de mogelijkheid om het resultaat op te slaan in elk register; niet alleen een van de bronoperanden.
En LEA
verandert de vlaggen niet.
Voorbeelden
LEA EAX, [ EAX + EBX + 1234567 ]
berekentEAX + EBX + 1234567
(dat zijn drie operanden)LEA EAX, [ EBX + ECX ]
berekentEBX + ECX
zonder een van beide te overschrijven met het resultaat.- vermenigvuldiging met constante (met twee, drie, vijf of negen), als je het gebruikt zoals
LEA EAX, [ EBX + N * EBX ]
(N kan 1,2,4,8 zijn ).
Andere usecase is handig in loops: het verschil tussen LEA EAX, [ EAX + 1 ]
en INC EAX
is dat de laatste EFLAGS
maar de eerste niet; hierdoor blijft de status CMP
behouden.
Antwoord 3, autoriteit 14%
Een ander belangrijk kenmerk van de LEA
-instructie is dat het de conditiecodes zoals CF
en ZF
niet verandert tijdens het berekenen van het adres door rekenkundige instructies zoals ADD
of MUL
doet. Deze functie vermindert de mate van afhankelijkheid tussen instructies en maakt zo ruimte voor verdere optimalisatie door de compiler of hardwareplanner.
Antwoord 4, autoriteit 12%
Ondanks alle uitleg is LEA een rekenkundige bewerking:
LEA Rt, [Rs1+a*Rs2+b] => Rt = Rs1 + a*Rs2 + b
Het is gewoon dat de naam extreem dom is voor een shift+add-bewerking. De reden daarvoor werd al uitgelegd in de best beoordeelde antwoorden (d.w.z. het was ontworpen om direct geheugenreferenties op hoog niveau in kaart te brengen).
Antwoord 5, autoriteit 10%
Misschien nog iets over LEA-instructie.
U kunt LEA ook gebruiken voor het snel vermenigvuldigen van registers met 3, 5 of 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
Antwoord 6, autoriteit 7%
LEA
is een afkorting van “laad effectief adres”. Het laadt het adres van de locatiereferentie door de bronoperand naar de bestemmingsoperand. U kunt het bijvoorbeeld gebruiken om:
lea ebx, [ebx+eax*8]
om ebx
pointer EAX
items verder te verplaatsen (in een 64-bit/element array) met een enkele instructie. Kortom, u profiteert van complexe adresseringsmodi die worden ondersteund door x86-architectuur om pointers efficiënt te manipuleren.
Antwoord 7, autoriteit 3%
De belangrijkste reden dat u LEA
gebruikt in plaats van een MOV
is als u rekenkunde moet uitvoeren op de registers die u gebruikt om het adres te berekenen. In feite kunt u wat neerkomt op aanwijzerberekeningen op verschillende van de registers in combinatie effectief “gratis” uitvoeren.
Wat er echt verwarrend aan is, is dat je meestal een LEA
schrijft, net als een MOV
, maar dat je het geheugen niet echt derefeert. Met andere woorden:
MOV EAX, [ESP+4]
Hierdoor wordt de inhoud van waar ESP+4
naar verwijst, verplaatst naar EAX
.
LEA EAX, [EBX*8]
Hierdoor wordt het effectieve adres EBX * 8
naar EAX verplaatst, niet naar het adres dat op die locatie wordt gevonden. Zoals je kunt zien, is het ook mogelijk om te vermenigvuldigen met factoren van twee (schalen) terwijl een MOV
beperkt is tot optellen/aftrekken.
Antwoord 8, autoriteit 3%
De 8086 heeft een grote reeks instructies die een registeroperand en een effectief adres accepteren, enkele berekeningen uitvoeren om het offset-gedeelte van dat effectieve adres te berekenen, en een bewerking uitvoeren met betrekking tot het register en het geheugen waarnaar wordt verwezen door het berekende adres . Het was vrij eenvoudig om een van de instructies in die familie zich te laten gedragen zoals hierboven, behalve dat de eigenlijke geheugenbewerking werd overgeslagen. Dus de instructies:
mov ax,[bx+si+5]
lea ax,[bx+si+5]
werden intern bijna identiek geïmplementeerd. Het verschil is een overgeslagen stap. Beide instructies werken ongeveer als volgt:
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
Waarom Intel dacht dat deze instructie de moeite waard was, weet ik niet precies, maar het feit dat het goedkoop te implementeren was, zou een grote factor zijn geweest. Een andere factor zou het feit zijn geweest dat Intel’s assembler toestond dat symbolen werden gedefinieerd ten opzichte van het BP
-register. Als fnord
werd gedefinieerd als een BP
-relatief symbool (bijv. BP+8
), zou men kunnen zeggen:
mov ax,fnord ; Equivalent to "mov ax,[BP+8]"
Als men iets als stosw
zou willen gebruiken om gegevens op te slaan op een BP-relatief adres, kunnen zeggen
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
was handiger dan:
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
Merk op dat het vergeten van de wereld "offset" zou ervoor zorgen dat de inhoud van locatie [BP+8]
, in plaats van de waarde 8, wordt toegevoegd aan DI
. Oeps.
Antwoord 9
Zoals de bestaande antwoorden vermeldden, heeft LEA
de voordelen van het uitvoeren van geheugenadresseringsberekeningen zonder toegang tot het geheugen, waarbij het rekenkundige resultaat wordt opgeslagen in een ander register in plaats van de eenvoudige vorm van optelinstructie. Het echte onderliggende prestatievoordeel is dat de moderne processor een aparte LEA ALU-eenheid en poort heeft voor effectieve adresgeneratie (inclusief LEA
en ander geheugenreferentie-adres), dit betekent de rekenkundige bewerking in LEA
en andere normale rekenkundige bewerkingen in ALU kunnen parallel in één kern worden uitgevoerd.
Bekijk dit artikel van Haswell-architectuur voor enkele details over LEA-eenheid:
http://www.realworldtech.com/haswell-cpu/4/
Een ander belangrijk punt dat niet in andere antwoorden wordt genoemd, is de LEA REG, [MemoryAddress]
-instructie is PIC (positie-onafhankelijke code) die het relatieve pc-adres in deze instructie codeert om te verwijzen naar MemoryAddress
. Dit is anders dan MOV REG, MemoryAddress
dat een relatief virtueel adres codeert en moet worden verplaatst/gepatcht in moderne besturingssystemen (zoals ASLR een veelvoorkomend kenmerk is). Dus LEA
kan worden gebruikt om dergelijke niet-PIC naar PIC te converteren.
Antwoord 10
De LEA-instructie (Load Effective Address) is een manier om het adres te verkrijgen dat voortkomt uit een van de geheugenadresseringsmodi van de Intel-processor.
Dat wil zeggen, als we een gegevensverplaatsing als deze hebben:
MOV EAX, <MEM-OPERAND>
het verplaatst de inhoud van de aangewezen geheugenlocatie naar het doelregister.
Als we de MOV
vervangen door LEA
, dan wordt het adres van de geheugenlocatie op precies dezelfde manier berekend door de <MEM-OPERAND>
adresuitdrukking. Maar in plaats van de inhoud van de geheugenlocatie, krijgen we de locatie zelf in de bestemming.
LEA
is geen specifieke rekenkundige instructie; het is een manier om het effectieve adres te onderscheppen dat voortkomt uit een van de geheugenadresseringsmodi van de processor.
We kunnen bijvoorbeeld LEA
gebruiken op slechts een eenvoudig direct adres. Er komt helemaal geen rekenkunde bij kijken:
MOV EAX, GLOBALVAR ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR ; fetch the address of GLOBALVAR into EAX.
Dit is geldig; we kunnen het testen bij de Linux-prompt:
$ 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
Hier is er geen toevoeging van een geschaalde waarde en geen offset. Nul wordt verplaatst naar EAX. We zouden dat ook kunnen doen met MOV met een onmiddellijke operand.
Dit is de reden waarom mensen die denken dat de haakjes in LEA
overbodig zijn, zich ernstig vergissen; de haakjes zijn geen LEA
-syntaxis, maar maken deel uit van de adresseringsmodus.
LEA is echt op hardwareniveau. De gegenereerde instructie codeert de feitelijke adresseringsmodus en de processor voert deze uit tot het punt waarop het adres wordt berekend. Vervolgens verplaatst het dat adres naar de bestemming in plaats van een geheugenreferentie te genereren. (Aangezien de adresberekening van een adresseringsmodus in een andere instructie geen effect heeft op CPU-vlaggen, heeft LEA
geen effect op CPU-vlaggen.)
Contrast met het laden van de waarde vanaf adres nul:
$ as
movl 0, %eax
$ objdump -d a.out | grep mov
0: 8b 04 25 00 00 00 00 mov 0x0,%eax
Het is een zeer vergelijkbare codering, zie je? Alleen de 8d
van LEA
is gewijzigd in 8b
.
Natuurlijk is deze LEA
-codering langer dan het verplaatsen van een onmiddellijke nul naar EAX
:
$ as
movl $0, %eax
$ objdump -d a.out | grep mov
0: b8 00 00 00 00 mov $0x0,%eax
Er is echter geen reden voor LEA
om deze mogelijkheid uit te sluiten, alleen omdat er een korter alternatief is; het is gewoon een orthogonale combinatie met de beschikbare adresseringsmodi.
Antwoord 11
De LEA-instructie kan worden gebruikt om tijdrovende berekeningen van effectieve adressen door de CPU te vermijden. Als een adres herhaaldelijk wordt gebruikt, is het effectiever om het in een register op te slaan in plaats van het effectieve adres telkens te berekenen wanneer het wordt gebruikt.
Antwoord 12
Het lijkt erop dat veel antwoorden al zijn voltooid, ik zou graag nog een voorbeeldcode willen toevoegen om te laten zien hoe de lea- en move-instructie anders werken wanneer ze hetzelfde expressieformaat hebben.
Om een lang verhaal kort te maken, lea-instructie en mov-instructies kunnen beide worden gebruikt met de haakjes die de src-operand van de instructies omsluiten. Als ze tussen de () staan, wordt de uitdrukking in de () op dezelfde manier berekend; twee instructies zullen de berekende waarde in de src-operand echter op een andere manier interpreteren.
Of de uitdrukking nu wordt gebruikt met de lea of mov, de src-waarde wordt als volgt berekend.
D ( Rb, Ri, S ) => (Reg[Rb]+S*Reg[Ri]+ D)
Wanneer het echter wordt gebruikt met de mov-instructie, probeert het toegang te krijgen tot de waarde waarnaar wordt verwezen door het adres dat wordt gegenereerd door de bovenstaande expressie en het op te slaan op de bestemming.
In tegenstelling hiermee, wanneer de lea-instructie wordt uitgevoerd met de bovenstaande uitdrukking, laadt het de gegenereerde waarde zoals het is naar de bestemming.
De onderstaande code voert de lea-instructie en mov-instructie uit met dezelfde parameter. Om het verschil op te vangen, heb ik echter een signaalhandler op gebruikersniveau toegevoegd om de segmentatiefout op te vangen die wordt veroorzaakt door toegang tot een verkeerd adres als gevolg van mov-instructie.
Voorbeeldcode
#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
);
}
Uitvoeringsresultaat
Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed
Antwoord 13
Hier is een voorbeeld.
// 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;
}
Met -O (optimaliseren) als compileroptie, zal gcc de lea-instructie voor de aangegeven coderegel vinden.
Antwoord 14
LEA: gewoon een “rekenkundige” instructie..
MOV draagt gegevens over tussen operanden, maar lea is gewoon aan het berekenen
Antwoord 15
Alle normale “bereken” instructies zoals vermenigvuldiging toevoegen, exclusief of de statusvlaggen instellen als nul, teken. Als u een ingewikkeld adres gebruikt, AX xor:= mem[0x333 +BX + 8*CX]
, worden de vlaggen ingesteld volgens de xor-bewerking.
Nu wilt u misschien het adres meerdere keren gebruiken. Het laden van een dergelijk adres in een register is nooit bedoeld om statusvlaggen in te stellen en gelukkig ook niet. De zinsnede “laad effectief adres” maakt de programmeur zich daarvan bewust. Dat is waar de rare uitdrukking vandaan komt.
Het is duidelijk dat als de processor eenmaal in staat is om het gecompliceerde adres te gebruiken om de inhoud te verwerken, hij deze ook voor andere doeleinden kan berekenen. Het kan inderdaad worden gebruikt om een transformatie x <- 3*x+1
in één instructie uit te voeren. Dit is een algemene regel bij het programmeren van assemblages: Gebruik de instructies, hoe je boot ook schommelt.
Het enige dat telt, is of de specifieke transformatie die door de instructie wordt belichaamd, nuttig voor u is.
Kortom
MOV, X| T| AX'| R| BX|
en
LEA, AX'| [BX]
hebben hetzelfde effect op AX maar niet op de statusvlaggen.
(Dit is de notatie ciasdis.)
Antwoord 16
Vergeef me als iemand het al zei, maar in de dagen van x86 toen geheugensegmentatie nog relevant was, krijg je misschien niet dezelfde resultaten met deze twee instructies:
LEA AX, DS:[0x1234]
en
LEA AX, CS:[0x1234]