Pregunta ¿Cuál es el propósito de la instrucción LEA?


Para mí, parece un MOV funky. ¿Cuál es su propósito y cuándo debería usarlo?


531
2017-11-01 20:57


origen


Respuestas:


Como han señalado otros, LEA (dirección efectiva de carga) a menudo se usa como un "truco" para hacer ciertos cálculos, pero ese no es su objetivo principal. El conjunto de instrucciones x86 fue diseñado para admitir lenguajes de alto nivel como Pascal y C, donde las matrices, especialmente las matrices de ints o pequeñas estructuras, son comunes. Considere, por ejemplo, una estructura que representa coordenadas (x, y):

struct Point
{
     int xcoord;
     int ycoord;
};

Ahora imagine una afirmación como:

int y = points[i].ycoord;

dónde points[] es una matriz de Point. Suponiendo que la base de la matriz ya está en EBXy variable i es en EAXy xcoord y ycoord son cada uno de 32 bits (por lo ycoord está en offset 4 bytes en la estructura), esta declaración se puede compilar para:

MOV EDX, [EBX + 8*EAX + 4]    ; right side is "effective address"

que aterrizará y en EDX. El factor de escala de 8 es porque cada Point tiene 8 bytes de tamaño. Ahora considere la misma expresión utilizada con el operador "dirección de" y:

int *p = &points[i].ycoord;

En este caso, no quieres el valor de ycoord, pero su dirección. Ahí es donde LEA (dirección efectiva de carga) entra. En lugar de un MOV, el compilador puede generar

LEA ESI, [EBX + 8*EAX + 4]

que cargará la dirección en ESI.


651
2017-11-03 06:25



Desde el "Zen de la Asamblea" por Abrash:

LEA, la única instrucción que realiza cálculos de direccionamiento de memoria pero en realidad no aborda la memoria. LEA acepta un operando de direccionamiento de memoria estándar, pero no hace nada más que almacenar el desplazamiento de memoria calculado en el registro especificado, que puede ser cualquier registro de propósito general.

¿Qué nos da eso? Dos cosas que ADD no proporciona:

  1. la capacidad de realizar adiciones con dos o tres operandos, y
  2. la capacidad de almacenar el resultado en alguna registro; no solo uno de los operandos fuente.

Y LEA no altera las banderas

Ejemplos

  • LEA EAX, [ EAX + EBX + 1234567 ] calcula EAX + EBX + 1234567 (eso es tres operandos)
  • LEA EAX, [ EBX + ECX ] calcula EBX + ECX sin anular tampoco con el resultado.
  • multiplicación por constante (por dos, tres, cinco o nueve), si lo usa como LEA EAX, [ EBX + N * EBX ] (N puede ser 1,2,4,8).

Otro uso es útil en bucles: la diferencia entre LEA EAX, [ EAX + 1 ] y INC EAX es que esto último cambia EFLAGS pero el primero no; esto conserva CMP estado.


460
2017-11-01 21:03



Otra característica importante de la LEA La instrucción es que no altera los códigos de condición tales como CF y ZF, al calcular la dirección mediante instrucciones aritméticas como ADD o MUL hace. Esta característica disminuye el nivel de dependencia entre las instrucciones y, por lo tanto, deja espacio para una mayor optimización por parte del compilador o planificador de hardware.


86
2017-10-09 17:35



A pesar de todas las explicaciones, LEA es una operación aritmética:

LEA Rt, [Rs1+a*Rs2+b] =>  Rt = Rs1 + a*Rs2 + b

Es solo que su nombre es extremadamente estúpido para una operación shift + add. La razón de esto ya se explicó en las respuestas mejor valoradas (es decir, se diseñó para mapear directamente las referencias de memoria de alto nivel).


70
2017-08-15 21:43



Tal vez solo otra cosa sobre la instrucción LEA. También puede usar LEA para registros rápidos de multiplicación por 3, 5 o 9.

LEA EAX, [EAX * 2 + EAX]   ;EAX = EAX * 3
LEA EAX, [EAX * 4 + EAX]   ;EAX = EAX * 5
LEA EAX, [EAX * 8 + EAX]   ;EAX = EAX * 9

64
2017-11-02 10:16



lea es una abreviatura de "dirección efectiva de carga". Carga la dirección de la referencia de ubicación por el operando de origen al operando de destino. Por ejemplo, podrías usarlo para:

lea ebx, [ebx+eax*8]

para mover ebx puntero eax elementos más (en una matriz de elementos de 64 bits) con una sola instrucción. Básicamente, se beneficia de los modos de direccionamiento complejos soportados por la arquitectura x86 para manipular punteros de manera eficiente.


51
2017-11-01 21:00



La razón más grande que usas LEA sobre un MOV es si necesita realizar aritmética en los registros que está utilizando para calcular la dirección. De hecho, puede realizar lo que equivale a la aritmética del puntero en varios de los registros en combinación efectiva para "libre".

Lo que es realmente confuso es que normalmente escribes un LEA solo como un MOV pero en realidad no está eliminando la referencia de la memoria. En otras palabras:

MOV EAX, [ESP+4]

Esto moverá el contenido de lo que ESP+4 apunta a EAX.

LEA EAX, [EBX*8]

Esto moverá la dirección efectiva EBX * 8 en EAX, no lo que se encuentra en esa ubicación. Como puede ver, también, es posible multiplicar por factores de dos (escalado) mientras MOV está limitado a sumar / restar


19
2018-05-05 22:22



El 8086 tiene una gran familia de instrucciones que aceptan un operando de registro y una dirección efectiva, realizan algunos cálculos para calcular la parte compensada de esa dirección efectiva y realizan alguna operación que involucra el registro y la memoria a la que hace referencia la dirección calculada. Era bastante simple tener una de las instrucciones en esa familia que se comportan como arriba, excepto por omitir esa operación de memoria real. Esto, las instrucciones:

mov ax,[bx+si+5]
lea ax,[bx+si+5]

fueron implementados de manera casi idéntica internamente. La diferencia es un paso omitido. Ambas instrucciones funcionan de la siguiente manera:

temp = fetched immediate operand (5)
temp += bx
temp += si
address_out = temp  (skipped for LEA)
trigger 16-bit read  (skipped for LEA)
temp = data_in  (skipped for LEA)
ax = temp

En cuanto a por qué Intel pensó que valía la pena incluir esta instrucción, no estoy del todo seguro, pero el hecho de que era barato de implementar habría sido un factor importante. Otro factor habría sido el hecho de que el ensamblador de Intel permitía definir símbolos en relación con el registro BP. Si fnord se definió como un símbolo relativo a BP (por ejemplo, BP + 8), se podría decir:

mov ax,fnord  ; Equivalent to "mov ax,[BP+8]"

Si uno quisiera usar algo como stosw para almacenar datos en una dirección relativa a BP, ser capaz de decir

mov ax,0 ; Data to store
mov cx,16 ; Number of words
lea di,fnord
rep movs fnord  ; Address is ignored EXCEPT to note that it's an SS-relative word ptr

fue más conveniente que:

mov ax,0 ; Data to store
mov cx,16 ; Number of words
mov di,bp
add di,offset fnord (i.e. 8)
rep movs fnord  ; Address is ignored EXCEPT to note that it's an SS-relative word ptr

Tenga en cuenta que si se olvida el "desplazamiento" mundial, el contenido de la ubicación [BP + 8], en lugar del valor 8, se agregará a DI. Oops.


16
2018-04-29 19:46



Como se mencionaron las respuestas existentes, LEA tiene la ventaja de realizar aritmética de direccionamiento de memoria sin tener acceso a la memoria, guardando el resultado aritmético en un registro diferente en lugar de la forma simple de agregar instrucción. El beneficio de rendimiento real subyacente es que el procesador moderno tiene una unidad LEA ALU separada y un puerto para la generación efectiva de direcciones (incluyendo LEA y otra dirección de referencia de memoria), esto significa que la operación aritmética en LEAy otra operación aritmética normal en ALU se podría hacer en paralelo en un núcleo.

Consulte este artículo de la arquitectura de Haswell para conocer algunos detalles sobre la unidad LEA: http://www.realworldtech.com/haswell-cpu/4/

Otro punto importante que no se menciona en otras respuestas es LEA REG, [MemoryAddress] la instrucción es PIC (código de posición independiente) que codifica la dirección relativa de la PC en esta instrucción para referenciar MemoryAddress. Esto es diferente de MOV REG, MemoryAddress que codifica la dirección virtual relativa y requiere reubicación / parcheo en sistemas operativos modernos (como ASLR es una característica común). Asi que LEA se puede usar para convertir dicho PIC no a PIC.


10
2017-07-31 03:41



La instrucción LEA se puede utilizar para evitar cálculos de direcciones efectivas por parte de la CPU. Si una dirección se usa repetidamente, es más efectivo almacenarla en un registro en lugar de calcular la dirección efectiva cada vez que se usa.


7
2018-06-04 20:32



Aquí hay un ejemplo.

// compute parity of permutation from lexicographic index
int parity (int p)
{
  assert (p >= 0);
  int r = p, k = 1, d = 2;
  while (p >= k) {
    p /= d;
    d += (k << 2) + 6; // only one lea instruction
    k += 2;
    r ^= p;
  }
  return r & 1;
}

Con -O (optimizar) como opción de compilación, gcc encontrará la instrucción lea para la línea de código indicada.


6
2018-05-16 19:40