wake-up-neo.com

Was ist der Zweck der LEA-Anweisung?

Für mich wirkt es einfach wie ein flippiger MOV. Was ist der Zweck und wann sollte ich es verwenden?

575
user200557

Wie andere darauf hingewiesen haben, wird LEA (load effect address) oft als "Trick" für bestimmte Berechnungen verwendet, aber das ist nicht der Hauptzweck. Der x86-Befehlssatz wurde für die Unterstützung von Hochsprachen wie Pascal und C entwickelt, wobei Arrays - insbesondere Arrays von Ints oder kleinen Strukturen - üblich sind. Betrachten Sie zum Beispiel eine Struktur, die (x, y) -Koordinaten darstellt:

struct Point
{
     int xcoord;
     int ycoord;
};

Nun stellen Sie sich eine Aussage vor wie:

int y = points[i].ycoord;

dabei ist points[] ein Array von Point. Angenommen, die Basis des Arrays befindet sich bereits in EBX, und die Variable i befindet sich in EAX und xcoord und ycoord sind jeweils 32 Bits (dh ycoord ist in der Struktur um 4 Bytes versetzt).

MOV EDX, [EBX + 8*EAX + 4]    ; right side is "effective address"

welches y in EDX landet. Der Skalierungsfaktor von 8 ist, weil jeder Point 8 Byte groß ist. Betrachten Sie nun den gleichen Ausdruck wie beim Operator "Adresse von" &:

int *p = &points[i].ycoord;

In diesem Fall möchten Sie nicht den Wert von ycoord, sondern dessen Adresse. Hier kommt LEA (effektive Adresse laden) ins Spiel. Anstelle von MOV kann der Compiler generieren

LEA ESI, [EBX + 8*EAX + 4]

dadurch wird die Adresse in ESI geladen.

703
I. J. Kennedy

Aus dem "Zen of Assembly" von Abrash:

LEA, die einzige Anweisung, die Speicheradressierungsberechnungen durchführt, jedoch nicht wirklich den Speicher adressiert. LEA akzeptiert einen Standardspeicheradressierungsoperanden, speichert jedoch nur den berechneten Speicheroffset in dem angegebenen Register, das ein Universalregister sein kann.

Was gibt uns das? Zwei Dinge, die ADD nicht bietet: 

  1. die Möglichkeit, eine Addition mit zwei oder drei Operanden durchzuführen, und 
  2. die Fähigkeit, das Ergebnis in any register zu speichern; nicht nur einer der Quelloperanden.

Und LEA ändert die Flags nicht.

Beispiele

  • LEA EAX, [ EAX + EBX + 1234567 ] berechnet EAX + EBX + 1234567 (das sind drei Operanden)
  • LEA EAX, [ EBX + ECX ] berechnet EBX + ECX, ohne sie mit dem Ergebnis zu überschreiben.
  • multiplikation mit Konstanten (mit zwei, drei, fünf oder neun), wenn Sie sie wie LEA EAX, [ EBX + N * EBX ] verwenden (N kann 1,2,4,8 sein).

Andere Anwendungen sind in Schleifen nützlich: Der Unterschied zwischen LEA EAX, [ EAX + 1 ] und INC EAX besteht darin, dass der letztere EFLAGS ändert, der erste jedoch nicht; Dadurch bleibt der CMP-Zustand erhalten.

499
Frank Krueger

Ein weiteres wichtiges Merkmal der Anweisung LEA ist, dass die Bedingungscodes wie CF und ZF nicht geändert werden, während die Adresse durch arithmetische Anweisungen wie ADD oder MUL berechnet wird. Diese Funktion verringert die Abhängigkeit zwischen Anweisungen und bietet somit Raum für weitere Optimierungen durch den Compiler oder den Hardware-Scheduler.

93
Angus Lee

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 extrem dumm für eine Shift + Add-Operation ist. Der Grund dafür wurde bereits in den Antworten mit den höchsten Bewertungen erläutert (d. H. Es wurde entworfen, um Speicherreferenzen auf hoher Ebene direkt abzubilden).

77
hdante

Vielleicht nur eine weitere Sache zum LEA-Befehl ..__ 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
69
GJ.

lea ist eine Abkürzung für "effektive Adresse laden". Es lädt die Adresse der Ortsreferenz durch den Quelloperanden in den Zieloperanden. Sie können es beispielsweise verwenden, um:

lea ebx, [ebx+eax*8]

um ebx-Zeiger eax-Elemente mit einem einzigen Befehl weiter zu verschieben (in einem 64-Bit/Element-Array) Grundsätzlich profitieren Sie von komplexen Adressierungsmodi, die von der x86-Architektur unterstützt werden, um Zeiger effizient zu bearbeiten.

53
Mehrdad Afshari

Der größte Grund, aus dem Sie LEA für ein MOV verwenden, ist, wenn Sie in den Registern, die Sie zur Berechnung der Adresse verwenden, eine Arithmetik durchführen müssen. Sie können effektiv die Zeigerarithmetik für mehrere der Register in Kombination effektiv für "frei" ausführen.

Was wirklich verwirrend ist, ist, dass Sie in der Regel eine LEA wie eine MOV schreiben, der Speicher wird jedoch nicht dereferenziert. Mit anderen Worten:

MOV EAX, [ESP+4]

Dadurch wird der Inhalt von ESP+4 in EAX verschoben.

LEA EAX, [EBX*8]

Dadurch wird die effektive Adresse EBX * 8 in EAX verschoben, nicht das, was sich an diesem Ort befindet. Wie Sie sehen, ist es auch möglich, mit Faktoren von zwei zu multiplizieren (Skalierung), während ein MOV auf das Addieren/Subtrahieren beschränkt ist.

22
David Hoelzer

Der 8086 hat eine große Familie von Befehlen, die einen Registeroperanden und eine effektive Adresse akzeptieren, einige Berechnungen ausfü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, dass sich eine der Anweisungen in dieser Familie wie oben beschrieben verhält, es sei denn, die eigentliche Speicheroperation wird übersprungen. Dies sind die Anweisungen:

mov ax,[bx+si+5]
lea ax,[bx+si+5]

wurden intern nahezu identisch umgesetzt. Der Unterschied ist ein übersprungener Schritt. Beide Anweisungen funktionieren in etwa wie:

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 die Einführung dieses Unterrichts für wertvoll hielt, aber die Tatsache, dass er billig implementiert werden konnte, wäre ein großer Faktor gewesen. Ein weiterer Faktor wäre die Tatsache gewesen, dass Intels Assembler es erlaubte, Symbole relativ zum BP-Register zu definieren. 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 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 Weltversatzes dazu führen würde, dass der Inhalt des Ortes [BP + 8] anstelle des Werts 8 zu DI addiert wird. Hoppla.

18
supercat

Wie in den vorhandenen Antworten erwähnt, hat LEA die Vorteile, eine Speicheradressierungsarithmetik durchzuführen, ohne auf den Speicher zuzugreifen, und das arithmetische Ergebnis in einem anderen Register anstelle der einfachen Form der Additionsanweisung zu speichern. Der eigentliche zugrunde liegende Leistungsvorteil besteht darin, dass der moderne Prozessor eine separate LEA-ALU-Einheit und einen separaten Port für die effektive Adressenerzeugung (einschließlich LEA und anderer Speicherreferenzadressen) aufweist. Dies bedeutet, dass die arithmetische Operation in LEA und andere normale arithmetische Operationen in ALU parallel ausgeführt werden können in einem Kern.

In diesem Artikel der Haswell-Architektur finden Sie weitere Informationen 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 zum Verweis auf MemoryAddress codiert. Dies unterscheidet sich von MOV REG, MemoryAddress, das die relative virtuelle Adresse codiert, und erfordert das Umstellen/Patchen in modernen Betriebssystemen (wie ASLR ist eine übliche Funktion). Daher kann LEA verwendet werden, um einen solchen Nicht-PIC in einen PIC zu konvertieren.

10
Thomson

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, statt bei jeder Verwendung die effektive Adresse zu berechnen.

7
red-E

Die LEA-Anweisung (Load Effective Address) ist eine Möglichkeit, die Adresse zu ermitteln, die aus einem der Speicheradressierungsmodi des Intel-Prozessors stammt.

Das heißt, wenn wir einen Datenzug wie folgt haben:

MOV EAX, <MEM-OPERAND>

es verschiebt den Inhalt des angegebenen Speicherplatzes in das Zielregister.

Wenn wir die Variable MOV durch die Variable LEA ersetzen, wird die Adresse des Speicherplatzes auf dieselbe Weise durch den Adressierungsausdruck <MEM-OPERAND> berechnet. Anstelle des Inhalts des Speicherorts bekommen wir den Ort selbst in das Ziel.

LEA ist keine bestimmte arithmetische Anweisung. es ist eine Möglichkeit, die effektive Adresse abzufangen, die aus einem der Speicheradressierungsmodi des Prozessors stammt. 

Beispielsweise können wir LEA nur für 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 auf der Linux Prompt 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 wird kein skalierter Wert und kein Offset hinzugefügt. Null wird in EAX verschoben. Das könnte man auch mit MOV mit einem sofortigen Operanden machen.

Dies ist der Grund, warum Leute, die der Meinung sind, dass die Klammern in LEA überflüssig sind, sich irrtümlich irren; Die Klammern sind nicht LEA, sondern Teil des Adressierungsmodus.

LEA ist real auf Hardwareebene. Der erzeugte Befehl codiert den tatsächlichen Adressierungsmodus, und der Prozessor führt ihn bis zur Berechnung der Adresse aus. Dann verschiebt er diese Adresse zum Ziel, anstatt eine Speicherreferenz zu erzeugen. (Da die Adressberechnung eines Adressierungsmodus in einer anderen Anweisung keine Auswirkung auf CPU-Flags hat, hat LEA keine Auswirkung auf CPU-Flags.)

Im Gegensatz zum Laden des Wertes 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 Kodierung. Nur der 8d von LEA hat sich in 8b geändert.

Natürlich ist diese LEA-Codierung länger als das Verschieben einer direkten 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 für LEA, diese Möglichkeit auszuschließen, nur weil es eine kürzere Alternative gibt. es wird nur orthogonal mit den verfügbaren Adressierungsmodi kombiniert.

6
Kaz

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. 

6
user3634373

LEA: Nur eine "arithmetische" Anweisung ..

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

4
the accountant

Es scheint, dass viele Antworten bereits vollständig sind. Ich möchte noch einen weiteren Beispielcode hinzufügen, um zu zeigen, wie Lea- und Move-Anweisungen anders funktionieren, wenn sie dasselbe Ausdrucksformat haben. 

Um eine lange Geschichte kurz zu machen, können Lea-Anweisungen und Mov-Anweisungen mit den Klammern verwendet werden, die den Operanden src der Anweisungen einschließen. Wenn sie in () eingeschlossen sind, wird der Ausdruck in () auf dieselbe Weise berechnet. Zwei Anweisungen interpretieren den berechneten Wert im Operanden src 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 es jedoch mit der mov-Anweisung verwendet wird, versucht es, auf den Wert zuzugreifen, auf den die durch den obigen Ausdruck generierte Adresse zeigt, und es im Ziel zu speichern. 

Im Gegensatz dazu lädt der Lea-Befehl, wenn er mit dem obigen Ausdruck ausgeführt wird, den generierten Wert in das Ziel.

Der folgende Code führt die Lea-Anweisung und die Mov-Anweisung mit demselben Parameter aus. Um den Unterschied zu erkennen, habe ich einen Signalhandler auf Benutzerebene hinzugefügt, um den Segmentierungsfehler aufzufangen, der durch den Zugriff auf eine falsche Adresse als Ergebnis eines mov-Befehls verursacht wurde. 

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
0
Jaehyuk Lee