Gostaria de saber qual é a diferença entre estas instruções:
MOV AX, [TABLE-ADDR]
e
LEA AX, [TABLE-ADDR]
Gostaria de saber qual é a diferença entre estas instruções:
MOV AX, [TABLE-ADDR]
e
LEA AX, [TABLE-ADDR]
Respostas:
LEA
significa carregar endereço efetivoMOV
significa carregar valorEm resumo, LEA
carrega 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.
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 s
equivalente ... 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.
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 var
para obter um movimento imediato em vez de uma carga.
mov eax, var
é uma carga, o mesmo que mov eax, [var]
, e você deve usar mov eax, OFFSET var
para usar um rótulo como uma constante imediata.
lea
é a pior opção, exceto no modo de 64 bits, para o endereçamento relativo ao RIP. mov r32, imm32
roda 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 .
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.
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
leal TextLabel, LabelFromBssSegment
quando você tem algo. tipo .bss .lcomm LabelFromBssSegment, 4
, você precisaria movl $TextLabel, LabelFromBssSegment
, não é?
lea
requer um destino de registro, mas mov
pode ter uma imm32
fonte e um destino de memória. Obviamente, essa limitação não é específica para o montador GNU.
MOV AX, [TABLE-ADDR]
, o que é uma carga. Portanto, há uma grande diferença. A instrução equivalente émov ax, OFFSET table_addr
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_addr
e 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.
lea
versão também funciona bem se table_addr
for uma variável local, por exemplo
some_procedure proc
local table_addr[64]:word
lea ax,table_addr
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 lea
operações tratam o uso de parênteses de maneira diferente de comomov
.
Pense em C. Digamos que eu tenha uma matriz long
que 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 mov
e 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), %r9
e 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 * 8
no registro %rbp
. Ou seja: obtenha o valor no registro %rdi
e 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 array
se torna e se torna %rdi
e i
se torna . o%rsi
x
%rbp
8
é 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 movq
correspondeu à desreferenciação, o uso leaq
aqui 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 leaq
altera 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 %rdi
e 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 leaq
e movq
é que movq
faz uma desreferência e leaq
não. De fato, para escrever a leaq
descrição, basicamente copio + colei a descrição de movq
e removi "Encontre o valor neste local".
Resumir: movq
vs. 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 leaq
eles 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 p
apontando para valores do tipo T
. A expressão p + i
faz não significativo "a posição em p
mais i
bytes". Em vez disso, a expressão p + i
realmente significa "a posição em p
mais i * sizeof(T)
bytes".
A conveniência disso é que, para obter "o próximo valor", basta escrever em p + 1
vez 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 5
por 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 lea
faz 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, cc
com -O2
otimizaçã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
&
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), %eax
apenas 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.
%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.
%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.
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
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.
Como indicado nas outras respostas:
MOV
vai pegar os dados no endereço dentro dos colchetes e colocá- los no operando de destino.LEA
realizará 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 LEA
está 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 ADD
ou MUL
instruçã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 */
lea label, %eax
um [disp32]
modo de endereçamento absoluto . Use em mov $label, %eax
vez 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.
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 )
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.
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.
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.