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 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
LEA ESI, [EBX + 8*EAX + 4]
Dadurch wird die Adresse geladen ESI.
movAnweisung zu erweitern und die Klammern wegzulassen? MOV EDX, EBX + 8*EAX + 4
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.
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.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.
LEA EAX, [ EAX + EBX + 1234567 ]Berechnet die Summe von EAX, EBXund 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 ]( 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
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)
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.
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? )
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).
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" ...
LEAgibt 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]?
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.
lea eax,[eax*3]würde das gleichbedeutend sein mit lea eax,[eax+eax*2].
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.
LEAtun.
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.
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, 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.
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.
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 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).
[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
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
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:
$ 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 8dvon LEAhat sich geändert zu 8b.
Natürlich ist diese LEACodierung 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
=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.
%%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), %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.
LEA: nur eine "arithmetische" Anweisung.
MOV überträgt Daten zwischen Operanden, aber lea berechnet nur
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.
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).
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.)
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