¿Cuál es la diferencia entre MOV y LEA?


139

Me gustaría saber cuál es la diferencia entre estas instrucciones:

MOV AX, [TABLE-ADDR]

y

LEA AX, [TABLE-ADDR]


8
gracias nick. En primer lugar, no habría encontrado una respuesta a esta pregunta al mirar ese enlace. Aquí estaba buscando información específica, la discusión en el enlace que proporcionó es de naturaleza más general.
naveen

3
Hace años que voté por el duplicado de @ Nick, pero acabo de hacerlo. Reflexionando, estaba demasiado apresurado y ahora con naveen que a) la otra pregunta no responde "cuál es la diferencia" yb) esta es una pregunta útil. Disculpas a naveen por mi error - si tan solo pudiera deshacer vtc ...
Ruben Bartelink


Relacionado: ¿ Usar LEA en valores que no son direcciones / punteros? habla sobre otros usos de LEA, para matemática arbitraria.
Peter Cordes

Respuestas:


169
  • LEA significa dirección efectiva de carga
  • MOV significa valor de carga

En resumen, LEAcarga un puntero al elemento que está direccionando, mientras que MOV carga el valor real en esa dirección.

El propósito de LEAes 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.


66
+1 gracias por la explicación clara, me ayudó a responder otra pregunta.
legends2k

Me confunde que lea tiene "cargar" en el nombre y la gente dice que "carga" una dirección calculada en un registro, porque todas las entradas para calcular la ubicación de la memoria son valores o registros inmediatos. AFAICT lea solo realiza un cálculo, no carga nada, ¿dónde cargar significa tocar la memoria?
Joseph Garvin

2
@josephGarvin IIRC el término fetch se aplicaría a ese aspecto; Cargar es solo cómo reemplaza el valor en un registro con algo desde cero. Por ejemplo 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.
Ruben Bartelink


45

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 varpara obtener un movimiento inmediato en lugar de una carga.


3
solo en sintaxis NASM. En la sintaxis MASM, mov eax, vares una carga, igual que mov eax, [var], y debe usar mov eax, OFFSET varpara usar una etiqueta como una constante inmediata.
Peter Cordes

1
Claro, simple, y demuestra lo que estaba tratando de confirmar. Gracias.
JayArby el

1
Tenga en cuenta que en todos estos ejemplos, leaes la peor opción, excepto en el modo de 64 bits para el direccionamiento relativo a RIP. mov r32, imm32Se 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 .
Peter Cordes

29

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.


11

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


Supongo, con la excepción de que en el ensamblador GNU no es cierto cuando se trata de etiquetas en el segmento .bss. AFAIR realmente no leal TextLabel, LabelFromBssSegmentpuedes cuando tienes algo. como .bss .lcomm LabelFromBssSegment, 4, tendrías que hacerlo movl $TextLabel, LabelFromBssSegment, ¿no?
JSmyth

@JSmyth: Eso es solo porque learequiere un destino de registro, pero movpuede tener un imm32origen y un destino de memoria. Por supuesto, esta limitación no es específica del ensamblador GNU.
Peter Cordes

1
Además, esta respuesta es básicamente incorrecta porque la pregunta es sobre MOV AX, [TABLE-ADDR], que es una carga. Entonces hay una gran diferencia. La instrucción equivalente esmov ax, OFFSET table_addr
Peter Cordes

10

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_addry 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

leala versión también funciona bien si table_addres una variable local, por ejemplo

some_procedure proc

local table_addr[64]:word

lea ax,table_addr

muchas gracias, es solo que no puedo marcar más de una como respuesta :(
naveen

55
La diferencia entre las instrucciones x86 MOV y LEA definitivamente NO depende del ensamblador.
IJ Kennedy

4

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 leaoperaciones tratan el uso de paréntesis diferente de cómo lo movhace.

Piense en C. Digamos que tengo una variedad de longeso 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), %r9y 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 %rdiy 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 arrayconvierte %rdiy se iconvierte %rsiy se xconvierte %rbp. El 8es 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 movqcorresponde a la desreferenciación, el uso de leaqaquí 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 leaqcambia 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 %rdiy 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 leaqymovq es que movqhace una desreferencia, y leaqno lo hace. De hecho, para escribir la leaqdescripción, básicamente copié + pegué la descripción movqy luego eliminé "Buscar el valor en esta ubicación".

Para resumir: movqvs. leaqes 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 leaqellos no lo hacen y son una sintaxis puramente conveniente.


[1] He dicho que cuando arrayes 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 papunta a valores de tipo T. La expresión p + ihace no media "en la posición pmás ibytes". En cambio, la expresión en p + i realidad significa "la posición en pmá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 leahace 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, cccon-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

1
Sí, buena explicación, con más detalle que las otras respuestas, y sí, el &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), %eaxsolo 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.
Peter Cordes

"obtener el valor en %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.
ecm

@PeterCordes Gracias! He agregado el punto de que es un caso especial a la respuesta.
Quelklef

1
@ecm Buen punto; No me di cuenta de eso. Lo he cambiado ahora, gracias! :)
Quelklef

Para su información, la redacción más corta que soluciona el problema que ecm señaló incluye: "el valor de %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.
Peter Cordes

2

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

Sí, leaes 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.
Peter Cordes

1

Como se indica en las otras respuestas:

  • MOVse agarra los datos a la dirección dentro de los corchetes y lugar que los datos en el destino operando.
  • LEArealizará 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 LEAestá 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 LEAveces se usa para agregar o multiplicar registros juntos sin usar una instrucción explícita ADDo 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 */

Nunca quieres lea label, %eaxun [disp32]modo de direccionamiento absoluto . Usar en su mov $label, %eaxlugar. 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.
Peter Cordes

1

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 )


0

LEA (Dirección efectiva de carga) es una instrucción shift-and-add. Se agregó a 8086 porque el hardware está allí para decodificar y calcular los modos de dirección.


0

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.


Eso solo es cierto para el modo de 64 bits (donde el direccionamiento relativo a la PC era nuevo); en otros modos lea [labeles 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.
Peter Cordes

-1

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.


77
Creo que esta respuesta es confusa en el mejor de los casos. "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". En realidad, LEA cargará la dirección, no el contenido de la dirección. Creo que en realidad el interlocutor debe asegurarse de que MOV y LEA pueden superponerse y hacer exactamente lo mismo, en algunas circunstancias
Bill Forster, el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.