Siehe auch Verwenden von LEA für Werte, die keine Adressen / Zeiger sind? : LEA ist nur eine Shift-and-Add-Anweisung. Es wurde wahrscheinlich zu 8086 hinzugefügt, weil die Hardware bereits zum Dekodieren und Berechnen von Adressierungsmodi vorhanden ist, nicht weil sie nur für die Verwendung mit Adressen "vorgesehen" ist. Denken Sie daran, dass Zeiger in Assembly nur Ganzzahlen sind.
Peter Cordes
Antworten:
797
Wie andere bereits betont haben, wird LEA (Load Effective Address) häufig als "Trick" für bestimmte Berechnungen verwendet, aber das ist nicht der Hauptzweck. Der x86-Befehlssatz wurde entwickelt, um Hochsprachen wie Pascal und C zu unterstützen, bei denen Arrays - insbesondere Arrays von Ints oder kleinen Strukturen - häufig vorkommen. Stellen Sie sich zum Beispiel eine Struktur vor, die (x, y) -Koordinaten darstellt:
struct Point
{
int xcoord;
int ycoord;
};
Stellen Sie sich nun eine Aussage vor wie:
int y = points[i].ycoord;
wo points[]ist ein Array von Point. Angenommen , die Basis der Anordnung ist bereits in EBXund variabel iist EAX, und xcoordund ycoordsind jeweils 32 Bit (so ycoordwird bei Offset 4 Bytes in der struct) Diese Anweisung kann zu erstellen:
MOV EDX, [EBX + 8*EAX + 4] ; right side is "effective address"
die landen yin EDX. Der Skalierungsfaktor von 8 liegt daran, dass jedes Point8 Byte groß ist. Betrachten Sie nun denselben Ausdruck, der mit dem Operator "Adresse von" & verwendet wird:
int *p = &points[i].ycoord;
In diesem Fall möchten Sie nicht den Wert von ycoord, sondern dessen Adresse. Hier kommt die LEA(effektive Adresse laden) ins Spiel. Anstelle von a MOVkann der Compiler generieren
Wäre es nicht sauberer gewesen, die movAnweisung zu erweitern und die Klammern wegzulassen? MOV EDX, EBX + 8*EAX + 4
Natan Yellin
14
@imacake Wenn Sie LEA durch ein spezielles MOV ersetzen, halten Sie die Syntax sauber: [] Klammern entsprechen immer der Dereferenzierung eines Zeigers in C. Ohne Klammern behandeln Sie immer den Zeiger selbst.
Natan Yellin
139
Das Rechnen in einer MOV-Anweisung (EBX + 8 * EAX + 4) ist ungültig. LEA ESI, [EBX + 8 * EAX + 4] ist gültig, da dies ein von x86 unterstützter Adressierungsmodus ist. en.wikipedia.org/wiki/X86#Addressing_modes
Erik
29
@ JonathanDickinson LEA ist wie eine MOVmit einer indirekten Quelle, außer dass es nur die Indirektion und nicht die tut MOV. Es liest nicht wirklich von der berechneten Adresse, sondern berechnet sie nur.
Hobbs
24
Erik, Tourkommentar ist nicht korrekt. MOV eax, [ebx + 8 * ecx + 4] ist gültig. MOV gibt jedoch den Inhalt des Speicherorts zurück, während LEA die Adresse
zurückgibt
562
Aus dem "Zen der Versammlung" von Abrash:
LEA, der einzige Befehl, der Speicheradressierungsberechnungen durchführt, den Speicher jedoch nicht adressiert. LEAakzeptiert einen Standardspeicheradressierungsoperanden, speichert jedoch lediglich den berechneten Speicherversatz in dem angegebenen Register, das ein beliebiges Allzweckregister sein kann.
Was gibt uns das? Zwei Dinge, ADDdie nicht bieten:
die Fähigkeit, eine Addition mit zwei oder drei Operanden durchzuführen, und
die Fähigkeit, das Ergebnis in einem beliebigen Register zu speichern ; nicht nur einer der Quelloperanden.
Und LEAändert die Flaggen nicht.
Beispiele
LEA EAX, [ EAX + EBX + 1234567 ]berechnet EAX + EBX + 1234567(das sind drei Operanden)
LEA EAX, [ EBX + ECX ]berechnet, EBX + ECXohne das Ergebnis zu überschreiben.
Multiplikation mit Konstante (mit zwei, drei, fünf oder neun), wenn Sie es wie verwenden LEA EAX, [ EBX + N * EBX ](N kann 1,2,4,8 sein).
Ein anderer Anwendungsfall ist in Schleifen praktisch: Der Unterschied zwischen LEA EAX, [ EAX + 1 ]und INC EAXbesteht darin, dass sich der letztere ändert EFLAGS, der erstere jedoch nicht; Dies bewahrt den CMPZustand.
@AbidRahmanK einige Beispiele: LEA EAX, [ EAX + EBX + 1234567 ]Berechnet die Summe von EAX, EBXund 1234567(das sind drei Operanden). LEA EAX, [ EBX + ECX ]berechnet, EBX + ECXohne das Ergebnis zu überschreiben. Das dritte, wofür LEA(nicht von Frank aufgeführt) verwendet wird, ist die Multiplikation mit der Konstanten (mit zwei, drei, fünf oder neun), wenn Sie es wie verwenden LEA EAX, [ EBX + N * EBX ]( Nkann 1,2,4,8 sein). Ein anderer Anwendungsfall ist in Schleifen praktisch: Der Unterschied zwischen LEA EAX, [ EAX + 1 ]und INC EAXbesteht darin, dass sich der letztere ändert EFLAGS, der erstere jedoch nicht; das bewahrt den CMPZustand
FrankH.
@FrankH. Ich verstehe immer noch nicht, also lädt es einen Zeiger auf irgendwo anders?
6
@ ripDaddy69 ja, irgendwie - wenn mit "Laden" gemeint ist "führt die Adressberechnung / Zeigerarithmetik durch". Es greift nicht auf den Speicher zu (dh es wird der Zeiger nicht "dereferenziert", wie er in C-Programmierbegriffen genannt wird).
FrankH.
2
+1: Dies macht deutlich, für welche Arten von 'Tricks' LEAverwendet werden kann ... (siehe "LEA (Load Effective Address) wird häufig als" Trick "verwendet, um bestimmte Berechnungen durchzuführen" in IJ Kennedys populärer Antwort oben)
Assad Ebrahim
3
Es gibt einen großen Unterschied zwischen 2 Operanden LEA, die schnell sind, und 3 Operanden LEA, die langsam sind. Das Intel Optimization-Handbuch besagt, dass LEA mit schnellem Pfad ein einzelner Zyklus ist und LEA mit langsamem Pfad drei Zyklen dauert. Darüber hinaus gibt es auf Skylake zwei Funktionseinheiten für schnelle Pfade (Ports 1 und 5) und nur eine Funktionseinheit für langsame Pfade (Port 1). Assembly / Compiler-Codierung Regel 33 im Handbuch warnt sogar vor der Verwendung von 3 Operanden LEA.
Olsonist
110
Ein weiteres wichtiges Merkmal des LEABefehls ist, dass er die Bedingungscodes wie CFund nicht ändert ZF, während die Adresse durch arithmetische Befehle wie ADDoder berechnet MULwird. Diese Funktion verringert die Abhängigkeit zwischen Anweisungen und bietet somit Raum für weitere Optimierungen durch den Compiler oder den Hardware-Scheduler.
Ja, leamanchmal ist es für den Compiler (oder den menschlichen Codierer) nützlich, zu rechnen, ohne ein Flag-Ergebnis zu beeinträchtigen. Ist leaaber nicht schneller als add. Die meisten x86-Anweisungen schreiben Flags. Hochleistungs-x86-Implementierungen müssen EFLAGS umbenennen oder auf andere Weise die Gefahr des Schreibens nach dem Schreiben vermeiden , damit normaler Code schnell ausgeführt werden kann. Daher sind Anweisungen, die das Schreiben von Flags vermeiden, aus diesem Grund nicht besser. ( Teilweise Flaggenmaterial kann Probleme verursachen, siehe INC-Anweisung gegen ADD 1: Ist das wichtig? )
Peter Cordes
2
@PeterCordes: Ich hasse es, das hier anzusprechen, aber - bin ich allein, wenn ich denke, dass dieses neue [x86-lea] -Tag überflüssig und unnötig ist?
Michael Petch
2
@ MichaelPetch: Ja, ich denke es ist zu spezifisch. Es scheint Anfänger zu verwirren, die die Maschinensprache nicht verstehen und dass alles (einschließlich Zeiger) nur Bits / Bytes / Ganzzahlen sind, daher gibt es viele Fragen mit einer großen Anzahl von Stimmen. Ein Tag dafür bedeutet jedoch, dass Platz für eine unbegrenzte Anzahl zukünftiger Fragen vorhanden ist, obwohl es tatsächlich etwa 2 oder 3 insgesamt gibt, die nicht nur Duplikate sind. (Was ist das? Wie wird es zum Multiplizieren von ganzen Zahlen verwendet? Und wie läuft es intern auf AGUs im Vergleich zu ALUs und mit welcher Latenz / welchem Durchsatz. Und vielleicht ist es ein "beabsichtigter" Zweck)
Peter Cordes
@PeterCordes: Ich stimme zu, und wenn überhaupt, sind all diese Beiträge, die bearbeitet werden, so ziemlich ein Duplikat einiger der spannenden LEA-bezogenen Fragen. Anstelle eines Tags sollten alle Duplikate identifiziert und imho markiert werden.
Michael Petch
1
@EvanCarroll: Hängen Sie daran, alle LEA-Fragen zu markieren, falls Sie noch nicht fertig sind. Wie oben erläutert, halten wir x86-lea für zu spezifisch für ein Tag, und es gibt nicht viel Spielraum für zukünftige nicht doppelte Fragen. Ich denke, es wäre eine Menge Arbeit, tatsächlich ein "bestes" Q & A als Dup-Ziel für die meisten von ihnen auszuwählen oder tatsächlich zu entscheiden, welche Mods zusammengeführt werden sollen.
Peter Cordes
93
Trotz aller Erklärungen ist LEA eine arithmetische Operation:
LEA Rt, [Rs1+a*Rs2+b] => Rt = Rs1 + a*Rs2 + b
Es ist nur so, dass sein Name für eine Shift + Add-Operation extrem dumm ist. Der Grund dafür wurde bereits in den am besten bewerteten Antworten erläutert (dh es wurde entwickelt, um Speicherreferenzen auf hoher Ebene direkt abzubilden).
Und dass die Arithmetik von der Adressberechnungshardware ausgeführt wird.
Ben Voigt
30
@ BenVoigt Ich habe das immer gesagt, weil ich ein alter Kerl bin :-) Traditionell haben x86-CPUs die Adressierungseinheiten dafür verwendet, stimmte zu. Aber die "Trennung" ist heutzutage sehr verschwommen. Einige CPUs haben überhaupt keine dedizierten AGUs mehr, andere haben sich dafür entschieden, nicht LEAauf den AGUs, sondern auf den normalen Ganzzahl-ALUs auszuführen . Man muss die CPU-Spezifikationen heutzutage sehr genau lesen, um herauszufinden, "wo Sachen laufen" ...
FrankH.
2
@FrankH.: CPUs außerhalb der Reihenfolge führen LEA normalerweise auf ALUs aus, während einige CPUs in der richtigen Reihenfolge (wie Atom) es manchmal auf AGUs ausführen (weil sie nicht mit der Verarbeitung eines Speicherzugriffs beschäftigt sein können).
Peter Cordes
3
Nein, der Name ist nicht dumm. LEAgibt Ihnen die Adresse an, die sich aus einem speicherbezogenen Adressierungsmodus ergibt. Es ist keine Shift- und Add-Operation.
Kaz
3
FWIW gibt es nur sehr wenige (wenn überhaupt) aktuelle x86-CPUs, die die Operation auf der AGU ausführen. Die meisten oder alle verwenden einfach eine ALU wie jede andere arithmetische Operation.
BeeOnRope
77
Vielleicht nur eine andere Sache über LEA-Unterricht. Sie können LEA auch zum schnellen Multiplizieren von Registern mit 3, 5 oder 9 verwenden.
+1 für den Trick. Aber ich möchte eine Frage stellen (mag dumm sein), warum nicht direkt mit drei davon multiplizieren LEA EAX, [EAX*3]?
Abid Rahman K
13
@Abid Rahman K: Es gibt keinen Befehl unter x86-CPU-Befehlssatz.
GJ.
50
@AbidRahmanK Trotz der Intel ASM-Syntax sieht es wie eine Multiplikation aus. Der Befehl lea kann nur Verschiebungsoperationen codieren. Der Opcode hat 2 Bits, um die Verschiebung zu beschreiben, daher können Sie nur mit 1,2,4 oder 8 multiplizieren.
ithkuil
6
@Koray Tugay: Sie können die shlAnweisung zum Verschieben nach links verwenden , um Register mit 2,4,8,16 zu multiplizieren ... es ist schneller und kürzer. Aber zum Multiplizieren mit Zahlen unterschiedlicher Potenz von 2 verwenden wir normalerweise mulAnweisungen, die anspruchsvoller und langsamer sind.
GJ.
7
@ GJ. Obwohl es keine solche Codierung gibt, akzeptieren einige Assembler dies als Abkürzung, z. B. fasm. Also zB lea eax,[eax*3]würde das gleichbedeutend sein mit lea eax,[eax+eax*2].
Ruslan
59
leaist eine Abkürzung für "effektive Adresse laden". Es lädt die Adresse der Ortsreferenz durch den Quelloperanden in den Zieloperanden. Zum Beispiel könnten Sie es verwenden, um:
lea ebx, [ebx+eax*8]
bewegen ebxZeiger eaxArtikel weiter (in einer 64-bit / Element - Array) mit einer einzigen Anweisung. Grundsätzlich profitieren Sie von komplexen Adressierungsmodi, die von der x86-Architektur unterstützt werden, um Zeiger effizient zu bearbeiten.
Der Hauptgrund, den Sie LEAüber a verwenden, MOVbesteht darin, dass Sie für die Register, die Sie zur Berechnung der Adresse verwenden, eine Arithmetik durchführen müssen. Effektiv können Sie effektiv "kostenlos" eine Zeigerarithmetik für mehrere Register in Kombination durchführen.
Was wirklich verwirrend ist, ist, dass Sie normalerweise ein LEAgenau wie ein schreiben, MOVaber den Speicher nicht wirklich dereferenzieren. Mit anderen Worten:
MOV EAX, [ESP+4]
Dadurch wird der Inhalt dessen verschoben, worauf ESP+4verwiesen wird EAX.
LEA EAX, [EBX*8]
Dadurch wird die effektive Adresse EBX * 8in EAX verschoben , nicht in das, was sich an diesem Speicherort befindet. Wie Sie sehen können, ist es auch möglich, mit zwei Faktoren zu multiplizieren (Skalierung), während a MOVauf das Addieren / Subtrahieren beschränkt ist.
Entschuldigung an alle. @ big.heart hat mich getäuscht, indem er vor drei Stunden eine Antwort darauf gegeben hat, damit sie in meiner Versammlungsfrage als "neu" angezeigt wird.
David Hoelzer
1
Warum verwendet die Syntax Klammern, wenn keine Speicheradressierung erfolgt?
Golopot
3
@ q4w56 Dies ist eines der Dinge, bei denen die Antwort lautet: "So machst du es einfach." Ich glaube, das ist einer der Gründe, warum es den Menschen so schwer fällt, herauszufinden, was sie LEAtun.
David Hoelzer
2
@ q4w56: Es handelt sich um eine Anweisung Shift + Add, die die Syntax des Speicheroperanden und die Codierung des Maschinencodes verwendet. Auf einigen CPUs wird möglicherweise sogar die AGU-Hardware verwendet, dies ist jedoch ein historisches Detail. Die immer noch relevante Tatsache ist, dass die Decoderhardware bereits zum Decodieren dieser Art von Shift + Add vorhanden ist und LEA es uns ermöglicht, sie für die Arithmetik anstelle der Speicheradressierung zu verwenden. (Oder für Adressberechnungen, wenn eine Eingabe tatsächlich ein Zeiger ist).
Peter Cordes
20
Der 8086 verfügt über eine große Familie von Befehlen, die einen Registeroperanden und eine effektive Adresse akzeptieren, einige Berechnungen durchführen, um den Versatzteil dieser effektiven Adresse zu berechnen, und einige Operationen ausführen, die das Register und den Speicher betreffen, auf die sich die berechnete Adresse bezieht. Es war ziemlich einfach, eine der Anweisungen in dieser Familie wie oben zu verhalten, außer dass diese eigentliche Speicheroperation übersprungen wurde. Dies sind die Anweisungen:
mov ax,[bx+si+5]
lea ax,[bx+si+5]
wurden intern fast identisch implementiert. Der Unterschied ist ein übersprungener Schritt. Beide Anweisungen funktionieren ungefähr so:
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
Ich bin mir nicht ganz sicher, warum Intel diese Anweisung für wertvoll hielt, aber die Tatsache, dass die Implementierung billig war, wäre ein großer Faktor gewesen. Ein weiterer Faktor wäre die Tatsache gewesen, dass Intels Assembler die Definition von Symbolen relativ zum BP-Register ermöglichte. Wenn fnordals BP-relatives Symbol definiert wurde (z. B. BP + 8), könnte man sagen:
mov ax,fnord ; Equivalent to "mov ax,[BP+8]"
Wenn man so etwas wie stosw verwenden wollte, um Daten an einer BP-relativen Adresse zu speichern, kann man sagen
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
war bequemer als:
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
Beachten Sie, dass das Vergessen des Welt- "Offsets" dazu führen würde, dass der Inhalt von Position [BP + 8] anstelle des Werts 8 zu DI hinzugefügt wird. Hoppla.
Wie die vorhandenen Antworten erwähnt haben, LEAhat dies den Vorteil, dass eine Speicheradressierungsarithmetik ohne Zugriff auf den Speicher durchgeführt wird und das arithmetische Ergebnis in einem anderen Register anstelle der einfachen Form der Additionsanweisung gespeichert wird. Der eigentliche Leistungsvorteil besteht darin, dass der moderne Prozessor über eine separate LEA-ALU-Einheit und einen separaten Port für eine effektive Adressgenerierung (einschließlich LEAund anderer Speicherreferenzadressen) verfügt. Dies bedeutet, dass die arithmetische Operation in LEAund andere normale arithmetische Operationen in ALU parallel in einer ausgeführt werden können Ader.
Ein weiterer wichtiger Punkt, der in anderen Antworten nicht erwähnt wird, ist der LEA REG, [MemoryAddress]Befehl PIC (positionsunabhängiger Code), der die relative PC-Adresse in diesem Befehl als Referenz codiert MemoryAddress. Dies unterscheidet sich von MOV REG, MemoryAddressder Codierung der relativen virtuellen Adresse und erfordert das Verschieben / Patchen in modernen Betriebssystemen (wie ASLR ist eine gemeinsame Funktion). So LEAkann verwendet werden, nicht PIC zu PIC zu konvertieren.
Der "separate LEA ALU" Teil ist größtenteils falsch. Moderne CPUs werden leaauf einer oder mehreren derselben ALUs ausgeführt, die andere arithmetische Befehle ausführen (im Allgemeinen jedoch weniger als andere arithmetische). Zum Beispiel erwähnte der Haswell - CPU ausführen kann addoder suboder die meisten anderen Grundrechenarten auf vier verschiedene ALUs, kann aber nur ausführen leaauf einem (Komplex lea) oder zwei (einfach lea). Noch wichtiger ist, dass diese zwei leafähigen ALUs einfach zwei der vier sind, die andere Befehle ausführen können, so dass es keinen behaupteten Parallelitätsvorteil gibt.
BeeOnRope
Der Artikel, den Sie (korrekt) verlinkt haben, zeigt, dass sich LEA am selben Port befindet wie eine Ganzzahl-ALU (add / sub / boolean) und die Ganzzahl-MUL-Einheit in Haswell. (Und Vektor-ALUs einschließlich FP ADD / MUL / FMA). Die einfache LEA-Einheit befindet sich an Port 5, auf dem auch ADD / SUB / Whatever sowie Vektor-Shuffles und andere Dinge ausgeführt werden. Der einzige Grund, warum ich nicht abstimme, ist, dass Sie auf die Verwendung von RIP-relativem LEA hinweisen (nur für x86-64).
Peter Cordes
8
Der LEA-Befehl kann verwendet werden, um zeitaufwändige Berechnungen effektiver Adressen durch die CPU zu vermeiden. Wenn eine Adresse wiederholt verwendet wird, ist es effektiver, sie in einem Register zu speichern, als die effektive Adresse jedes Mal zu berechnen, wenn sie verwendet wird.
Nicht unbedingt auf modernem x86. Die meisten Adressierungsmodi haben mit einigen Einschränkungen die gleichen Kosten. Ist [esi]also selten billiger als sagen [esi + 4200]und ist nur selten billiger als [esi + ecx*8 + 4200].
BeeOnRope
@BeeOnRope [esi]ist nicht billiger als [esi + ecx*8 + 4200]. Aber warum sich die Mühe machen zu vergleichen? Sie sind nicht gleichwertig. Wenn Sie möchten, dass der erstere denselben Speicherort wie der letztere bezeichnet, benötigen Sie zusätzliche Anweisungen: Sie müssen esiden Wert von ecxmultipliziert mit 8 addieren . Oh, die Multiplikation wird Ihre CPU-Flags blockieren! Dann müssen Sie den 4200 hinzufügen. Diese zusätzlichen Anweisungen erhöhen die Codegröße (belegen Speicherplatz im Anweisungscache, Zyklen zum Abrufen).
Kaz
2
@Kaz - Ich denke, Sie haben meinen Punkt verfehlt (oder ich habe den Punkt des OP verpasst). Mein Verständnis ist, dass das OP sagt, wenn Sie so etwas wie [esi + 4200]wiederholt in einer Folge von Anweisungen verwenden wollen, ist es besser, zuerst die effektive Adresse in ein Register zu laden und diese zu verwenden. Zum Beispiel add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200]sollten Sie lieber schreiben als schreiben lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi], was selten schneller ist. Zumindest ist das die einfache Interpretation dieser Antwort.
BeeOnRope
Der Grund, warum ich verglichen habe [esi]und [esi + 4200](oder [esi + ecx*8 + 4200]ist, dass dies die Vereinfachung ist, die das OP vorschlägt (so wie ich es verstehe): dass N Befehle mit derselben komplexen Adresse in N Befehle mit einfacher (ein reg) Adressierung plus eins umgewandelt werden lea, da komplexe Adressierung "zeitaufwändig" ist. Tatsächlich ist sie sogar auf modernem x86 langsamer, aber nur in
Bezug auf die
1
Vielleicht entlasten Sie den Registerdruck, ja - aber das Gegenteil kann der Fall sein: Wenn die Register, mit denen Sie die effektive Adresse generiert haben, aktiv sind, benötigen Sie ein anderes Register, um das Ergebnis zu speichern, leasodass der Druck in diesem Fall erhöht wird. Im Allgemeinen ist die Lagerung von Zwischenprodukten eine Ursache für Registerdruck, keine Lösung dafür - aber ich denke, in den meisten Situationen ist es eine Wäsche. @ Kaz
BeeOnRope
7
Mit dem LEA-Befehl (Load Effective Address) kann die Adresse ermittelt werden, die sich aus einem der Speicheradressierungsmodi des Intel-Prozessors ergibt.
Das heißt, wenn wir eine Datenverschiebung wie diese haben:
MOV EAX, <MEM-OPERAND>
Es verschiebt den Inhalt des angegebenen Speicherorts in das Zielregister.
Wenn wir das MOVdurch ersetzen LEA, wird die Adresse des Speicherorts durch den <MEM-OPERAND>Adressierungsausdruck genauso berechnet . Anstelle des Inhalts des Speicherorts erhalten wir jedoch den Speicherort selbst in das Ziel.
LEAist keine spezifische arithmetische Anweisung; Dies ist eine Möglichkeit, die effektive Adresse abzufangen, die sich aus einem der Speicheradressierungsmodi des Prozessors ergibt.
Zum Beispiel können wir LEAnur eine einfache direkte Adresse verwenden. Es ist überhaupt keine Arithmetik beteiligt:
MOV EAX, GLOBALVAR ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR ; fetch the address of GLOBALVAR into EAX.
Dies ist gültig; Wir können es an der Linux-Eingabeaufforderung testen:
$ 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 gibt es keine Addition eines skalierten Wertes und keinen Offset. Null wird in EAX verschoben. Wir könnten das auch mit MOV mit einem Sofortoperanden machen.
Dies ist der Grund, warum Leute, die denken, dass die Klammern LEAüberflüssig sind, sich schwer irren; Die Klammern sind keine LEASyntax, sondern Teil des Adressierungsmodus.
LEA ist auf Hardwareebene real. Der erzeugte Befehl codiert den tatsächlichen Adressierungsmodus und der Prozessor führt ihn bis zur Berechnung der Adresse aus. Dann wird diese Adresse an das Ziel verschoben, anstatt eine Speicherreferenz zu generieren. (Da die Adressberechnung eines Adressierungsmodus in einem anderen Befehl keine Auswirkung auf CPU-Flags hat, LEAhat dies keine Auswirkung auf CPU-Flags.)
Im Gegensatz zum Laden des Werts von Adresse Null:
Es gibt keinen Grund LEA, diese Möglichkeit auszuschließen, nur weil es eine kürzere Alternative gibt. Es wird nur orthogonal mit den verfügbaren Adressierungsmodi kombiniert.
// 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;
}
Mit -O (optimieren) als Compileroption findet gcc die lea-Anweisung für die angegebene Codezeile.
Es scheint, dass viele Antworten bereits vollständig sind. Ich möchte noch einen Beispielcode hinzufügen, um zu zeigen, wie die Anweisungen lea und move unterschiedlich funktionieren, wenn sie dasselbe Ausdrucksformat haben.
Um es kurz zu machen, können sowohl lea-Anweisungen als auch mov-Anweisungen mit den Klammern verwendet werden, die den src-Operanden der Anweisungen einschließen. Wenn sie mit () eingeschlossen sind , wird der Ausdruck in () auf die gleiche Weise berechnet. Zwei Anweisungen interpretieren den berechneten Wert im src-Operanden jedoch unterschiedlich.
Unabhängig davon, ob der Ausdruck mit lea oder mov verwendet wird, wird der src-Wert wie folgt berechnet.
D (Rb, Ri, S) => (Reg [Rb] + S * Reg [Ri] + D)
Wenn es jedoch mit der Anweisung mov verwendet wird, versucht es, auf den Wert zuzugreifen, auf den die durch den obigen Ausdruck erzeugte Adresse zeigt, und ihn im Ziel zu speichern.
Im Gegensatz dazu lädt der lea-Befehl, wenn er mit dem obigen Ausdruck ausgeführt wird, den generierten Wert so wie er ist in das Ziel.
Der folgende Code führt den Befehl lea und mov mit demselben Parameter aus. Um den Unterschied zu erkennen, habe ich jedoch einen Signalhandler auf Benutzerebene hinzugefügt, um den Segmentierungsfehler zu erfassen, der durch den Zugriff auf eine falsche Adresse infolge eines mov-Befehls verursacht wird.
Beispielcode
#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
);
}
Ausführungsergebnis
Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed
Das Aufteilen Ihres Inline-Asms in separate Anweisungen ist unsicher und Ihre Clobber-Listen sind unvollständig. Der Basic-Asm-Block teilt dem Compiler mit, dass er keine Clobber hat, ändert jedoch tatsächlich mehrere Register. Sie können =ddem Compiler auch mitteilen, dass das Ergebnis in EDX vorliegt, und a speichern mov. Sie haben auch eine Early-Clobber-Deklaration für die Ausgabe ausgelassen. Dies zeigt zwar, was Sie demonstrieren möchten, ist aber auch ein irreführendes Beispiel für Inline-Asm, das bei Verwendung in anderen Kontexten nicht funktioniert. Das ist eine schlechte Sache für eine Stapelüberlaufantwort.
Peter Cordes
Wenn Sie nicht %%auf alle diese Registernamen in Extended asm schreiben möchten , verwenden Sie Eingabeeinschränkungen. wie asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));. Wenn Sie die Compiler-Init-Register zulassen, müssen Sie auch keine Clobber deklarieren. Sie überkomplizieren die Dinge durch Xor-Zeroing, bevor mov-unmittelbar auch das gesamte Register überschreibt.
Peter Cordes
@PeterCordes Danke, Peter, soll ich diese Antwort löschen oder nach Ihren Kommentaren ändern?
Jaehyuk Lee
1
Wenn Sie den Inline-Asm korrigieren, schadet er nicht und ist möglicherweise ein gutes konkretes Beispiel für Anfänger, die die anderen Antworten nicht verstanden haben. Keine Notwendigkeit zu löschen, und es ist eine einfache Lösung, wie ich in meinem letzten Kommentar gezeigt habe. Ich denke, es wäre eine positive Bewertung wert, wenn das schlechte Beispiel für Inline-Asm in ein "gutes" Beispiel umgewandelt würde. (Ich habe nicht abgelehnt)
Peter Cordes
1
Wo sagt jemand, dass mov 4(%ebx, %eax, 8), %edxdas ungültig ist? Wie auch immer, ja, denn moves wäre sinnvoll zu schreiben "a"(1ULL), um dem Compiler mitzuteilen, dass Sie einen 64-Bit-Wert haben, und daher muss er sicherstellen, dass er erweitert ist, um das gesamte Register zu füllen. In der Praxis wird es weiterhin verwendet mov $1, %eax, da das Schreiben von EAX mit Null in RAX erweitert wird, es sei denn, Sie haben eine seltsame Situation mit umgebendem Code, in der der Compiler wusste, dass RAX = 0xff00000001oder so. Denn lea, sind Sie noch mit 32-Bit - Operanden-Größe, so dass die alle Streu High - Bits in Eingangsregister haben keinen Einfluss auf die 32-Bit - Ergebnis.
Peter Cordes
4
LEA: nur eine "arithmetische" Anweisung.
MOV überträgt Daten zwischen Operanden, aber lea berechnet nur
LEA verschiebt offensichtlich Daten; Es hat einen Zieloperanden. LEA berechnet nicht immer; Es wird berechnet, ob die im Quelloperanden ausgedrückte effektive Adresse berechnet wird. LEA EAX, GLOBALVAR berechnet nicht; Es wird nur die Adresse von GLOBALVAR in EAX verschoben.
Kaz
@ Kaz danke für dein Feedback. Meine Quelle war "LEA (Load Effective Address) ist im Wesentlichen eine arithmetische Anweisung - sie führt keinen tatsächlichen Speicherzugriff durch, wird jedoch häufig zur Berechnung von Adressen verwendet (obwohl Sie damit Ganzzahlen für allgemeine Zwecke berechnen können)." Formular Eldad-Eilam Buch Seite 149
der Buchhalter
@Kaz: Deshalb ist LEA redundant, wenn die Adresse bereits eine Verbindungszeitkonstante ist. Verwenden Sie mov eax, offset GLOBALVARstattdessen. Sie können LEA verwenden, es ist jedoch etwas größer als der Code mov r32, imm32und wird auf weniger Ports ausgeführt, da der Adressberechnungsprozess noch durchlaufen wird . lea reg, symbolist nur in 64-Bit für eine RIP-relative LEA nützlich, wenn Sie PIC und / oder Adressen außerhalb der niedrigen 32 Bit benötigen. In 32- oder 16-Bit-Code gibt es keinen Vorteil. LEA ist eine arithmetische Anweisung, die die Fähigkeit der CPU offenlegt, Adressierungsmodi zu decodieren / zu berechnen.
Peter Cordes
@Kaz: Mit dem gleichen Argument könnte man sagen, dass imul eax, edx, 1das nicht kalkuliert: Es kopiert nur edx nach eax. Tatsächlich werden Ihre Daten jedoch mit einer Latenz von 3 Zyklen durch den Multiplikator geleitet. Oder das rorx eax, edx, 0kopiert einfach (um Null drehen).
Peter Cordes
@PeterCordes Mein Punkt ist, dass sowohl LEA EAX, GLOBALVAL als auch MOV EAX, GLOBALVAR nur die Adresse von einem unmittelbaren Operanden abrufen. Es wird kein Multiplikator von 1 oder Offset von 0 angewendet. Dies könnte auf Hardwareebene so sein, wird jedoch in der Assemblersprache oder im Befehlssatz nicht angezeigt.
Kaz
1
Alle normalen "Berechnungs" -Anweisungen wie das Hinzufügen von Multiplikationen, Exklusiv- oder Setzen der Statusflags wie Null, Vorzeichen. Wenn Sie eine komplizierte Adresse verwenden, werden AX xor:= mem[0x333 +BX + 8*CX] die Flags gemäß der xor-Operation gesetzt.
Jetzt möchten Sie die Adresse möglicherweise mehrmals verwenden. Das Laden einer solchen Adresse in ein Register soll niemals Statusflags setzen und zum Glück nicht. Der Ausdruck "effektive Adresse laden" macht den Programmierer darauf aufmerksam. Von dort kommt der seltsame Ausdruck.
Es ist klar, dass der Prozessor, sobald er in der Lage ist, die komplizierte Adresse zur Verarbeitung seines Inhalts zu verwenden, diese für andere Zwecke berechnen kann. In der Tat kann es verwendet werden, um eine Transformation x <- 3*x+1in einem Befehl durchzuführen . Dies ist eine allgemeine Regel bei der Baugruppenprogrammierung: Verwenden Sie die Anweisungen, jedoch wird Ihr Boot dadurch erschüttert.
Es kommt nur darauf an, ob die in der Anweisung enthaltene Transformation für Sie nützlich ist.
Endeffekt
MOV, X| T| AX'| R| BX|
und
LEA, AX'| [BX]
haben den gleichen Effekt auf AX, jedoch nicht auf die Statusflags. (Dies ist die Ciasdis- Notation.)
"Dies ist eine allgemeine Regel bei der Baugruppenprogrammierung: Verwenden Sie die Anweisungen, aber es rockt Ihr Boot." Ich würde diesen Rat nicht persönlich aussprechen, weil Dinge wie call lbllbl: pop rax"technisch" funktionieren, um den Wert zu ermitteln rip, aber Sie werden die Branchenvorhersage sehr unglücklich machen. Verwenden Sie die Anweisungen, wie Sie möchten, aber wundern Sie sich nicht, wenn Sie etwas
Kniffliges
@ The6P4C Das ist eine nützliche Einschränkung. Wenn es jedoch keine Alternative gibt, um die Verzweigungsvorhersage unglücklich zu machen, muss man sich dafür entscheiden. Es gibt eine andere allgemeine Regel bei der Baugruppenprogrammierung. Es kann alternative Möglichkeiten geben, etwas zu tun, und Sie müssen mit Bedacht aus Alternativen auswählen. Es gibt Hunderte von Möglichkeiten, den Inhalt des Registers BL in das Register AL zu übertragen. Wenn der Rest von RAX nicht erhalten bleiben muss, kann LEA eine Option sein. Bei einigen der Tausenden von x86-Prozessortypen ist es möglicherweise eine gute Idee, die Flags nicht zu beeinflussen. Groetjes Albert
Antworten:
Wie andere bereits betont haben, wird LEA (Load Effective Address) häufig als "Trick" für bestimmte Berechnungen verwendet, aber das ist nicht der Hauptzweck. Der x86-Befehlssatz wurde entwickelt, um Hochsprachen wie Pascal und C zu unterstützen, bei denen Arrays - insbesondere Arrays von Ints oder kleinen Strukturen - häufig vorkommen. Stellen Sie sich zum Beispiel eine Struktur vor, die (x, y) -Koordinaten darstellt:
Stellen Sie sich nun eine Aussage vor wie:
wo
points[]
ist ein Array vonPoint
. Angenommen , die Basis der Anordnung ist bereits inEBX
und variabeli
istEAX
, undxcoord
undycoord
sind jeweils 32 Bit (soycoord
wird bei Offset 4 Bytes in der struct) Diese Anweisung kann zu erstellen:die landen
y
inEDX
. Der Skalierungsfaktor von 8 liegt daran, dass jedesPoint
8 Byte groß ist. Betrachten Sie nun denselben Ausdruck, der mit dem Operator "Adresse von" & verwendet wird:In diesem Fall möchten Sie nicht den Wert von
ycoord
, sondern dessen Adresse. Hier kommt dieLEA
(effektive Adresse laden) ins Spiel. Anstelle von aMOV
kann der Compiler generierenDadurch wird die Adresse geladen
ESI
.quelle
mov
Anweisung zu erweitern und die Klammern wegzulassen?MOV EDX, EBX + 8*EAX + 4
MOV
mit einer indirekten Quelle, außer dass es nur die Indirektion und nicht die tutMOV
. Es liest nicht wirklich von der berechneten Adresse, sondern berechnet sie nur.Aus dem "Zen der Versammlung" von Abrash:
Und
LEA
ändert die Flaggen nicht.Beispiele
LEA EAX, [ EAX + EBX + 1234567 ]
berechnetEAX + EBX + 1234567
(das sind drei Operanden)LEA EAX, [ EBX + ECX ]
berechnet,EBX + ECX
ohne das Ergebnis zu überschreiben.LEA EAX, [ EBX + N * EBX ]
(N kann 1,2,4,8 sein).Ein anderer Anwendungsfall ist in Schleifen praktisch: Der Unterschied zwischen
LEA EAX, [ EAX + 1 ]
undINC EAX
besteht darin, dass sich der letztere ändertEFLAGS
, der erstere jedoch nicht; Dies bewahrt denCMP
Zustand.quelle
LEA EAX, [ EAX + EBX + 1234567 ]
Berechnet die Summe vonEAX
,EBX
und1234567
(das sind drei Operanden).LEA EAX, [ EBX + ECX ]
berechnet,EBX + ECX
ohne das Ergebnis zu überschreiben. Das dritte, wofürLEA
(nicht von Frank aufgeführt) verwendet wird, ist die Multiplikation mit der Konstanten (mit zwei, drei, fünf oder neun), wenn Sie es wie verwendenLEA EAX, [ EBX + N * EBX ]
(N
kann 1,2,4,8 sein). Ein anderer Anwendungsfall ist in Schleifen praktisch: Der Unterschied zwischenLEA EAX, [ EAX + 1 ]
undINC EAX
besteht darin, dass sich der letztere ändertEFLAGS
, der erstere jedoch nicht; das bewahrt denCMP
ZustandLEA
verwendet werden kann ... (siehe "LEA (Load Effective Address) wird häufig als" Trick "verwendet, um bestimmte Berechnungen durchzuführen" in IJ Kennedys populärer Antwort oben)Ein weiteres wichtiges Merkmal des
LEA
Befehls ist, dass er die Bedingungscodes wieCF
und nicht ändertZF
, während die Adresse durch arithmetische Befehle wieADD
oder berechnetMUL
wird. Diese Funktion verringert die Abhängigkeit zwischen Anweisungen und bietet somit Raum für weitere Optimierungen durch den Compiler oder den Hardware-Scheduler.quelle
lea
manchmal ist es für den Compiler (oder den menschlichen Codierer) nützlich, zu rechnen, ohne ein Flag-Ergebnis zu beeinträchtigen. Istlea
aber nicht schneller alsadd
. Die meisten x86-Anweisungen schreiben Flags. Hochleistungs-x86-Implementierungen müssen EFLAGS umbenennen oder auf andere Weise die Gefahr des Schreibens nach dem Schreiben vermeiden , damit normaler Code schnell ausgeführt werden kann. Daher sind Anweisungen, die das Schreiben von Flags vermeiden, aus diesem Grund nicht besser. ( Teilweise Flaggenmaterial kann Probleme verursachen, siehe INC-Anweisung gegen ADD 1: Ist das wichtig? )Trotz aller Erklärungen ist LEA eine arithmetische Operation:
Es ist nur so, dass sein Name für eine Shift + Add-Operation extrem dumm ist. Der Grund dafür wurde bereits in den am besten bewerteten Antworten erläutert (dh es wurde entwickelt, um Speicherreferenzen auf hoher Ebene direkt abzubilden).
quelle
LEA
auf den AGUs, sondern auf den normalen Ganzzahl-ALUs auszuführen . Man muss die CPU-Spezifikationen heutzutage sehr genau lesen, um herauszufinden, "wo Sachen laufen" ...LEA
gibt Ihnen die Adresse an, die sich aus einem speicherbezogenen Adressierungsmodus ergibt. Es ist keine Shift- und Add-Operation.Vielleicht nur eine andere Sache über LEA-Unterricht. Sie können LEA auch zum schnellen Multiplizieren von Registern mit 3, 5 oder 9 verwenden.
quelle
LEA EAX, [EAX*3]
?shl
Anweisung zum Verschieben nach links verwenden , um Register mit 2,4,8,16 zu multiplizieren ... es ist schneller und kürzer. Aber zum Multiplizieren mit Zahlen unterschiedlicher Potenz von 2 verwenden wir normalerweisemul
Anweisungen, die anspruchsvoller und langsamer sind.lea eax,[eax*3]
würde das gleichbedeutend sein mitlea eax,[eax+eax*2]
.lea
ist eine Abkürzung für "effektive Adresse laden". Es lädt die Adresse der Ortsreferenz durch den Quelloperanden in den Zieloperanden. Zum Beispiel könnten Sie es verwenden, um:bewegen
ebx
Zeigereax
Artikel weiter (in einer 64-bit / Element - Array) mit einer einzigen Anweisung. Grundsätzlich profitieren Sie von komplexen Adressierungsmodi, die von der x86-Architektur unterstützt werden, um Zeiger effizient zu bearbeiten.quelle
Der Hauptgrund, den Sie
LEA
über a verwenden,MOV
besteht darin, dass Sie für die Register, die Sie zur Berechnung der Adresse verwenden, eine Arithmetik durchführen müssen. Effektiv können Sie effektiv "kostenlos" eine Zeigerarithmetik für mehrere Register in Kombination durchführen.Was wirklich verwirrend ist, ist, dass Sie normalerweise ein
LEA
genau wie ein schreiben,MOV
aber den Speicher nicht wirklich dereferenzieren. Mit anderen Worten:MOV EAX, [ESP+4]
Dadurch wird der Inhalt dessen verschoben, worauf
ESP+4
verwiesen wirdEAX
.LEA EAX, [EBX*8]
Dadurch wird die effektive Adresse
EBX * 8
in EAX verschoben , nicht in das, was sich an diesem Speicherort befindet. Wie Sie sehen können, ist es auch möglich, mit zwei Faktoren zu multiplizieren (Skalierung), während aMOV
auf das Addieren / Subtrahieren beschränkt ist.quelle
LEA
tun.Der 8086 verfügt über eine große Familie von Befehlen, die einen Registeroperanden und eine effektive Adresse akzeptieren, einige Berechnungen durchführen, um den Versatzteil dieser effektiven Adresse zu berechnen, und einige Operationen ausführen, die das Register und den Speicher betreffen, auf die sich die berechnete Adresse bezieht. Es war ziemlich einfach, eine der Anweisungen in dieser Familie wie oben zu verhalten, außer dass diese eigentliche Speicheroperation übersprungen wurde. Dies sind die Anweisungen:
wurden intern fast identisch implementiert. Der Unterschied ist ein übersprungener Schritt. Beide Anweisungen funktionieren ungefähr so:
Ich bin mir nicht ganz sicher, warum Intel diese Anweisung für wertvoll hielt, aber die Tatsache, dass die Implementierung billig war, wäre ein großer Faktor gewesen. Ein weiterer Faktor wäre die Tatsache gewesen, dass Intels Assembler die Definition von Symbolen relativ zum BP-Register ermöglichte. Wenn
fnord
als BP-relatives Symbol definiert wurde (z. B. BP + 8), könnte man sagen:Wenn man so etwas wie stosw verwenden wollte, um Daten an einer BP-relativen Adresse zu speichern, kann man sagen
war bequemer als:
Beachten Sie, dass das Vergessen des Welt- "Offsets" dazu führen würde, dass der Inhalt von Position [BP + 8] anstelle des Werts 8 zu DI hinzugefügt wird. Hoppla.
quelle
Wie die vorhandenen Antworten erwähnt haben,
LEA
hat dies den Vorteil, dass eine Speicheradressierungsarithmetik ohne Zugriff auf den Speicher durchgeführt wird und das arithmetische Ergebnis in einem anderen Register anstelle der einfachen Form der Additionsanweisung gespeichert wird. Der eigentliche Leistungsvorteil besteht darin, dass der moderne Prozessor über eine separate LEA-ALU-Einheit und einen separaten Port für eine effektive Adressgenerierung (einschließlichLEA
und anderer Speicherreferenzadressen) verfügt. Dies bedeutet, dass die arithmetische Operation inLEA
und andere normale arithmetische Operationen in ALU parallel in einer ausgeführt werden können Ader.In diesem Artikel der Haswell-Architektur finden Sie einige Details zur LEA-Einheit: http://www.realworldtech.com/haswell-cpu/4/
Ein weiterer wichtiger Punkt, der in anderen Antworten nicht erwähnt wird, ist der
LEA REG, [MemoryAddress]
Befehl PIC (positionsunabhängiger Code), der die relative PC-Adresse in diesem Befehl als Referenz codiertMemoryAddress
. Dies unterscheidet sich vonMOV REG, MemoryAddress
der Codierung der relativen virtuellen Adresse und erfordert das Verschieben / Patchen in modernen Betriebssystemen (wie ASLR ist eine gemeinsame Funktion). SoLEA
kann verwendet werden, nicht PIC zu PIC zu konvertieren.quelle
lea
auf einer oder mehreren derselben ALUs ausgeführt, die andere arithmetische Befehle ausführen (im Allgemeinen jedoch weniger als andere arithmetische). Zum Beispiel erwähnte der Haswell - CPU ausführen kannadd
odersub
oder die meisten anderen Grundrechenarten auf vier verschiedene ALUs, kann aber nur ausführenlea
auf einem (Komplexlea
) oder zwei (einfachlea
). Noch wichtiger ist, dass diese zweilea
fähigen ALUs einfach zwei der vier sind, die andere Befehle ausführen können, so dass es keinen behaupteten Parallelitätsvorteil gibt.Der LEA-Befehl kann verwendet werden, um zeitaufwändige Berechnungen effektiver Adressen durch die CPU zu vermeiden. Wenn eine Adresse wiederholt verwendet wird, ist es effektiver, sie in einem Register zu speichern, als die effektive Adresse jedes Mal zu berechnen, wenn sie verwendet wird.
quelle
[esi]
also selten billiger als sagen[esi + 4200]
und ist nur selten billiger als[esi + ecx*8 + 4200]
.[esi]
ist nicht billiger als[esi + ecx*8 + 4200]
. Aber warum sich die Mühe machen zu vergleichen? Sie sind nicht gleichwertig. Wenn Sie möchten, dass der erstere denselben Speicherort wie der letztere bezeichnet, benötigen Sie zusätzliche Anweisungen: Sie müssenesi
den Wert vonecx
multipliziert mit 8 addieren . Oh, die Multiplikation wird Ihre CPU-Flags blockieren! Dann müssen Sie den 4200 hinzufügen. Diese zusätzlichen Anweisungen erhöhen die Codegröße (belegen Speicherplatz im Anweisungscache, Zyklen zum Abrufen).[esi + 4200]
wiederholt in einer Folge von Anweisungen verwenden wollen, ist es besser, zuerst die effektive Adresse in ein Register zu laden und diese zu verwenden. Zum Beispieladd eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200]
sollten Sie lieber schreiben als schreibenlea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi]
, was selten schneller ist. Zumindest ist das die einfache Interpretation dieser Antwort.[esi]
und[esi + 4200]
(oder[esi + ecx*8 + 4200]
ist, dass dies die Vereinfachung ist, die das OP vorschlägt (so wie ich es verstehe): dass N Befehle mit derselben komplexen Adresse in N Befehle mit einfacher (ein reg) Adressierung plus eins umgewandelt werdenlea
, da komplexe Adressierung "zeitaufwändig" ist. Tatsächlich ist sie sogar auf modernem x86 langsamer, aber nur inlea
sodass der Druck in diesem Fall erhöht wird. Im Allgemeinen ist die Lagerung von Zwischenprodukten eine Ursache für Registerdruck, keine Lösung dafür - aber ich denke, in den meisten Situationen ist es eine Wäsche. @ KazMit dem LEA-Befehl (Load Effective Address) kann die Adresse ermittelt werden, die sich aus einem der Speicheradressierungsmodi des Intel-Prozessors ergibt.
Das heißt, wenn wir eine Datenverschiebung wie diese haben:
Es verschiebt den Inhalt des angegebenen Speicherorts in das Zielregister.
Wenn wir das
MOV
durch ersetzenLEA
, wird die Adresse des Speicherorts durch den<MEM-OPERAND>
Adressierungsausdruck genauso berechnet . Anstelle des Inhalts des Speicherorts erhalten wir jedoch den Speicherort selbst in das Ziel.LEA
ist keine spezifische arithmetische Anweisung; Dies ist eine Möglichkeit, die effektive Adresse abzufangen, die sich aus einem der Speicheradressierungsmodi des Prozessors ergibt.Zum Beispiel können wir
LEA
nur eine einfache direkte Adresse verwenden. Es ist überhaupt keine Arithmetik beteiligt:Dies ist gültig; Wir können es an der Linux-Eingabeaufforderung testen:
Hier gibt es keine Addition eines skalierten Wertes und keinen Offset. Null wird in EAX verschoben. Wir könnten das auch mit MOV mit einem Sofortoperanden machen.
Dies ist der Grund, warum Leute, die denken, dass die Klammern
LEA
überflüssig sind, sich schwer irren; Die Klammern sind keineLEA
Syntax, sondern Teil des Adressierungsmodus.LEA ist auf Hardwareebene real. Der erzeugte Befehl codiert den tatsächlichen Adressierungsmodus und der Prozessor führt ihn bis zur Berechnung der Adresse aus. Dann wird diese Adresse an das Ziel verschoben, anstatt eine Speicherreferenz zu generieren. (Da die Adressberechnung eines Adressierungsmodus in einem anderen Befehl keine Auswirkung auf CPU-Flags hat,
LEA
hat dies keine Auswirkung auf CPU-Flags.)Im Gegensatz zum Laden des Werts von Adresse Null:
Es ist eine sehr ähnliche Codierung, sehen Sie? Nur das
8d
vonLEA
hat sich geändert zu8b
.Natürlich ist diese
LEA
Codierung länger als das Verschieben einer unmittelbaren Null inEAX
:Es gibt keinen Grund
LEA
, diese Möglichkeit auszuschließen, nur weil es eine kürzere Alternative gibt. Es wird nur orthogonal mit den verfügbaren Adressierungsmodi kombiniert.quelle
Hier ist ein Beispiel.
Mit -O (optimieren) als Compileroption findet gcc die lea-Anweisung für die angegebene Codezeile.
quelle
Es scheint, dass viele Antworten bereits vollständig sind. Ich möchte noch einen Beispielcode hinzufügen, um zu zeigen, wie die Anweisungen lea und move unterschiedlich funktionieren, wenn sie dasselbe Ausdrucksformat haben.
Um es kurz zu machen, können sowohl lea-Anweisungen als auch mov-Anweisungen mit den Klammern verwendet werden, die den src-Operanden der Anweisungen einschließen. Wenn sie mit () eingeschlossen sind , wird der Ausdruck in () auf die gleiche Weise berechnet. Zwei Anweisungen interpretieren den berechneten Wert im src-Operanden jedoch unterschiedlich.
Unabhängig davon, ob der Ausdruck mit lea oder mov verwendet wird, wird der src-Wert wie folgt berechnet.
Wenn es jedoch mit der Anweisung mov verwendet wird, versucht es, auf den Wert zuzugreifen, auf den die durch den obigen Ausdruck erzeugte Adresse zeigt, und ihn im Ziel zu speichern.
Im Gegensatz dazu lädt der lea-Befehl, wenn er mit dem obigen Ausdruck ausgeführt wird, den generierten Wert so wie er ist in das Ziel.
Der folgende Code führt den Befehl lea und mov mit demselben Parameter aus. Um den Unterschied zu erkennen, habe ich jedoch einen Signalhandler auf Benutzerebene hinzugefügt, um den Segmentierungsfehler zu erfassen, der durch den Zugriff auf eine falsche Adresse infolge eines mov-Befehls verursacht wird.
Beispielcode
Ausführungsergebnis
quelle
=d
dem Compiler auch mitteilen, dass das Ergebnis in EDX vorliegt, und a speichernmov
. Sie haben auch eine Early-Clobber-Deklaration für die Ausgabe ausgelassen. Dies zeigt zwar, was Sie demonstrieren möchten, ist aber auch ein irreführendes Beispiel für Inline-Asm, das bei Verwendung in anderen Kontexten nicht funktioniert. Das ist eine schlechte Sache für eine Stapelüberlaufantwort.%%
auf alle diese Registernamen in Extended asm schreiben möchten , verwenden Sie Eingabeeinschränkungen. wieasm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));
. Wenn Sie die Compiler-Init-Register zulassen, müssen Sie auch keine Clobber deklarieren. Sie überkomplizieren die Dinge durch Xor-Zeroing, bevor mov-unmittelbar auch das gesamte Register überschreibt.mov 4(%ebx, %eax, 8), %edx
das ungültig ist? Wie auch immer, ja, dennmov
es wäre sinnvoll zu schreiben"a"(1ULL)
, um dem Compiler mitzuteilen, dass Sie einen 64-Bit-Wert haben, und daher muss er sicherstellen, dass er erweitert ist, um das gesamte Register zu füllen. In der Praxis wird es weiterhin verwendetmov $1, %eax
, da das Schreiben von EAX mit Null in RAX erweitert wird, es sei denn, Sie haben eine seltsame Situation mit umgebendem Code, in der der Compiler wusste, dass RAX =0xff00000001
oder so. Dennlea
, sind Sie noch mit 32-Bit - Operanden-Größe, so dass die alle Streu High - Bits in Eingangsregister haben keinen Einfluss auf die 32-Bit - Ergebnis.LEA: nur eine "arithmetische" Anweisung.
MOV überträgt Daten zwischen Operanden, aber lea berechnet nur
quelle
mov eax, offset GLOBALVAR
stattdessen. Sie können LEA verwenden, es ist jedoch etwas größer als der Codemov r32, imm32
und wird auf weniger Ports ausgeführt, da der Adressberechnungsprozess noch durchlaufen wird .lea reg, symbol
ist nur in 64-Bit für eine RIP-relative LEA nützlich, wenn Sie PIC und / oder Adressen außerhalb der niedrigen 32 Bit benötigen. In 32- oder 16-Bit-Code gibt es keinen Vorteil. LEA ist eine arithmetische Anweisung, die die Fähigkeit der CPU offenlegt, Adressierungsmodi zu decodieren / zu berechnen.imul eax, edx, 1
das nicht kalkuliert: Es kopiert nur edx nach eax. Tatsächlich werden Ihre Daten jedoch mit einer Latenz von 3 Zyklen durch den Multiplikator geleitet. Oder dasrorx eax, edx, 0
kopiert einfach (um Null drehen).Alle normalen "Berechnungs" -Anweisungen wie das Hinzufügen von Multiplikationen, Exklusiv- oder Setzen der Statusflags wie Null, Vorzeichen. Wenn Sie eine komplizierte Adresse verwenden, werden
AX xor:= mem[0x333 +BX + 8*CX]
die Flags gemäß der xor-Operation gesetzt.Jetzt möchten Sie die Adresse möglicherweise mehrmals verwenden. Das Laden einer solchen Adresse in ein Register soll niemals Statusflags setzen und zum Glück nicht. Der Ausdruck "effektive Adresse laden" macht den Programmierer darauf aufmerksam. Von dort kommt der seltsame Ausdruck.
Es ist klar, dass der Prozessor, sobald er in der Lage ist, die komplizierte Adresse zur Verarbeitung seines Inhalts zu verwenden, diese für andere Zwecke berechnen kann. In der Tat kann es verwendet werden, um eine Transformation
x <- 3*x+1
in einem Befehl durchzuführen . Dies ist eine allgemeine Regel bei der Baugruppenprogrammierung: Verwenden Sie die Anweisungen, jedoch wird Ihr Boot dadurch erschüttert. Es kommt nur darauf an, ob die in der Anweisung enthaltene Transformation für Sie nützlich ist.Endeffekt
und
haben den gleichen Effekt auf AX, jedoch nicht auf die Statusflags. (Dies ist die Ciasdis- Notation.)
quelle
call lbl
lbl: pop rax
"technisch" funktionieren, um den Wert zu ermittelnrip
, aber Sie werden die Branchenvorhersage sehr unglücklich machen. Verwenden Sie die Anweisungen, wie Sie möchten, aber wundern Sie sich nicht, wenn Sie etwas