Me gustaría saber cuál es la diferencia entre estas instrucciones:
MOV AX, [TABLE-ADDR]
y
LEA AX, [TABLE-ADDR]
Me gustaría saber cuál es la diferencia entre estas instrucciones:
MOV AX, [TABLE-ADDR]
y
LEA AX, [TABLE-ADDR]
Respuestas:
LEA
significa dirección efectiva de cargaMOV
significa valor de cargaEn resumen, LEA
carga un puntero al elemento que está direccionando, mientras que MOV carga el valor real en esa dirección.
El propósito de LEA
es permitirle a uno realizar un cálculo de dirección no trivial y almacenar el resultado [para su uso posterior]
LEA ax, [BP+SI+5] ; Compute address of value
MOV ax, [BP+SI+5] ; Load value at that address
Donde solo hay constantes involucradas, MOV
(a través de los cálculos constantes del ensamblador) a veces parece superponerse con los casos más simples de uso de LEA
. Es útil si tiene un cálculo de varias partes con múltiples direcciones base, etc.
LAHF
: cargar banderas en el registro AH . En el CIL de CLR (que es una máquina abstracta basada en una pila de nivel superior, el término carga se refiere a poner un valor en la pila nocional y normalmente es l
..., y el s
... equivalente es el inverso). Estas notas: cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/load.html ) sugieren que efectivamente existen arquitecturas en las que se aplica su distinción.
En sintaxis 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
En la sintaxis MASM, use OFFSET var
para obtener un movimiento inmediato en lugar de una carga.
mov eax, var
es una carga, igual que mov eax, [var]
, y debe usar mov eax, OFFSET var
para usar una etiqueta como una constante inmediata.
lea
es la peor opción, excepto en el modo de 64 bits para el direccionamiento relativo a RIP. mov r32, imm32
Se ejecuta en más puertos. lea eax, [edx*4]
es una copia y cambio que no se puede hacer en una sola instrucción, pero en el mismo registro LEA solo necesita más bytes para codificar porque [eax*4]
requiere a disp32=0
. (Sin embargo, se ejecuta en diferentes puertos que los turnos). Consulte agner.org/optimize y stackoverflow.com/tags/x86/info .
La instrucción MOV reg, addr significa leer una variable almacenada en la dirección addr en el registro de registro. La instrucción LEA reg, addr significa leer la dirección (no la variable almacenada en la dirección) en el registro de registro.
Otra forma de la instrucción MOV es MOV reg, immdata, que significa leer los datos inmediatos (es decir, constantes) immdata en el registro de registro. Tenga en cuenta que si el addr en LEA reg, addr es solo una constante (es decir, un desplazamiento fijo), entonces esa instrucción LEA es esencialmente exactamente la misma que un MOV reg equivalente, instrucción immdata que carga la misma constante que los datos inmediatos.
Si solo especifica un literal, no hay diferencia. Sin embargo, LEA tiene más habilidades, y puedes leer sobre ellas aquí:
http://www.oopweb.com/Assembly/Documents/ArtOfAssembly/Volume/Chapter_6/CH06-1.html#HEADING1-136
leal TextLabel, LabelFromBssSegment
puedes cuando tienes algo. como .bss .lcomm LabelFromBssSegment, 4
, tendrías que hacerlo movl $TextLabel, LabelFromBssSegment
, ¿no?
lea
requiere un destino de registro, pero mov
puede tener un imm32
origen y un destino de memoria. Por supuesto, esta limitación no es específica del ensamblador GNU.
MOV AX, [TABLE-ADDR]
, que es una carga. Entonces hay una gran diferencia. La instrucción equivalente esmov ax, OFFSET table_addr
Depende del ensamblador utilizado, porque
mov ax,table_addr
en MASM funciona como
mov ax,word ptr[table_addr]
Entonces carga los primeros bytes table_addr
y NO el desplazamiento a table_addr
. Deberías usar en su lugar
mov ax,offset table_addr
o
lea ax,table_addr
que funciona igual
lea
la versión también funciona bien si table_addr
es una variable local, por ejemplo
some_procedure proc
local table_addr[64]:word
lea ax,table_addr
Ninguna de las respuestas anteriores llegó al fondo de mi propia confusión, así que me gustaría agregar la mía.
Lo que me faltaba es que las lea
operaciones tratan el uso de paréntesis diferente de cómo lo mov
hace.
Piense en C. Digamos que tengo una variedad de long
eso que llamo array
. Ahora la expresión array[i]
realiza una desreferencia, cargando el valor de la memoria en la dirección array + i * sizeof(long)
[1].
Por otro lado, considere la expresión &array[i]
. ¡Esto todavía contiene la sub-expresión array[i]
, pero no se realiza la desreferenciación! El significado de array[i]
ha cambiado. Ya no significa realizar una deferencia, sino que actúa como una especie de especificación , indicando &
qué dirección de memoria estamos buscando. Si lo desea, puede pensar alternativamente &
como "cancelar" la desreferenciación.
Debido a que los dos casos de uso son similares en muchos aspectos, comparten la sintaxis array[i]
, pero la existencia o ausencia de un &
cambio cambia la forma en que se interpreta esa sintaxis. Sin &
, es una desreferencia y en realidad se lee de la matriz. Con &
, no lo es. El valor array + i * sizeof(long)
aún se calcula, pero no se desreferencia.
La situación es muy similar con mov
y lea
. Con mov
, se produce una desreferencia que no sucede con lea
. Esto es a pesar del uso de paréntesis que ocurre en ambos. Por ejemplo, movq (%r8), %r9
y leaq (%r8), %r9
. Con mov
, estos paréntesis significan "desreferencia"; con lea
, no lo hacen. Esto es similar a cómo array[i]
solo significa "desreferencia" cuando no existe &
.
Un ejemplo está en orden.
Considera el código
movq (%rdi, %rsi, 8), %rbp
Esto carga el valor en la ubicación de la memoria %rdi + %rsi * 8
en el registro %rbp
. Es decir: obtener el valor en el registro %rdi
y el valor en el registro %rsi
. Multiplique el último por 8 y luego agréguelo al primero. Encuentre el valor en esta ubicación y colóquelo en el registro %rbp
.
Este código corresponde a la línea C x = array[i];
, donde se array
convierte %rdi
y se i
convierte %rsi
y se x
convierte %rbp
. El 8
es la longitud del tipo de datos contenido en la matriz.
Ahora considere un código similar que usa lea
:
leaq (%rdi, %rsi, 8), %rbp
Así como el uso de movq
corresponde a la desreferenciación, el uso de leaq
aquí corresponde a no desreferenciar. Esta línea de montaje corresponde a la línea C x = &array[i];
. Recuerde que &
cambia el significado de array[i]
desreferenciar a simplemente especificar una ubicación. Del mismo modo, el uso de leaq
cambia el significado de (%rdi, %rsi, 8)
desreferenciar a especificar una ubicación.
La semántica de esta línea de código es la siguiente: obtener el valor en el registro %rdi
y el valor en el registro %rsi
. Multiplique el último por 8 y luego agréguelo al primero. Coloque este valor en el registro %rbp
. No hay carga de memoria involucrada, solo operaciones aritméticas [2].
Tenga en cuenta que la única diferencia entre mis descripciones de leaq
ymovq
es que movq
hace una desreferencia, y leaq
no lo hace. De hecho, para escribir la leaq
descripción, básicamente copié + pegué la descripción movq
y luego eliminé "Buscar el valor en esta ubicación".
Para resumir: movq
vs. leaq
es complicado porque tratan el uso de paréntesis, como en (%rsi)
y (%rdi, %rsi, 8)
, de manera diferente. Enmovq
(y en todas las demás instrucciones excepto lea
), estos paréntesis denotan una desreferencia genuina, mientras que en leaq
ellos no lo hacen y son una sintaxis puramente conveniente.
[1] He dicho que cuando array
es una matriz de long
, la expresiónarray[i]
carga el valor de la dirección array + i * sizeof(long)
. Esto es cierto, pero hay una sutileza que debe abordarse. Si escribo el código C
long x = array[5];
esto no es mismo que escribir
long x = *(array + 5 * sizeof(long));
Parece que debería basarse en mis declaraciones anteriores, pero no lo es.
Lo que sucede es que la adición del puntero C tiene un truco. Digamos que tengo un puntero que p
apunta a valores de tipo T
. La expresión p + i
hace no media "en la posición p
más i
bytes". En cambio, la expresión en p + i
realidad significa "la posición en p
más i * sizeof(T)
bytes".
La conveniencia de esto es que para obtener "el siguiente valor" solo tenemos que escribir p + 1
lugar de p + 1 * sizeof(T)
.
Esto significa que el código C long x = array[5];
es realmente equivalente a
long x = *(array + 5)
porque C multiplicará automáticamente el 5
por sizeof(long)
.
Entonces, en el contexto de esta pregunta de StackOverflow, ¿cómo es todo esto relevante? Significa que cuando digo "la dirección array + i * sizeof(long)
", no digo por " array + i * sizeof(long)
" debe interpretarse como una expresión C. Estoy haciendo la multiplicación por sizeof(long)
mí mismo para hacer mi respuesta más explícita, pero entiendo que debido a eso, esta expresión no debe leerse como C. Igual que las matemáticas normales que usan la sintaxis de C.
[2] Nota al margen: debido a que todo lo que lea
hace son operaciones aritméticas, sus argumentos en realidad no tienen que referirse a direcciones válidas. Por esta razón, a menudo se usa para realizar operaciones aritméticas puras en valores que pueden no ser desreferenciados. Por ejemplo, cc
con-O2
optimización se traduce
long f(long x) {
return x * 5;
}
en lo siguiente (líneas irrelevantes eliminadas):
f:
leaq (%rdi, %rdi, 4), %rax # set %rax to %rdi + %rdi * 4
ret
&
operador de C es una buena analogía. Quizás valga la pena señalar que LEA es el caso especial, mientras que MOV es como cualquier otra instrucción que pueda tomar una memoria o registrar un operando. por ejemplo, add (%rdi), %eax
solo usa el modo de direccionamiento para direccionar memoria, igual que MOV. También relacionado: ¿ Usar LEA en valores que no son direcciones / punteros? lleva esta explicación más allá: LEA es cómo puede usar el soporte HW de la CPU para la matemática de direcciones para hacer cálculos arbitrarios.
%rdi
": esto está redactado de forma extraña. Quiere decir que se debe usar el valor en el registro rdi
. Su uso de "at" parece significar una desreferencia de memoria donde no la hay.
%rdi
" o "el valor en %rdi
". Su "valor en el registro %rdi
" es largo pero está bien, y quizás podría ayudar a alguien que lucha por comprender los registros frente a la memoria.
Básicamente ... "Pasar a REG ... después de calcularlo ..." parece ser bueno para otros propósitos también :)
si olvida que el valor es un puntero, puede usarlo para optimizar / minimizar el código ... lo que sea ...
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 sería:
MOV EAX, EBX
ADD EAX, ECX
ADD EAX, 5
lea
es una instrucción shift-and-add que usa codificación y sintaxis de máquina de operandos de memoria, porque el hardware ya sabe cómo decodificar ModR / M + SIB + disp0 / 8/32.
Como se indica en las otras respuestas:
MOV
se agarra los datos a la dirección dentro de los corchetes y lugar que los datos en el destino operando.LEA
realizará el cálculo de la dirección dentro de los corchetes y colocará esa dirección calculada en el operando de destino. Esto sucede sin salir realmente a la memoria y obtener los datos. El trabajo realizado por LEA
está en el cálculo de la "dirección efectiva".Debido a que la memoria se puede abordar de varias maneras diferentes (ver ejemplos a continuación), a LEA
veces se usa para agregar o multiplicar registros juntos sin usar una instrucción explícita ADD
o MUL
(o equivalente).
Como todos muestran ejemplos en la sintaxis de Intel, aquí hay algunos en la sintaxis de 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
un [disp32]
modo de direccionamiento absoluto . Usar en su mov $label, %eax
lugar. Sí, funciona, pero es menos eficiente (código de máquina más grande y se ejecuta en menos unidades de ejecución). Como menciona AT&T, ¿ usa LEA en valores que no son direcciones / punteros? usa AT&T, y mi respuesta allí tiene algunos otros ejemplos de AT&T.
Vamos a entender esto con un ejemplo.
mov eax, [ebx] y
lea eax, [ebx] Suponga que el valor en ebx es 0x400000. Luego, mov irá a la dirección 0x400000 y copiará 4 bytes de datos para registrarlos en eax. Mientras que lea copiará la dirección 0x400000 en eax. Entonces, después de la ejecución de cada instrucción, el valor de eax en cada caso será (suponiendo que la memoria 0x400000 contiene es 30).
eax = 30 (en caso de mov) eax = 0x400000 (en caso de lea) Para la definición mov, copie los datos de rm32 al destino (mov dest rm32) y lea (dirección efectiva de carga) copiará la dirección al destino (mov dest rm32 )
MOV puede hacer lo mismo que LEA [etiqueta], pero la instrucción MOV contiene la dirección efectiva dentro de la instrucción como una constante inmediata (calculada de antemano por el ensamblador). LEA usa PC-relative para calcular la dirección efectiva durante la ejecución de la instrucción.
lea [label
es un desperdicio de bytes sin sentido frente a un más compacto mov
, por lo que debe especificar las condiciones de las que habla. Además, para algunos ensambladores [label]
no es la sintaxis correcta para un modo de direccionamiento relativo a RIP. Pero sí, eso es exacto. Cómo cargar la dirección de la función o etiqueta en el registro en GNU Assembler se explica con más detalle.
La diferencia es sutil pero importante. La instrucción MOV es un 'MOVe' efectivamente una copia de la dirección que representa la etiqueta TABLE-ADDR. La instrucción LEA es una 'Dirección efectiva de carga' que es una instrucción indirecta, lo que significa que TABLE-ADDR apunta a una ubicación de memoria en la que se encuentra la dirección para cargar.
Efectivamente, usar LEA es equivalente a usar punteros en lenguajes como C, como tal, es una instrucción poderosa.