Qual é a diferença entre MOV e LEA?

138

Gostaria de saber qual é a diferença entre estas instruções:

MOV AX, [TABLE-ADDR]

e

LEA AX, [TABLE-ADDR]
naveen
fonte
5
duplicado: stackoverflow.com/questions/1658294/…
Nick Dandoulakis
8
obrigado nick. Primeiro de tudo, eu não teria encontrado uma resposta para essa pergunta olhando para esse link. Aqui, eu estava procurando por informações específicas. A discussão no link que você forneceu é de natureza mais genérica.
Naveen
3
Eu votei no dup do Nick há eras atrás, mas vtc'd agora. Refletindo, eu estava muito apressado e agora percebi que a) a outra pergunta não responde "qual é a diferença" eb) essa é uma pergunta útil. Desculpas para Naveen pelo meu erro - Se eu pudesse desfazer VTC ...
Ruben Bartelink
Relacionado: Usando LEA em valores que não são endereços / ponteiros? fala sobre outros usos do LEA, para matemática arbitrária.
Peter Cordes

Respostas:

168
  • LEA significa carregar endereço efetivo
  • MOV significa carregar valor

Em resumo, LEAcarrega um ponteiro para o item que você está endereçando, enquanto o MOV carrega o valor real nesse endereço.

O objetivo de LEAé permitir realizar um cálculo de endereço não trivial e armazenar o resultado [para uso posterior]

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

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

Onde há apenas constantes envolvidas MOV(por meio dos cálculos constantes do montador), às vezes pode parecer se sobrepor aos casos mais simples de uso de LEA. É útil se você tiver um cálculo de várias partes com vários endereços base etc.

Ruben Bartelink
fonte
6
+1 obrigado pela explicação clara, me ajudou a responder outra pergunta.
legends2k
Me confunde que lea tenha "load" no nome e as pessoas digam que "carrega" um endereço computado em um registrador, porque todas as entradas para calcular a localização da memória são valores imediatos ou registradores. Lea AFAICT executa apenas um cálculo, ele não carrega nada, onde carregar significa tocar na memória?
Joseph Garvin
2
@josephGarvin IIRC, o termo busca seria aplicado a esse aspecto; Carga é exatamente como você substitui o valor em um registro por algo do zero. por exemplo, LAHFé: Carregue FLAGS no registro AH . No CIL do CLR (que é uma máquina abstrata baseada em pilha de nível superior, o termo carga refere-se a colocar um valor na pilha nocional e é normalmente l... e o sequivalente ... faz o inverso). Essas notas: cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html ) sugerem que existem de fato arquiteturas nas quais sua distinção se aplica.
Ruben Bartelink
tudo me lembra slideshare.net/pirhilton/... ;)
Ruben Bartelink
45

Na sintaxe do 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

Na sintaxe MASM, use OFFSET varpara obter um movimento imediato em vez de uma carga.

Amit Singh Tomar
fonte
3
apenas na sintaxe NASM. Na sintaxe do MASM, mov eax, varé uma carga, o mesmo que mov eax, [var], e você deve usar mov eax, OFFSET varpara usar um rótulo como uma constante imediata.
Peter Cordes
1
Claro, simples e demonstra o que eu estava tentando confirmar. Obrigado.
precisa saber é o seguinte
1
Observe que, em todos esses exemplos, leaé a pior opção, exceto no modo de 64 bits, para o endereçamento relativo ao RIP. mov r32, imm32roda em mais portas. lea eax, [edx*4]é uma cópia e troca que não pode ser executada em uma instrução, mas no mesmo registro o LEA leva apenas mais bytes para codificar porque [eax*4]requer a disp32=0. (Porém, ele roda em portas diferentes das turnos.) Consulte agner.org/optimize e stackoverflow.com/tags/x86/info .
Peter Cordes
29

A instrução MOV reg, addr significa ler uma variável armazenada no endereço addr no registrador reg. A instrução LEA reg, addr significa ler o endereço (não a variável armazenada no endereço) no registro reg.

Outra forma da instrução MOV é MOV reg, immdata, que significa ler os dados imediatos (ie constantes) immdata no registrador reg. Observe que se o addr no LEA reg, addr é apenas uma constante (ou seja, um deslocamento fixo), então a instrução LEA é essencialmente exatamente igual a um MOV reg equivalente, instrução immdata que carrega a mesma constante que os dados imediatos.

Bill Forster
fonte
11

Se você especificar apenas um literal, não haverá diferença. LEA tem mais habilidades, no entanto, e você pode ler sobre elas aqui:

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

Lars D
fonte
Eu acho que, com a exceção de que no GNU assembler não é verdade quando se trata de rótulos no segmento .bss? AFAIR você não pode realmente leal TextLabel, LabelFromBssSegmentquando você tem algo. tipo .bss .lcomm LabelFromBssSegment, 4, você precisaria movl $TextLabel, LabelFromBssSegment, não é?
JSmyth
@JSmyth: Isso é apenas porque learequer um destino de registro, mas movpode ter uma imm32fonte e um destino de memória. Obviamente, essa limitação não é específica para o montador GNU.
Peter Cordes
1
Além disso, esta resposta está basicamente errada, porque a pergunta está sendo feita MOV AX, [TABLE-ADDR], o que é uma carga. Portanto, há uma grande diferença. A instrução equivalente émov ax, OFFSET table_addr
Peter Cordes
10

Depende do montador usado, porque

mov ax,table_addr

no MASM funciona como

mov ax,word ptr[table_addr]

Portanto, ele carrega os primeiros bytes de table_addre NÃO o deslocamento para table_addr. Você deveria usar

mov ax,offset table_addr

ou

lea ax,table_addr

que funciona da mesma maneira.

leaversão também funciona bem se table_addrfor uma variável local, por exemplo

some_procedure proc

local table_addr[64]:word

lea ax,table_addr
Bartosz Wójcik
fonte
muito obrigado, é só que eu não posso marcar mais de uma resposta como :(
Naveen
5
A diferença entre as instruções x86 MOV e LEA definitivamente NÃO depende do montador.
IJ Kennedy
4

Nenhuma das respostas anteriores chegou ao fundo da minha própria confusão, então eu gostaria de adicionar a minha.

O que eu estava perdendo é que as leaoperações tratam o uso de parênteses de maneira diferente de comomov .

Pense em C. Digamos que eu tenha uma matriz longque eu chamo array. Agora a expressão array[i]executa uma desreferência, carregando o valor da memória no endereço array + i * sizeof(long)[1].

Por outro lado, considere a expressão &array[i]. Isso ainda contém a subexpressão array[i], mas nenhuma desreferenciação é realizada! O significado de array[i]mudou. Não significa mais executar uma deferência, mas age como uma espécie de especificação , informando &o endereço de memória que estamos procurando. Se quiser, você pode pensar alternadamente &em "cancelar" a desreferência.

Como os dois casos de uso são semelhantes em muitos aspectos, eles compartilham a sintaxe array[i], mas a existência ou ausência de uma &alteração na forma como essa sintaxe é interpretada. Sem &, é uma desreferência e, na verdade, lê da matriz. Com &, não é. O valor quearray + i * sizeof(long) ainda é calculado, mas não é desreferenciado.

A situação é muito parecida com move lea. Com mov, ocorre uma desreferência que não acontece com lea. Isso ocorre apesar do uso de parênteses que ocorre em ambos. Por exemplo, movq (%r8), %r9e leaq (%r8), %r9. Com mov, esses parênteses significam "desreferência"; com lea, eles não. Isso é semelhante a como array[i]apenas significa "desreferência" quando não há& .

Um exemplo está em ordem.

Considere o código

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

Isso carrega o valor no local da memória %rdi + %rsi * 8no registro %rbp. Ou seja: obtenha o valor no registro %rdie o valor no registro %rsi. Multiplique o último por 8 e adicione-o ao primeiro. Encontre o valor neste local e coloque-o no registro %rbp.

Este código corresponde à linha C x = array[i];, onde arrayse torna e se torna %rdie ise torna . o%rsix%rbp8 é o comprimento do tipo de dados contido na matriz.

Agora considere código semelhante que usa lea:

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

Assim como o uso de movqcorrespondeu à desreferenciação, o uso leaqaqui corresponde a não desreferenciação. Esta linha de montagem corresponde à linha C x = &array[i];. Lembre-se de que &altera o significado de array[i]desreferenciar para simplesmente especificar um local. Da mesma forma, o uso de leaqaltera o significado de (%rdi, %rsi, 8)desreferenciar para especificar um local.

A semântica desta linha de código é a seguinte: obtenha o valor no registro %rdie o valor no registro %rsi. Multiplique o último por 8 e adicione-o ao primeiro. Coloque esse valor no registro %rbp. Nenhuma carga da memória está envolvida, apenas operações aritméticas [2].

Observe que a única diferença entre minhas descrições de leaqe movqé que movqfaz uma desreferência e leaqnão. De fato, para escrever a leaqdescrição, basicamente copio + colei a descrição de movqe removi "Encontre o valor neste local".

Resumir: movqvs. leaqé complicado porque eles tratam o uso de parênteses, como em (%rsi)e (%rdi, %rsi, 8), de maneira diferente. Em movq(e em todas as outras instruções, exceto lea), esses parênteses denotam uma desreferência genuína, enquanto que leaqeles não são e são uma sintaxe puramente conveniente.


[1] Eu disse que quando arrayé uma matriz de long, a expressão array[i]carrega o valor do endereço array + i * sizeof(long). Isso é verdade, mas há uma sutileza que deve ser abordada. Se eu escrever o código C

long x = array[5];

isso não é o mesmo que digitar

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

Parece que deve basear-se em minhas declarações anteriores, mas não é.

O que está acontecendo é que a adição de ponteiro C tem um truque. Digamos que eu tenho um ponteiro papontando para valores do tipo T. A expressão p + ifaz não significativo "a posição em pmais ibytes". Em vez disso, a expressão p + i realmente significa "a posição em pmais i * sizeof(T)bytes".

A conveniência disso é que, para obter "o próximo valor", basta escrever em p + 1vez dep + 1 * sizeof(T) .

Isso significa que o código C long x = array[5];é realmente equivalente a

long x = *(array + 5)

porque C multiplicará automaticamente o 5por sizeof(long).

Portanto, no contexto desta questão do StackOverflow, como isso é relevante? Isso significa que, quando digo "o endereço array + i * sizeof(long)", não quero que " array + i * sizeof(long)" seja interpretado como uma expressão C. Estou fazendo a multiplicação sizeof(long)sozinho para tornar minha resposta mais explícita, mas entendo que, devido a isso, essa expressão não deve ser lida como C. Assim como a matemática normal que usa a sintaxe C.

[2] Nota: como tudo o que leafaz são operações aritméticas, seus argumentos não precisam se referir a endereços válidos. Por esse motivo, é frequentemente usado para executar aritmética pura em valores que podem não ter a intenção de ser desreferenciados. Por exemplo, cccom -O2otimização traduz

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

nas seguintes linhas (linhas irrelevantes removidas):

f:
  leaq (%rdi, %rdi, 4), %rax  # set %rax to %rdi + %rdi * 4
  ret
Quelklef
fonte
1
Sim, boa explicação, com mais detalhes do que as outras respostas, e sim, o &operador de C é uma boa analogia. Talvez valha a pena ressaltar que LEA é o caso especial, enquanto MOV é como todas as outras instruções que podem levar uma memória ou um operando de registro. por exemplo, add (%rdi), %eaxapenas usa o modo de endereçamento para endereçar a memória, o mesmo que o MOV. Também relacionado: Usando o LEA em valores que não são endereços / ponteiros? leva esta explicação adiante: LEA é como você pode usar o suporte HW da CPU para matemática de endereços para fazer cálculos arbitrários.
Peter Cordes
"obtenha o valor em %rdi" - isso é estranhamente redigido. Você quer dizer que o valor no registro rdi deve ser usado. Seu uso de "at" parece significar uma desreferência de memória onde não há nenhuma.
ecm 20/02
@PeterCordes Thanks! Adicionei o argumento de que esse é um caso especial à resposta.
Quelklef 20/02
1
@ecm Bom ponto; Eu não percebi isso. Eu mudei agora, obrigado! :)
Quelklef 20/02
Para sua informação, frases mais curtas que solucionam o problema apontado por ecm incluem: "o valor de %rdi " ou "o valor em %rdi ". Seu "valor no registro %rdi" é longo, mas bom, e talvez possa ajudar alguém que está lutando para entender registros versus memória.
Peter Cordes
2

Basicamente ... "Mude para o REG ... depois de calculá-lo ..." parece ser bom para outros fins também :)

se você simplesmente esquecer que o valor é um ponteiro, você pode usá-lo para otimizações / minimizações de código ... seja como for.

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

originalmente seria:

MOV EAX, EBX
ADD EAX, ECX
ADD EAX, 5
Ostap
fonte
Sim, leaé uma instrução shift-and-add que usa codificação e sintaxe da máquina operadora de memória, porque o hardware já sabe decodificar ModR / M + SIB + disp0 / 8/32.
Peter Cordes
1

Como indicado nas outras respostas:

  • MOVvai pegar os dados no endereço dentro dos colchetes e colocá- los no operando de destino.
  • LEArealizará o cálculo do endereço entre colchetes e colocará esse endereço calculado no operando de destino. Isso acontece sem realmente sair para a memória e obter os dados. O trabalho realizado por LEAestá no cálculo do "endereço efetivo".

Porque a memória pode ser abordada de várias maneiras diferentes (veja exemplos abaixo), LEAé por vezes utilizado para adicionar ou registos multiplicam em conjunto sem usar um explícita ADDou MULinstrução (ou equivalente).

Como todos estão mostrando exemplos na sintaxe da Intel, eis alguns na sintaxe da 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 */
Sir Random
fonte
Você nunca deseja lea label, %eaxum [disp32]modo de endereçamento absoluto . Use em mov $label, %eaxvez disso. Sim, funciona, mas é menos eficiente (código de máquina maior e roda com menos unidades de execução). Desde que você mencionou a AT&T, usando LEA em valores que não são endereços / ponteiros? usa a AT&T, e minha resposta tem alguns outros exemplos da AT&T.
Peter Cordes
1

Vamos entender isso com um exemplo.

mov eax, [ebx] e

lea eax, [ebx] Suponha que o valor em ebx seja 0x400000. Em seguida, mov irá para o endereço 0x400000 e copiará 4 bytes de dados presentes no registro eax. Enquanto lea copiará o endereço 0x400000 para o eax. Assim, após a execução de cada valor de instrução de eax em cada caso, será (assumindo que a memória 0x400000 contém 30).

eax = 30 (no caso de mov) eax = 0x400000 (no caso de lea) Para definição, mov copie os dados de rm32 para o destino (mov dest rm32) e lea (carregar endereço efetivo) copiará o endereço para o destino (mov dest rm32 )

Luftatako
fonte
0

LEA (Load Effective Address) é uma instrução shift-and-add. Foi adicionado ao 8086 porque o hardware existe para decodificar e calcular os modos de endereçamento.

jojasicek
fonte
0

MOV pode fazer o mesmo que LEA [rótulo], mas a instrução MOV contém o endereço efetivo dentro da própria instrução como uma constante imediata (calculada previamente pelo montador). O LEA usa parente do PC para calcular o endereço efetivo durante a execução da instrução.

Michel Sayde
fonte
Isso é verdade apenas no modo de 64 bits (onde o endereçamento relativo ao PC era novo); em outros modos, lea [labelé um desperdício inútil de bytes versus um mais compacto mov; portanto, você deve especificar as condições de que está falando. Além disso, para alguns montadores, [label]não é a sintaxe correta para um modo de endereçamento relativo ao RIP. Mas sim, isso é preciso. Como carregar o endereço da função ou etiqueta no registro no GNU Assembler explica mais detalhadamente.
Peter Cordes
-1

A diferença é sutil, mas importante. A instrução MOV é um 'MOVe' efetivamente uma cópia do endereço que a etiqueta TABLE-ADDR representa. A instrução LEA é um 'Endereço Efetivo de Carregamento', que é uma instrução indireta, o que significa que TABLE-ADDR aponta para um local de memória no qual o endereço a ser carregado é encontrado.

Usar LEA efetivamente é equivalente a usar ponteiros em idiomas como C, pois é uma instrução poderosa.

Guillermo Phillips
fonte
7
Eu acho que essa resposta é confusa na melhor das hipóteses. "A instrução LEA é um 'Endereço Efetivo de Carregamento', que é uma instrução indireta, o que significa que o TABLE-ADDR aponta para um local de memória no qual o endereço a ser carregado é encontrado." Na verdade, o LEA carregará o endereço, não o conteúdo do endereço. Eu acho que realmente as necessidades questionador de ser tranquilizados que MOV e LEA podem se sobrepor, e fazer exatamente a mesma coisa, em algumas circunstâncias
Bill Forster