867 Stimmen

Was ist der Zweck der LEA-Anweisung?

Für mich ist es einfach nur ein komisches MOV. Was ist sein Zweck und wann sollte ich es verwenden?

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.

10voto

ruach Punkte 1269

Es scheint, dass viele Antworten bereits vollständig sind. Ich möchte noch ein weiteres Codebeispiel hinzufügen, um zu zeigen, wie die Anweisungen lea und move unterschiedlich funktionieren, obwohl sie das gleiche Ausdrucksformat haben.

Um es kurz zu machen: Die Befehle lea und mov können beide mit den Klammern verwendet werden, die den src-Operanden der Befehle einschließen. Wenn sie mit der Klammer () ist der Ausdruck in der () wird auf die gleiche Weise berechnet; zwei Anweisungen interpretieren den berechneten Wert im src-Operanden jedoch auf unterschiedliche Weise.

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 er jedoch mit dem mov-Befehl verwendet wird, versucht er, auf den Wert zuzugreifen, auf den die durch den obigen Ausdruck erzeugte Adresse zeigt, und ihn im Ziel zu speichern.

Wird dagegen die Anweisung lea mit dem obigen Ausdruck ausgeführt, lädt sie den erzeugten Wert so, wie er ist, in das Ziel.

Der folgende Code führt den Befehl lea und den Befehl mov mit demselben Parameter aus. Um jedoch den Unterschied festzustellen, habe ich einen Signalhandler auf Benutzerebene hinzugefügt, um den Segmentierungsfehler abzufangen, der durch den Zugriff auf eine falsche Adresse als Ergebnis der mov-Anweisung verursacht wird.

Beispiel-Code

#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
          );
}

Ergebnis der Ausführung

Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed

1 Stimmen

Die Aufteilung der Inline-Asm in separate Anweisungen ist unsicher, und die Klammerlisten sind unvollständig. Der Basic-Asm-Block sagt dem Compiler, dass er keine Clobbers hat, aber er ändert tatsächlich mehrere Register. Außerdem können Sie =d um dem Compiler mitzuteilen, dass das Ergebnis im EDX-Format vorliegt, wodurch eine mov . Sie haben auch eine Frühverklumpungserklärung in der Ausgabe ausgelassen. Dies demonstriert zwar, was Sie zu demonstrieren versuchen, ist aber auch ein irreführendes schlechtes Beispiel für Inline-Asm, das bei Verwendung in anderen Kontexten kaputt geht. Das ist eine schlechte Sache für eine Stack-Overflow-Antwort.

0 Stimmen

Wenn Sie nicht schreiben wollen %% auf all diese Registernamen in Extended asm, und verwenden Sie dann Eingabebeschränkungen. wie asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2)); . Wenn Sie den Compiler Register initiieren lassen, müssen Sie auch keine Clobber deklarieren. Sie verkomplizieren die Dinge, indem Sie xor-zero machen, bevor mov-immediate das gesamte Register überschreibt.

0 Stimmen

@

9voto

red-E Punkte 1194

Der LEA-Befehl kann verwendet werden, um zeitaufwändige Berechnungen der effektiven Adressen durch die CPU zu vermeiden. Wenn eine Adresse wiederholt verwendet wird, ist es effektiver, sie in einem Register zu speichern, anstatt die effektive Adresse jedes Mal zu berechnen, wenn sie verwendet wird.

0 Stimmen

Nicht unbedingt auf modernen x86-Systemen. Die meisten der Adressierungsmodi haben die gleichen Kosten, mit einigen Abstrichen. Also [esi] ist selten billiger als beispielsweise [esi + 4200] und ist nur selten billiger als [esi + ecx*8 + 4200] .

0 Stimmen

@BeeOnRope [esi] nicht billiger ist als [esi + ecx*8 + 4200] . Aber warum sollte man vergleichen? Sie sind nicht gleichwertig. Wenn Sie wollen, dass der erste denselben Speicherplatz bezeichnet wie der zweite, benötigen Sie zusätzliche Anweisungen: Sie müssen zu esi den Wert von ecx multipliziert mit 8. Oh oh, die Multiplikation wird Ihre CPU-Flags verschlingen! Dann müssen Sie die 4200 hinzufügen. Diese zusätzlichen Anweisungen erhöhen die Codegröße (nehmen Platz im Befehls-Cache ein, benötigen Zyklen zum Abrufen).

2 Stimmen

@Kaz - Ich glaube, Sie haben meinen Punkt nicht verstanden (oder ich habe den Punkt des OPs nicht verstanden). Meines Erachtens will der OP sagen, dass man, wenn man etwas verwendet wie [esi + 4200] wiederholt in einer Befehlssequenz zu verwenden, ist es besser, die effektive Adresse zunächst in ein Register zu laden und dieses zu verwenden. Zum Beispiel, anstatt zu schreiben add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200] sollten Sie vorziehen 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.

7voto

Maxim Masiutin Punkte 2895

LEA vs. MOV (Antwort auf die ursprüngliche Frage)

LEA ist kein funky MOV . Wenn Sie MOV errechnet er die Adresse und greift auf den Speicher zu. LEA berechnet nur die Adresse, greift aber nicht auf den Speicher zu. Das ist der Unterschied.

In 8086 und später, LEA setzt einfach eine Summe von bis zu zwei Quellregistern und einen unmittelbaren Wert in ein Zielregister. Zum Beispiel, lea bp, [bx+si+3] setzt in das bp-Register die Summe aus bx plus si plus 3. Sie können diese Berechnung nicht durchführen, um das Ergebnis in einem Register zu speichern mit MOV .

Der 80386-Prozessor führte eine Reihe von Skalierungsmodi ein, bei denen der Indexregisterwert mit einem gültigen Skalierungsfaktor multipliziert werden kann, um die Verschiebung zu erhalten. Die gültigen Skalierungsfaktoren sind 1, 2, 4 und 8. Daher können Sie Befehle verwenden wie lea ebp, [ebx+esi*8+3] .

LDS & LES (optionale weiterführende Literatur)

Im Gegensatz zu LEA gibt es Anweisungen LDS y LES , die im Gegenteil Werte aus dem Speicher in das Registerpaar laden: ein Segmentregister ( DS o ES ) und ein allgemeines Register. Es gibt auch Versionen für die anderen Register: LFS , LGS y LSS para FS , GS y SS Segment-Register (eingeführt in 80386).

Diese Befehle laden also einen "Fernzeiger" - einen Zeiger, der aus einem 16-Bit-Segmentselektor und einem 16-Bit-Offset (oder einem 32-Bit-Offset, je nach Modus) besteht, so dass die Gesamtgröße des Fernzeigers im 16-Bit-Modus 32 Bit und im 32-Bit-Modus 48 Bit beträgt.

Dies sind praktische Anweisungen für den 16-Bit-Modus, sei es der 16-Bit-Real-Modus oder der 16-Bit-Protected-Modus.

Im 32-Bit-Modus sind diese Befehle nicht erforderlich, da die Betriebssysteme alle Segmentbasen auf Null setzen (flaches Speichermodell), so dass es nicht erforderlich ist, Segmentregister zu laden. Wir verwenden einfach 32-Bit-Zeiger, nicht 48.

Im 64-Bit-Modus sind diese Befehle nicht implementiert. Ihre Opcodes führen zu einer Zugriffsverletzungsunterbrechung (Ausnahme). Seit Intels Implementierung von VEX - "vector extensions - (AVX), hat Intel die Opcodes von LDS y LES und begann, sie für VEX-Präfixe zu verwenden. Wie Peter Cordes feststellte, ist das der Grund, warum nur x/ymm0..7 im 32-Bit-Modus zugänglich sind (Zitat): "Die VEX-Präfixe wurden sorgfältig entworfen, um sich nur mit ungültigen Kodierungen von LDS und LES im 32-Bit-Modus zu überschneiden, wo R X B alle 1 sind. Deshalb sind einige der Bits in den VEX-Präfixen invertiert".

1 Stimmen

[bx*2+si+3] ist kein gültiger 16-Bit-Adressierungsmodus. 16-Bit lässt keine Skalierungsfaktoren zu. lea bp, [ebx*2 + esi + 3] wäre jedoch im 16-Bit-Modus auf einem 386er oder später legal. (Normalerweise schreibt man zuerst die Basis und dann den skalierten Index, aber Assembler würden das akzeptieren).

0 Stimmen

Daher sind 256-Bit-AVX (YMM)-Register nur im 64-Bit-Modus verfügbar. - Nein, aber das ist der Grund, warum nur x/ymm0..7 sind im 32-Bit-Modus zugänglich. Die VEX-Präfixe wurden sorgfältig entworfen, damit sie sich nur mit ungültig Kodierungen von LDS und LES im 32-Bit-Modus, wobei R X B alle 1 sind. Deshalb sind einige der Bits in den VEX-Präfixen invertiert. ( de.wikipedia.org/wiki/VEX_präfix )

2 Stimmen

Dass LES/LDS/... im 32-Bit-Modus nutzlos sind, ist nur eine Konvention; Betriebssysteme entscheiden sich für die Verwendung eines flachen Speichermodells. Die Hardware unterstützt im 32-Bit-Modus Segmentbasen ungleich Null, im Gegensatz zum 64-Bit-Modus. In der Praxis gilt das also für normale Betriebssysteme, aber nicht unbedingt für x86 im Allgemeinen. Also vielleicht "da Betriebssysteme alle Segmentregister gleich setzen" oder so ähnlich, um anzudeuten, dass es eine Wahl und keine Voraussetzung ist, dass der 32-Bit-Modus auf diese Weise funktioniert, aber immer noch ohne eine Menge Worte darauf zu verwenden und von Ihrem Punkt abzulenken.

7voto

user3634373 Punkte 71

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 (optimize) als Compileroption findet gcc die Lea-Anweisung für die angegebene Codezeile.

5voto

the accountant Punkte 496

LEA : nur eine "Rechenanweisung"

MOV überträgt Daten zwischen Operanden, aber lea rechnet nur

0 Stimmen

LEA verschiebt offensichtlich Daten; es hat einen Zieloperanden. LEA rechnet nicht immer; es rechnet, wenn die im Quelloperanden ausgedrückte effektive Adresse rechnet. LEA EAX, GLOBALVAR rechnet nicht; es verschiebt nur die Adresse von GLOBALVAR nach EAX.

0 Stimmen

@Kaz danke für Ihr Feedback. Meine Quelle war "LEA (load effective address) ist im Wesentlichen eine arithmetische Anweisung - sie führt keinen tatsächlichen Speicherzugriff durch, sondern wird üblicherweise zur Berechnung von Adressen verwendet (obwohl man damit auch Allzweck-Ganzzahlen berechnen kann)." form Eldad-Eilam-Buch Seite 149

0 Stimmen

@Kaz: Deshalb ist LEA überflüssig, wenn die Adresse bereits eine Link-Zeit-Konstante ist; verwenden Sie mov eax, offset GLOBALVAR stattdessen. Sie puede LEA verwenden, aber der Code ist etwas größer als mov r32, imm32 und läuft auf weniger Ports, weil sie immer noch den Prozess der Adressberechnung durchläuft . 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 unteren 32 Bits benötigen. In 32- oder 16-Bit-Code gibt es keinen Vorteil. LEA ist ein arithmetischer Befehl, der die Fähigkeit der CPU zur Dekodierung/Berechnung von Adressierungsmodi offen legt.

CodeJaeger.com

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.

Powered by:

X