Vorrei sapere qual è la differenza tra queste istruzioni:
MOV AX, [TABLE-ADDR]
e
LEA AX, [TABLE-ADDR]
Vorrei sapere qual è la differenza tra queste istruzioni:
MOV AX, [TABLE-ADDR]
e
LEA AX, [TABLE-ADDR]
Risposte:
LEA
significa Carica indirizzo effettivoMOV
significa Carica valoreIn breve, LEA
carica 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.
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' s
equivalente ... 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.
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 var
per ottenere un mov-immediato invece di un carico.
mov eax, var
è un carico, lo stesso di mov eax, [var]
, e devi usare mov eax, OFFSET var
per usare un'etichetta come costante immediata.
lea
è la scelta peggiore, tranne nella modalità a 64 bit per l'indirizzamento relativo al RIP. mov r32, imm32
funziona 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 .
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.
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
leal TextLabel, LabelFromBssSegment
quando hai avuto smth. tipo .bss .lcomm LabelFromBssSegment, 4
, dovresti movl $TextLabel, LabelFromBssSegment
, vero?
lea
richiede una destinazione del registro, ma mov
può avere una destinazione di imm32
origine e memoria. Questa limitazione non è ovviamente specifica per l'assemblatore GNU.
MOV AX, [TABLE-ADDR]
, che è un carico. Quindi c'è una grande differenza. L'istruzione equivalente èmov ax, OFFSET table_addr
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_addr
e NON l'offset a table_addr
. Dovresti usare invece
mov ax,offset table_addr
o
lea ax,table_addr
che funziona allo stesso modo.
lea
versione funziona anche bene se table_addr
è una variabile locale ad es
some_procedure proc
local table_addr[64]:word
lea ax,table_addr
Nessuna delle risposte precedenti è arrivata fino in fondo alla mia confusione, quindi vorrei aggiungere la mia.
Quello che mi mancava è che le lea
operazioni trattano l'uso delle parentesi in modo diverso da come mov
funziona.
Pensa a C. Diciamo che ho una serie di long
ciò 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 mov
e 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), %r9
e 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 * 8
nel registro %rbp
. Cioè: ottenere il valore nel registro %rdi
e 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 array
diventa %rdi
e i
diventa %rsi
e x
diventa %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 movq
corrisponde al dereferenziamento, l'uso di leaq
qui 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 leaq
cambia 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 %rdi
e 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 leaq
e movq
è che movq
fa una dereferenza e leaq
non lo fa. In effetti, per scrivere la leaq
descrizione, ho praticamente copiato + incollato la descrizione movq
e poi rimosso "Trova il valore in questa posizione".
Riassumendo: movq
vs. 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 leaq
esse 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 p
punta a valori di tipo T
. L'espressione p + i
fa non medio "la posizione in p
più i
byte". Al contrario, l'espressione in p + i
realtà significa "la posizione a byte p
più i * sizeof(T)
".
La comodità di questo è che per ottenere "il valore successivo" non ci resta che scrivere p + 1
al 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 5
per 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 lea
ciò 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, cc
con l' -O2
ottimizzazione 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
&
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), %eax
utilizza 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.
%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'è.
%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.
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
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.
Come indicato nelle altre risposte:
MOV
catturerà i dati a l'indirizzo all'interno delle parentesi e posto che i dati nella destinazione operando.LEA
eseguirà 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 LEA
volte viene utilizzata per aggiungere o moltiplicare i registri insieme senza utilizzare un esplicito ADD
o MUL
un'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 */
lea label, %eax
di una [disp32]
modalità di indirizzamento assoluto . Usa mov $label, %eax
invece. 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.
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 ).
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.
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.
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.