Für mich scheint es nur ein funky MOV zu sein. Was ist ihr Zweck und wann sollte ich es verwenden?
Für mich scheint es nur ein funky MOV zu sein. Was ist ihr Zweck und wann sollte ich es verwenden?
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:
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 EBX
und variabel i
ist EAX
, und xcoord
und ycoord
sind jeweils 32 Bit (so ycoord
wird 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 y
in EDX
. Der Skalierungsfaktor von 8 liegt daran, dass jedes Point
8 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 MOV
kann der Compiler generieren
LEA ESI, [EBX + 8*EAX + 4]
Dadurch wird die Adresse geladen ESI
.
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 tut MOV
. Es liest nicht wirklich von der berechneten Adresse, sondern berechnet sie nur.
Aus dem "Zen der Versammlung" von Abrash:
LEA
, der einzige Befehl, der Speicheradressierungsberechnungen durchführt, den Speicher jedoch nicht adressiert.LEA
akzeptiert einen Standardspeicheradressierungsoperanden, speichert jedoch lediglich den berechneten Speicherversatz in dem angegebenen Register, das ein beliebiges Allzweckregister sein kann.Was gibt uns das? Zwei Dinge,
ADD
die 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 + 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 ]
und INC EAX
besteht darin, dass sich der letztere ändert EFLAGS
, der erstere jedoch nicht; Dies bewahrt den CMP
Zustand.
LEA EAX, [ EAX + EBX + 1234567 ]
Berechnet die Summe von EAX
, EBX
und 1234567
(das sind drei Operanden). LEA EAX, [ EBX + ECX ]
berechnet, EBX + ECX
ohne 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 ]
( N
kann 1,2,4,8 sein). Ein anderer Anwendungsfall ist in Schleifen praktisch: Der Unterschied zwischen LEA EAX, [ EAX + 1 ]
und INC EAX
besteht darin, dass sich der letztere ändert EFLAGS
, der erstere jedoch nicht; das bewahrt den CMP
Zustand
LEA
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 wie CF
und nicht ändert ZF
, während die Adresse durch arithmetische Befehle wie ADD
oder berechnet MUL
wird. Diese Funktion verringert die Abhängigkeit zwischen Anweisungen und bietet somit Raum für weitere Optimierungen durch den Compiler oder den Hardware-Scheduler.
lea
manchmal ist es für den Compiler (oder den menschlichen Codierer) nützlich, zu rechnen, ohne ein Flag-Ergebnis zu beeinträchtigen. Ist lea
aber 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? )
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).
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.
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
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 normalerweise mul
Anweisungen, die anspruchsvoller und langsamer sind.
lea eax,[eax*3]
würde das gleichbedeutend sein mit lea 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:
lea ebx, [ebx+eax*8]
bewegen ebx
Zeiger eax
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.
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 wird EAX
.
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 a MOV
auf das Addieren / Subtrahieren beschränkt ist.
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:
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 fnord
als 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, 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ßlich LEA
und anderer Speicherreferenzadressen) verfügt. Dies bedeutet, dass die arithmetische Operation in LEA
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 codiert MemoryAddress
. Dies unterscheidet sich von MOV REG, MemoryAddress
der Codierung der relativen virtuellen Adresse und erfordert das Verschieben / Patchen in modernen Betriebssystemen (wie ASLR ist eine gemeinsame Funktion). So LEA
kann verwendet werden, nicht PIC zu PIC zu konvertieren.
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 kann add
oder sub
oder die meisten anderen Grundrechenarten auf vier verschiedene ALUs, kann aber nur ausführen lea
auf einem (Komplex lea
) oder zwei (einfach lea
). Noch wichtiger ist, dass diese zwei lea
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.
[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üssen esi
den Wert von ecx
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 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.
[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
lea
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. @ Kaz
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 MOV
durch 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.
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:
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 LEA
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:
$ as
movl 0, %eax
$ objdump -d a.out | grep mov
0: 8b 04 25 00 00 00 00 mov 0x0,%eax
Es ist eine sehr ähnliche Codierung, sehen Sie? Nur das 8d
von LEA
hat sich geändert zu 8b
.
Natürlich ist diese LEA
Codierung länger als das Verschieben einer unmittelbaren Null in EAX
:
$ as
movl $0, %eax
$ objdump -d a.out | grep mov
0: b8 00 00 00 00 mov $0x0,%eax
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.
Hier ist ein Beispiel.
// 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
=d
dem 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.
%%
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.
mov 4(%ebx, %eax, 8), %edx
das ungültig ist? Wie auch immer, ja, denn mov
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 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 = 0xff00000001
oder 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.
LEA: nur eine "arithmetische" Anweisung.
MOV überträgt Daten zwischen Operanden, aber lea berechnet nur
mov eax, offset GLOBALVAR
stattdessen. Sie können LEA verwenden, es ist jedoch etwas größer als der Code mov 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 das rorx 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
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.)
call lbl
lbl: 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