Qual è la differenza tra MOV e LEA?


139

Vorrei sapere qual è la differenza tra queste istruzioni:

MOV AX, [TABLE-ADDR]

e

LEA AX, [TABLE-ADDR]


8
grazie nick. Prima di tutto, non avrei trovato una risposta a questa domanda osservando quel link. Qui stavo cercando informazioni specifiche, la discussione nel link che hai fornito è di natura più generica.
naveen,

3
Ho votato a favore di Nick's dup anni fa ma vtc solo ora. Riflettendomi, ero troppo frettoloso e ora con naveen che a) l'altra domanda non risponde "qual è la differenza" eb) questa è una domanda utile. Chiedo scusa a Naveen per il mio errore - se solo potessi annullare vtc ...
Ruben Bartelink,


Correlati: utilizzare LEA su valori che non sono indirizzi / puntatori? parla di altri usi del LEA, per la matematica arbitraria.
Peter Cordes,

Risposte:


169
  • LEA significa Carica indirizzo effettivo
  • MOV significa Carica valore

In breve, LEAcarica un puntatore all'elemento che stai indirizzando mentre MOV carica il valore effettivo a quell'indirizzo.

Lo scopo di LEAè quello di consentire a uno di eseguire un calcolo di indirizzo non banale e memorizzare il risultato [per un uso successivo]

LEA ax, [BP+SI+5] ; Compute address of value

MOV ax, [BP+SI+5] ; Load value at that address

Dove sono coinvolte solo costanti, MOV(attraverso i calcoli delle costanti dell'assemblatore) a volte può sembrare sovrapporsi con i casi più semplici di utilizzo di LEA. È utile se si dispone di un calcolo in più parti con più indirizzi di base, ecc.


6
+1 grazie per la chiara spiegazione, mi ha aiutato a rispondere a un'altra domanda.
legends2k,

Mi confonde che lea abbia "caricato" nel nome e la gente dice che "carica" ​​un indirizzo calcolato in un registro, perché tutti gli input per calcolare la posizione della memoria sono valori immediati o registri. AFAICT lea esegue solo un calcolo, non carica nulla, dove caricare significa toccare la memoria?
Joseph Garvin,

2
@josephGarvin IIRC il termine fetch verrebbe applicato a quell'aspetto; Il caricamento è proprio come si sostituisce il valore in un registro con qualcosa da zero. ad es. LAHFè: carica FLAG nel registro AH . Nel CIL del CLR (che è una macchina astratta basata su stack di livello superiore, il termine carico si riferisce all'inserimento di un valore nello stack nozionale ed è normalmente l..., e l' sequivalente ... fa l'inverso). Queste note: cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html ) suggeriscono che ci sono davvero architetture in cui si applica la tua distinzione.
Ruben Bartelink,


45

Nella sintassi NASM:

mov eax, var       == lea eax, [var]   ; i.e. mov r32, imm32
lea eax, [var+16]  == mov eax, var+16
lea eax, [eax*4]   == shl eax, 2        ; but without setting flags

Nella sintassi MASM, usare OFFSET varper ottenere un mov-immediato invece di un carico.


3
solo nella sintassi NASM. Nella sintassi MASM, mov eax, varè un carico, lo stesso di mov eax, [var], e devi usare mov eax, OFFSET varper usare un'etichetta come costante immediata.
Peter Cordes,

1
Chiaro, semplice e dimostra ciò che stavo cercando di confermare. Grazie.
JayArby,

1
Si noti che in tutti questi esempi leaè la scelta peggiore, tranne nella modalità a 64 bit per l'indirizzamento relativo al RIP. mov r32, imm32funziona su più porte. lea eax, [edx*4]è un copia-e-sposta che non può essere fatto in un'altra istruzione altrimenti, ma nello stesso registro LEA richiede solo più byte per codificare perché [eax*4]richiede un disp32=0. (Funziona su porte diverse rispetto a turni, però.) Vedi agner.org/optimize e stackoverflow.com/tags/x86/info .
Peter Cordes,

29

L'istruzione MOV reg, addr significa leggere una variabile memorizzata nell'indirizzo addr nel registro reg. L'istruzione LEA reg, addr significa leggere l'indirizzo (non la variabile memorizzata nell'indirizzo) nel registro reg.

Un'altra forma dell'istruzione MOV è MOV reg, immdata che significa leggere i dati immediati (cioè costanti) immdata nel registro reg. Si noti che se l'add nel registro LEA, addr è solo una costante (ovvero un offset fisso), allora l'istruzione LEA è essenzialmente la stessa di un equivalente MOV reg, istruzione immdata che carica la stessa costante dei dati immediati.


11

Se si specifica solo un valore letterale, non vi è alcuna differenza. LEA ha più abilità, però, e puoi leggerne qui:

http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-1.html#HEADING1-136


Immagino, con l'eccezione che nell'assemblatore GNU non è vero quando si tratta di etichette nel segmento .bss? AFAIR non puoi davvero leal TextLabel, LabelFromBssSegmentquando hai avuto smth. tipo .bss .lcomm LabelFromBssSegment, 4, dovresti movl $TextLabel, LabelFromBssSegment, vero?
JSmyth,

@JSmyth: è solo perché learichiede una destinazione del registro, ma movpuò avere una destinazione di imm32origine e memoria. Questa limitazione non è ovviamente specifica per l'assemblatore GNU.
Peter Cordes,

1
Inoltre, questa risposta è fondamentalmente sbagliata perché la domanda è MOV AX, [TABLE-ADDR], che è un carico. Quindi c'è una grande differenza. L'istruzione equivalente èmov ax, OFFSET table_addr
Peter Cordes,

10

Dipende dall'assemblatore usato, perché

mov ax,table_addr

in MASM funziona come

mov ax,word ptr[table_addr]

Quindi carica i primi byte da table_addre NON l'offset a table_addr. Dovresti usare invece

mov ax,offset table_addr

o

lea ax,table_addr

che funziona allo stesso modo.

leaversione funziona anche bene se table_addrè una variabile locale ad es

some_procedure proc

local table_addr[64]:word

lea ax,table_addr

grazie mille, è solo che non posso segnarne più di una come risposta :(
naveen,

5
La differenza tra le istruzioni x86 MOV e LEA sicuramente NON dipende dall'assemblatore.
IJ Kennedy,

4

Nessuna delle risposte precedenti è arrivata fino in fondo alla mia confusione, quindi vorrei aggiungere la mia.

Quello che mi mancava è che le leaoperazioni trattano l'uso delle parentesi in modo diverso da come movfunziona.

Pensa a C. Diciamo che ho una serie di longciò che chiamo array. Ora l'espressione array[i]esegue una dereferenza, caricando il valore dalla memoria all'indirizzo array + i * sizeof(long)[1].

D'altra parte, considera l'espressione &array[i]. Questo contiene ancora la sottoespressione array[i], ma non viene eseguita alcuna dereferenziazione! Il significato di array[i]è cambiato. Non significa più eseguire una deferenza ma si comporta invece come una specie di specifica , indicando &quale indirizzo di memoria stiamo cercando. Se lo desideri, puoi in alternativa pensare &a "cancellare" la dereferenza.

Poiché i due casi d'uso sono simili in molti modi, condividono la sintassi array[i], ma l'esistenza o l'assenza di una &modifica cambia il modo in cui tale sintassi viene interpretata. Senza &, è una dereferenza e in realtà legge dall'array. Con &, non lo è. Il valore array + i * sizeof(long)è ancora calcolato, ma non è referenziato.

La situazione è molto simile con move lea. Con mov, si verifica una dereferenza che non accade con lea. Questo nonostante l'uso delle parentesi che si verificano in entrambi. Ad esempio, movq (%r8), %r9e leaq (%r8), %r9. Con mov, queste parentesi significano "dereference"; con lea, non lo fanno. Questo è simile a come array[i]significhi "dereference" solo quando non c'è &.

Un esempio è in ordine.

Considera il codice

movq (%rdi, %rsi, 8), %rbp

Questo carica il valore nella posizione di memoria %rdi + %rsi * 8nel registro %rbp. Cioè: ottenere il valore nel registro %rdie il valore nel registro %rsi. Moltiplica il secondo per 8, quindi aggiungilo al primo. Trova il valore in questa posizione e inseriscilo nel registro %rbp.

Questo codice corrisponde alla linea C x = array[i];, dove arraydiventa %rdie idiventa %rsie xdiventa %rbp. La 8è la lunghezza del tipo di dati contenuti nella matrice.

Ora considera un codice simile che utilizza lea:

leaq (%rdi, %rsi, 8), %rbp

Proprio come l'uso di movqcorrisponde al dereferenziamento, l'uso di leaqqui corrisponde al non dereferenziamento. Questa linea di montaggio corrisponde alla linea C x = &array[i];. Ricordiamo che &cambia il significato del array[i]dereferenziare semplicemente specificando una posizione. Allo stesso modo, l'uso di leaqcambia il significato di (%rdi, %rsi, 8)da dereferenziazione a specificare un luogo.

La semantica di questa riga di codice è la seguente: ottenere il valore nel registro %rdie il valore nel registro %rsi. Moltiplica il secondo per 8, quindi aggiungilo al primo. Inserire questo valore nel registro %rbp. Nessun carico dalla memoria è coinvolto, solo operazioni aritmetiche [2].

Nota che l'unica differenza tra le mie descrizioni di leaqe movqè che movqfa una dereferenza e leaqnon lo fa. In effetti, per scrivere la leaqdescrizione, ho praticamente copiato + incollato la descrizione movqe poi rimosso "Trova il valore in questa posizione".

Riassumendo: movqvs. leaqè complicato perché trattano l'uso delle parentesi, come in (%rsi)e (%rdi, %rsi, 8), in modo diverso. In movq(e tutte le altre istruzioni tranne lea), queste parentesi denotano un'autentica dereferenza, mentre in leaqesse non sono e sono sintassi puramente convenienti.


[1] Ho detto che quando arrayè un array di long, l'espressione array[i]carica il valore dall'indirizzo array + i * sizeof(long). Questo è vero, ma c'è una sottigliezza che dovrebbe essere affrontata. Se scrivo il codice C.

long x = array[5];

questo non è lo stesso della digitazione

long x = *(array + 5 * sizeof(long));

Sembra che dovrebbe essere basato sulle mie precedenti dichiarazioni, ma non lo è.

Quello che sta succedendo è che l'aggiunta del puntatore C ha un trucco. Supponiamo di avere un puntatore che ppunta a valori di tipo T. L'espressione p + ifa non medio "la posizione in ppiù ibyte". Al contrario, l'espressione in p + i realtà significa "la posizione a byte ppiù i * sizeof(T)".

La comodità di questo è che per ottenere "il valore successivo" non ci resta che scrivere p + 1al posto di p + 1 * sizeof(T).

Ciò significa che il codice C long x = array[5];è effettivamente equivalente a

long x = *(array + 5)

perché C moltiplicherà automaticamente 5per sizeof(long).

Quindi, nel contesto di questa domanda StackOverflow, in che modo tutto ciò è rilevante? Significa che quando dico "l'indirizzo array + i * sizeof(long)", non intendo che " array + i * sizeof(long)" sia interpretato come un'espressione C. Sto facendo la moltiplicazione da sizeof(long)solo per rendere la mia risposta più esplicita, ma capisco che a causa di ciò, questa espressione non dovrebbe essere letta come C. Proprio come la matematica normale che usa la sintassi C.

[2] Nota a margine: poiché tutto leaciò che fa sono operazioni aritmetiche, i suoi argomenti in realtà non devono fare riferimento a indirizzi validi. Per questo motivo, viene spesso utilizzato per eseguire la pura aritmetica su valori che potrebbero non essere intesi come non dedotti. Ad esempio, cccon l' -O2ottimizzazione si traduce

long f(long x) {
  return x * 5;
}

nei seguenti (righe irrilevanti rimosse):

f:
  leaq (%rdi, %rdi, 4), %rax  # set %rax to %rdi + %rdi * 4
  ret

1
Sì, buona spiegazione, in modo più dettagliato rispetto alle altre risposte, e sì, l' &operatore di C è una buona analogia. Forse vale la pena sottolineare che LEA è il caso speciale, mentre MOV è proprio come ogni altra istruzione che può prendere una memoria o registrare un operando. ad esempio, add (%rdi), %eaxutilizza solo la modalità di indirizzamento per indirizzare la memoria, come in MOV. Anche correlato: utilizzare LEA su valori che non sono indirizzi / puntatori? porta ulteriormente questa spiegazione: LEA è il modo in cui è possibile utilizzare il supporto HW della CPU per la matematica degli indirizzi per eseguire calcoli arbitrari.
Peter Cordes,

"get the value at %rdi" - Questo è stranamente formulato. Intendi che dovrebbe essere usato il valore nel registro rdi . L'uso di "at" sembra significare una dereferenza di memoria dove non ce n'è.
ecm

@PeterCordes Grazie! Ho aggiunto che è un caso speciale la risposta.
Quelklef,

1
@ecm buon punto; Non me ne sono accorto. L'ho cambiato ora, grazie! :)
Quelklef,

Cordiali saluti, frase più breve che risolve il problema ecm sottolineato include: "il valore di %rdi " o "il valore in %rdi ". Il tuo "valore nel registro %rdi" è lungo ma va bene, e forse potrebbe aiutare qualcuno che fatica a capire i registri rispetto alla memoria.
Peter Cordes,

2

Fondamentalmente ... "Sposta in REG ... dopo averlo calcolato ..." sembra essere utile anche per altri scopi :)

se dimentichi semplicemente che il valore è un puntatore, puoi usarlo per l'ottimizzazione / minimizzazione del codice ... qualunque cosa ...

MOV EBX , 1
MOV ECX , 2

;//with 1 instruction you got result of 2 registers in 3rd one ...
LEA EAX , [EBX+ECX+5]

EAX = 8

originariamente sarebbe:

MOV EAX, EBX
ADD EAX, ECX
ADD EAX, 5

Sì, leaè un'istruzione shift-and-add che utilizza la codifica e la sintassi della macchina operando-memoria, perché l'hardware sa già come decodificare ModR / M + SIB + disp0 / 8/32.
Peter Cordes,

1

Come indicato nelle altre risposte:

  • MOVcatturerà i dati a l'indirizzo all'interno delle parentesi e posto che i dati nella destinazione operando.
  • LEAeseguirà il calcolo dell'indirizzo all'interno delle parentesi e posizionerà tale indirizzo calcolato nell'operando di destinazione. Questo accade senza effettivamente uscire in memoria e ottenere i dati. Il lavoro svolto da LEAè nel calcolo dell '"indirizzo effettivo".

Poiché la memoria può essere indirizzata in diversi modi (vedere gli esempi seguenti), a LEAvolte viene utilizzata per aggiungere o moltiplicare i registri insieme senza utilizzare un esplicito ADDo MULun'istruzione (o equivalente).

Poiché tutti mostrano esempi nella sintassi Intel, eccone alcuni nella sintassi AT&T:

MOVL 16(%ebp), %eax       /* put long  at  ebp+16  into eax */
LEAL 16(%ebp), %eax       /* add 16 to ebp and store in eax */

MOVQ (%rdx,%rcx,8), %rax  /* put qword at  rcx*8 + rdx  into rax */
LEAQ (%rdx,%rcx,8), %rax  /* put value of "rcx*8 + rdx" into rax */

MOVW 5(%bp,%si), %ax      /* put word  at  si + bp + 5  into ax */
LEAW 5(%bp,%si), %ax      /* put value of "si + bp + 5" into ax */

MOVQ 16(%rip), %rax       /* put qword at rip + 16 into rax                 */
LEAQ 16(%rip), %rax       /* add 16 to instruction pointer and store in rax */

MOVL label(,1), %eax      /* put long at label into eax            */
LEAL label(,1), %eax      /* put the address of the label into eax */

Non hai mai voglia lea label, %eaxdi una [disp32]modalità di indirizzamento assoluto . Usa mov $label, %eaxinvece. Sì, funziona, ma è meno efficiente (codice macchina più grande ed esegue meno unità di esecuzione). Dato che menzioni AT&T, utilizzando LEA su valori che non sono indirizzi / puntatori? usa AT&T e la mia risposta contiene alcuni altri esempi di AT&T.
Peter Cordes,

1

Comprendiamolo con un esempio.

mov eax, [ebx] e

lea eax, [ebx] Supponiamo che il valore in ebx sia 0x400000. Quindi mov andrà all'indirizzo 0x400000 e copierà 4 byte di dati presenti nel registro eax. Mentre lea copia l'indirizzo 0x400000 in eax. Quindi, dopo l'esecuzione di ciascuna istruzione il valore di eax in ciascun caso sarà (supponendo che in memoria 0x400000 contenga 30).

eax = 30 (in caso di mov) eax = 0x400000 (in caso di lea) Per definizione mov copia i dati da rm32 a destinazione (mov dest rm32) e lea (carica indirizzo effettivo) copierà l'indirizzo a destinazione (mov dest rm32 ).


0

LEA (Load Effective Address) è un'istruzione shift-and-add. È stato aggiunto a 8086 perché l'hardware è lì per decodificare e calcolare le modalità di indirizzo.


0

MOV può fare la stessa cosa di LEA [etichetta], ma l'istruzione MOV contiene l'indirizzo effettivo all'interno dell'istruzione stessa come costante immediata (calcolata in anticipo dall'assemblatore). LEA utilizza un PC relativo per calcolare l'indirizzo effettivo durante l'esecuzione dell'istruzione.


Questo è vero solo per la modalità a 64 bit (in cui l'indirizzamento relativo al PC era nuovo); in altre modalità lea [labelè un inutile spreco di byte rispetto a un più compatto mov, quindi è necessario specificare le condizioni di cui si sta parlando. Inoltre, per alcuni assemblatori [label]non è la sintassi corretta per una modalità di indirizzamento relativa al RIP. Ma sì, è esatto. Come caricare l'indirizzo della funzione o l'etichetta nel registro in GNU Assembler spiega in modo più dettagliato.
Peter Cordes, il

-1

La differenza è sottile ma importante. L'istruzione MOV è un 'MOVe' effettivamente una copia dell'indirizzo che rappresenta l'etichetta TABLE-ADDR. L'istruzione LEA è un "Load Effective Address" che è un'istruzione indiretta, il che significa che TABLE-ADDR punta a una posizione di memoria in cui si trova l'indirizzo da caricare.

L'uso efficace di LEA equivale all'utilizzo di puntatori in linguaggi come C, in quanto tale è un'istruzione potente.


7
Penso che questa risposta sia al massimo confusa. "L'istruzione LEA è un 'Load Effective Address' che è un'istruzione indiretta, il che significa che TABLE-ADDR punta a una posizione di memoria in cui si trova l'indirizzo da caricare." In realtà LEA caricherà l'indirizzo, non il contenuto dell'indirizzo. Penso che in realtà l'interrogatore debba essere rassicurato sul fatto che MOV e LEA possano sovrapporsi e fare esattamente la stessa cosa, in alcune circostanze
Bill Forster,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.