Für mich ist es einfach nur ein komisches MOV. Was ist sein Zweck und wann sollte ich es verwenden?
Wäre es nicht sauberer gewesen, die mov
Anleitung und lassen Sie die Klammern weg? MOV EDX, EBX + 8*EAX + 4
Für mich ist es einfach nur ein komisches MOV. Was ist sein Zweck und wann sollte ich es verwenden?
Wie bereits von anderen erwähnt, wird LEA (load effective address) oft als "Trick" für bestimmte Berechnungen verwendet, aber das ist nicht ihr Hauptzweck. Der x86-Befehlssatz wurde entwickelt, um Hochsprachen wie Pascal und C zu unterstützen, in denen Arrays - insbesondere Arrays von Ints oder kleinen Structs - üblich sind. Betrachten wir zum Beispiel eine Struktur, die (x, y)-Koordinaten darstellt:
struct Point
{
int xcoord;
int ycoord;
};
Stellen Sie sich nun eine Aussage wie diese vor:
int y = points[i].ycoord;
donde points[]
ist ein Array aus Point
. Angenommen, die Basis des Arrays befindet sich bereits in EBX
und variabel i
ist in EAX
y xcoord
y ycoord
sind jeweils 32 Bit (also ycoord
an einem Offset von 4 Bytes in der Struktur steht), kann diese Anweisung kompiliert werden zu:
MOV EDX, [EBX + 8*EAX + 4] ; right side is "effective address"
was zu einer y
en EDX
. Der Skalierungsfaktor von 8 ist darauf zurückzuführen, dass jede Point
hat eine Größe von 8 Byte. Betrachten wir nun denselben Ausdruck mit dem "Adresse von"-Operator &:
int *p = &points[i].ycoord;
In diesem Fall wollen Sie nicht, dass der Wert von ycoord
sondern seine Adresse. Das ist der Ort LEA
(ladungswirksame Adresse) eintritt. Anstelle einer MOV
kann der Compiler Folgendes erzeugen
LEA ESI, [EBX + 8*EAX + 4]
die die Adresse in ESI
.
Wäre es nicht sauberer gewesen, die mov
Anleitung und lassen Sie die Klammern weg? MOV EDX, EBX + 8*EAX + 4
@imacake Indem Sie LEA durch ein spezielles MOV ersetzen, halten Sie die Syntax sauber: []-Klammern sind immer das Äquivalent zur Dereferenzierung eines Zeigers in C. Ohne Klammern haben Sie es immer mit dem Zeiger selbst zu tun.
Desde el "Zen der Montage" von Abrash:
LEA
ist die einzige Anweisung, die Berechnungen zur Speicheradressierung durchführt, aber den Speicher nicht tatsächlich adressiert.LEA
akzeptiert einen Standard-Speicheradressierungsoperanden, tut aber nichts weiter, als den berechneten Speicher-Offset in dem angegebenen Register zu speichern, das ein beliebiges Allzweckregister sein kann.Was bedeutet das für uns? Zwei Dinge, die
ADD
nicht zur Verfügung stellt:
- die Fähigkeit, Additionen mit zwei oder drei Operanden durchzuführen, und
- die Möglichkeit, das Ergebnis zu speichern in jede Register; nicht nur einen der Quelloperanden.
Und LEA
ändert die Flaggen nicht.
Beispiele
LEA EAX, [ EAX + EBX + 1234567 ]
errechnet EAX + EBX + 1234567
(das sind drei Operanden)LEA EAX, [ EBX + ECX ]
errechnet EBX + ECX
ohne diese mit dem Ergebnis zu überschreiben.LEA EAX, [ EBX + N * EBX ]
(N kann 1,2,4,8 sein).Ein anderer Anwendungsfall ist praktisch in Schleifen: Der Unterschied zwischen LEA EAX, [ EAX + 1 ]
y INC EAX
ist, dass die letzteren Änderungen EFLAGS
aber die erste nicht; dadurch bleibt die CMP
Zustand.
@AbidRahmanK einige Beispiele: LEA EAX, [ EAX + EBX + 1234567 ]
berechnet die Summe von EAX
, EBX
y 1234567
(das sind drei Operanden). LEA EAX, [ EBX + ECX ]
errechnet EBX + ECX
ohne entweder mit dem Ergebnis überschreiben. Die dritte Sache LEA
verwendet wird (von Frank nicht aufgeführt), ist Multiplikation mit Konstante (durch zwei, drei, fünf oder neun), wenn Sie es wie folgt verwenden LEA EAX, [ EBX + N * EBX ]
( N
kann 1,2,4,8 sein). Ein anderer Anwendungsfall ist praktisch in Schleifen: der Unterschied zwischen LEA EAX, [ EAX + 1 ]
y INC EAX
ist, dass die letzteren Änderungen EFLAGS
aber die erste nicht; dadurch bleibt die CMP
Staat
@ripDaddy69 ja, in gewisser Weise - wenn Sie mit "laden" "die Adressberechnung/Zeigerarithmetik durchführen" meinen. Das tut es nicht auf den Speicher zugreifen (d.h. keine "Dereferenzierung" des Zeigers, wie es in der C-Programmierung heißen würde).
Ein weiteres wichtiges Merkmal der LEA
Anweisung ist, dass sie die Konditionscodes nicht ändert, wie zum Beispiel CF
y ZF
bei der Berechnung der Adresse durch arithmetische Befehle wie ADD
o MUL
tut. Diese Funktion verringert die Abhängigkeit zwischen den Anweisungen und schafft somit Raum für weitere Optimierungen durch den Compiler oder den Hardware-Scheduler.
Ja, lea
ist manchmal nützlich für den Compiler (oder den menschlichen Programmierer), um Berechnungen durchzuführen, ohne ein Flag-Ergebnis zu verfälschen. Aber lea
nicht schneller ist als add
. Die meisten x86-Befehle schreiben Flags. Leistungsstarke x86-Implementierungen müssen EFLAGS umbenennen oder anderweitig vermeiden die Gefahr des Schreibens nach dem Schreiben Damit normaler Code schnell läuft, sind Anweisungen, die das Schreiben von Flags vermeiden, deshalb nicht besser. ( teilweise Flaggenmaterial kann Probleme verursachen, siehe INC-Anweisung gegenüber ADD 1: Ist das wichtig? )
@PeterCordes : Ich spreche das hier nur ungern an, aber - bin ich der einzige, der dieses neue [x86-lea]-Tag für überflüssig und unnötig hält?
@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/Integer sind, daher gibt es viele Fragen dazu mit einer großen Anzahl von Stimmen. Aber ein Tag dafür zu haben, impliziert, dass es Platz für eine unbegrenzte Anzahl zukünftiger Fragen gibt, während es in Wirklichkeit nur 2 oder 3 Fragen gibt, die nicht nur Duplikate sind. (Was ist das? Wie verwendet man es zum Multiplizieren von ganzen Zahlen? und wie läuft es intern auf AGUs vs. ALUs und mit welcher Latenz / welchem Durchsatz. Und vielleicht ist es der "beabsichtigte" Zweck)
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 der Name extrem dumm ist für eine Shift+Add Operation. Der Grund dafür wurde bereits in den am besten bewerteten Antworten erklärt (d.h. er wurde entwickelt, um High-Level-Speicherreferenzen direkt abzubilden).
@BenVoigt Das habe ich immer gesagt, weil ich ein alter Knacker bin :-) Traditionell haben die x86-CPUs die Adressierungseinheiten dafür verwendet, das stimmt. Aber die "Trennung" ist heutzutage sehr unscharf geworden. Einige CPUs haben nicht mehr engagiert AGUs überhaupt, andere haben sich dafür entschieden, nicht auszuführen LEA
auf den AGUs, sondern auf den gewöhnlichen Integer-ALUs. Heutzutage muss man die CPU-Spezifikationen sehr genau lesen, um herauszufinden, "wo etwas läuft" ...
@FrankH.: Out-of-Order-CPUs lassen LEA typischerweise auf ALUs laufen, während einige In-Order-CPUs (wie Atom) es manchmal auf AGUs laufen lassen (weil sie nicht damit beschäftigt sein können, einen Speicherzugriff zu verarbeiten).
+1 für den Trick. Aber ich möchte eine Frage stellen (kann dumm sein), warum nicht direkt mit drei multiplizieren wie diese LEA EAX, [EAX*3]
?
@AbidRahmanK obwohl die Intel-Asm-Syntax es wie eine Multiplikation aussehen lässt, kann der Lea-Befehl nur Schiebeoperationen kodieren. Der Opcode hat 2 Bits, um die Verschiebung zu beschreiben, daher kann man nur mit 1, 2, 4 oder 8 multiplizieren.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.
8 Stimmen
Siehe auch LEA auf Werte anwenden, die keine Adressen/Zeiger sind? : LEA ist nur ein Shift-and-Add-Befehl. Er wurde wahrscheinlich zum 8086 hinzugefügt, weil die Hardware bereits vorhanden ist, um Adressierungsmodi zu dekodieren und zu berechnen, nicht weil er nur für die Verwendung mit Adressen "gedacht" ist. Denken Sie daran, dass Zeiger in Assembler nur ganze Zahlen sind.