Какова цель инструкции LEA?

Для меня это просто похоже на фанки MOV. Какова его цель и когда я должен ее использовать?

Ответ 1

Как указывали другие, LEA (эффективный адрес нагрузки) часто используется как "трюк" для выполнения определенных вычислений, но это не его основная цель. Набор инструкций x86 был разработан для поддержки языков высокого уровня, таких как Pascal и C, где обычно используются массивы, особенно массивы int или small structs — Рассмотрим, например, структуру, представляющую (x, y) координаты:

struct Point
{
     int xcoord;
     int ycoord;
};

Теперь представьте себе выражение вроде:

int y = points[i].ycoord;

где points[] - массив из Point. Предполагая, что база массива уже находится в EBX, а переменная i находится в EAX, а xcoord и ycoord - 32 бита (поэтому ycoord находится в смещении 4 байта в структуре), этот оператор может быть скомпилирован для:

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

который приземлится y в EDX. Масштабный коэффициент 8 - это то, что каждый Point имеет размер 8 байтов. Теперь рассмотрим одно и то же выражение, используемое с "адресом оператора &:

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

В этом случае вам не нужно значение ycoord, но его адрес. Это где LEA (загружаемый эффективный адрес). Вместо MOV компилятор может сгенерировать

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

который будет загружать адрес в ESI.

Ответ 2

Из "Дзен Ассамблеи" Абраша:

LEA, единственная команда, которая выполняет вычисления адресации памяти, но фактически не адресует память. LEA принимает стандартный операнд адресации памяти, но не делает ничего, кроме хранения вычисленного смещения памяти в указанном регистре, который может быть любым регистром общего назначения.

Что это дает нам? Две вещи, которые ADD не предоставляет:

  1. возможность выполнения добавления с двумя или тремя операндами и
  2. возможность сохранять результат в любом регистре; а не только один из исходных операндов.

И LEA не изменяет флаги.

Примеры

  • LEA EAX, [ EAX + EBX + 1234567 ] вычисляет EAX + EBX + 1234567 (три операнда)
  • LEA EAX, [ EBX + ECX ] вычисляет EBX + ECX без переопределения результата.
  • умножение на константу (на два, три, пять или девять), если вы используете его как LEA EAX, [ EBX + N * EBX ] (N может быть 1,2,4,8).

Другая usecase удобна в циклах: разница между LEA EAX, [ EAX + 1 ] и INC EAX заключается в том, что последняя изменяет EFLAGS но первая не работает; это сохраняет состояние CMP.

Ответ 3

Еще одна важная особенность инструкции LEA заключается в том, что она не изменяет кодов условий, таких как CF и ZF, при вычислении адреса по арифметическим инструкциям, таким как ADD или MUL. Эта функция снижает уровень зависимости между инструкциями и, таким образом, дает возможность для дальнейшей оптимизации компилятором или аппаратным планировщиком.

Ответ 4

Несмотря на все объяснения, LEA является арифметической операцией:

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

Это просто, что его имя крайне глупо для операции shift + add. Причина этого уже объяснялась в самых рейтинговых ответах (т.е. Была предназначена для непосредственного отображения ссылок на память высокого уровня).

Ответ 5

Возможно, еще одна вещь в инструкции LEA. Вы также можете использовать LEA для быстрого умножения регистров на 3, 5 или 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

Ответ 6

lea является аббревиатурой от "эффективного адреса нагрузки". Он загружает адрес ссылки местоположения исходным операндом в операнд назначения. Например, вы можете использовать его для:

lea ebx, [ebx+eax*8]

перемещать указатели ebx указателя eax далее (в массиве 64-разрядных/элементов) с помощью одной команды. В основном, вы используете сложные режимы адресации, поддерживаемые архитектурой x86, для эффективного управления указателями.

Ответ 7

Самая большая причина, по которой вы используете LEA над MOV, - это если вам нужно выполнить арифметику в регистрах, которые вы используете для вычисления адреса. Эффективно, вы можете выполнить то, что составляет арифметику указателя на нескольких регистрах в комбинации эффективно для "бесплатного".

Что действительно запутывает, так это то, что вы обычно пишете LEA так же, как и MOV, но на самом деле вы не разыгрываете память. Другими словами:

MOV EAX, [ESP+4]

Это приведет к перемещению содержимого того, что ESP+4 указывает на EAX.

LEA EAX, [EBX*8]

Это переместит эффективный адрес EBX * 8 в EAX, а не то, что найдено в этом месте. Как вы можете видеть, также можно умножить на два фактора (масштабирование), а MOV ограничено добавлением/вычитанием.

Ответ 8

8086 имеет большое семейство инструкций, которые принимают операнд регистров и эффективный адрес, выполняют некоторые вычисления для вычисления смещенной части этого эффективного адреса и выполняют некоторую операцию, связанную с регистром и памятью, на которую ссылается вычисленный адрес. Было довольно просто, чтобы одна из инструкций в этом семействе вела себя так, как указано выше, за исключением пропусков этой реальной операции памяти. Это, инструкции:

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

были реализованы почти идентично внутренне. Разница - это пропущенный шаг. Обе инструкции работают примерно так:

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

Что касается того, почему Intel считала, что эта инструкция стоит включать, я не совсем уверен, но тот факт, что она была дешевой для реализации, была бы большим фактором. Другим фактором был бы тот факт, что ассемблер Intel разрешил определять символы относительно регистра BP. Если fnord был определен как относительный символ BP (например, BP + 8), можно было бы сказать:

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

Если кто-то хотел использовать что-то вроде stosw для хранения данных на относительном адресе BP, будучи в состоянии сказать

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 an SS-relative word ptr

был более удобным, чем:

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 an SS-relative word ptr

Обратите внимание, что забвение "смещения" в мире приведет к добавлению содержимого места [BP + 8], а не значение 8, в DI. К сожалению.

Ответ 9

Как уже упоминалось в существующих ответах, LEA имеет преимущества выполнения арифметики адресации памяти без доступа к памяти, сохраняя результат арифметики в другом регистре вместо простой формы команды add. Реальное базовое преимущество в производительности заключается в том, что современный процессор имеет отдельный блок LEA ALU и порт для эффективной генерации адресов (включая LEA и другой адрес ссылки на память), это означает, что арифметическая операция в LEA и другая нормальная арифметическая операция в ALU могут выполняться параллельно в одном ядре.

Ознакомьтесь с этой статьей архитектуры Haswell для получения подробной информации о модуле LEA: http://www.realworldtech.com/haswell-cpu/4/

Другим важным моментом, который не упоминается в других ответах, является инструкция LEA REG, [MemoryAddress] - это PIC (независимый от положения код), который кодирует относительный адрес ПК в этой инструкции для ссылки MemoryAddress. Это отличается от MOV REG, MemoryAddress, который кодирует относительный виртуальный адрес и требует перемещения/исправления в современных операционных системах (например, ASLR является общей функцией). Таким образом, LEA может использоваться для преобразования такого не PIC в PIC.

Ответ 10

Инструкция LEA может использоваться для избежания трудоемких вычислений эффективных адресов CPU. Если адрес используется повторно, более эффективно хранить его в регистре, а не вычислять эффективный адрес каждый раз, когда он используется.

Ответ 11

Инструкция LEA (Load Effective Address) - это способ получения адреса, который возникает из любого режима адресации памяти процессора Intel.

То есть, если у нас есть данные, двигайтесь так:

MOV EAX, <MEM-OPERAND>

он перемещает содержимое назначенной ячейки памяти в целевой регистр.

Если мы заменим MOV на LEA, адрес адреса памяти будет точно таким же образом <MEM-OPERAND> выражения адресации <MEM-OPERAND>. Но вместо содержимого ячейки памяти мы попадаем в место назначения.

LEA не является конкретной арифметической инструкцией; это способ перехвата эффективного адреса, возникающего из любого из режимов адресации памяти процессора.

Например, мы можем использовать LEA только на простом прямом адресе. Арифметика вообще не задействована:

MOV EAX, GLOBALVAR   ; fetch the value of GLOBALVAR into EAX
LEA EAX, GLOBALVAR   ; fetch the address of GLOBALVAR into EAX.

Это действительно; мы можем протестировать его в командной строке Linux:

$ as
LEA 0, %eax
$ objdump -d a.out

a.out:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <.text>:
   0:   8d 04 25 00 00 00 00    lea    0x0,%eax

Здесь нет добавления масштабированного значения и никакого смещения. Zero перемещается в EAX. Мы могли бы сделать это, используя MOV с немедленным операндом.

Именно по этой причине люди, которые считают, что скобки в LEA излишни, сильно ошибаются; скобки не являются синтаксисом LEA а являются частью режима адресации.

LEA работает на аппаратном уровне. Сгенерированная команда кодирует фактический режим адресации, и процессор выполняет ее до точки вычисления адреса. Затем он перемещает этот адрес в пункт назначения вместо генерации ссылки на память. (Так как вычисление адреса режима адресации в любой другой команде не влияет на флаги CPU, LEA не влияет на флаги CPU.)

Контраст с загрузкой значения с нуля адреса:

$ as
movl 0, %eax
$ objdump -d a.out | grep mov
   0:   8b 04 25 00 00 00 00    mov    0x0,%eax

Это очень похожее кодирование, понимаете? Только 8d LEA изменилось на 8b.

Конечно, это LEA кодирование длиннее, чем немедленное перемещение в EAX:

$ as
movl $0, %eax
$ objdump -d a.out | grep mov
   0:   b8 00 00 00 00          mov    $0x0,%eax

Для LEA нет причин исключать эту возможность, хотя бы потому, что существует более короткая альтернатива; он просто объединяется ортогональным способом с доступными режимами адресации.

Ответ 12

Вот пример.

// 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;
}

С -O (оптимизация) в качестве параметра компилятора gcc найдет инструкцию lea для указанной строки кода.

Ответ 13

LEA: просто инструкция "арифметика".

MOV передает данные между операндами, но lea просто вычисляет

Ответ 14

Кажется, что многие ответы уже завершены, я хотел бы добавить еще один пример кода для демонстрации того, как команды lea и move работают по-разному, когда они имеют одинаковый формат выражения.

Короче говоря, можно использовать как инструкции Le, так и инструкции MOV с круглыми скобками, включающими операнд src инструкций. Когда они заключены в (), выражение в () вычисляется таким же образом; однако две инструкции по-разному интерпретируют вычисленное значение в операнде src.

Независимо от того, используется ли выражение с lea или mov, значение src рассчитывается, как показано ниже.

D (Rb, Ri, S) => (Reg [Rb] +S * Reg [Ri] + D)

Однако, когда он используется с инструкцией mov, он пытается получить доступ к значению, указанному адресом, сгенерированным вышеприведенным выражением, и сохранить его в месте назначения.

В отличие от этого, когда инструкция lea выполняется с вышеуказанным выражением, она загружает сгенерированное значение в том виде, в котором оно находится, к месту назначения.

Приведенный ниже код выполняет инструкцию lea и инструкцию mov с одним и тем же параметром. Однако, чтобы уловить разницу, я добавил обработчик сигнала уровня пользователя, чтобы отследить ошибку сегментации, вызванную обращением к неправильному адресу в результате команды mov.

Пример кода

#define _GNU_SOURCE 1  /* To pick up REG_RIP */
#include <stdio.h> 
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>


uint32_t
register_handler (uint32_t event, void (*handler)(int, siginfo_t*, void*))
{
        uint32_t ret = 0;
        struct sigaction act;

        memset(&act, 0, sizeof(act));
        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;
        ret = sigaction(event, &act, NULL);
        return ret;
}

void
segfault_handler (int signum, siginfo_t *info, void *priv)
{
        ucontext_t *context = (ucontext_t *)(priv);
        uint64_t rip = (uint64_t)(context->uc_mcontext.gregs[REG_RIP]);
        uint64_t faulty_addr = (uint64_t)(info->si_addr);

        printf("inst at 0x%lx tries to access memory at %ld, but failed\n",
                rip,faulty_addr);
        exit(1);
}

int
main(void)
{
        int result_of_lea = 0;

        register_handler(SIGSEGV, segfault_handler);

        //initialize registers %eax = 1, %ebx = 2

        // the compiler will emit something like
           // mov $1, %eax
           // mov $2, %ebx
        // because of the input operands
        asm("lea 4(%%rbx, %%rax, 8), %%edx \t\n"
            :"=d" (result_of_lea)   // output in EDX
            : "a"(1), "b"(2)        // inputs in EAX and EBX
            : // no clobbers
         );

        //lea 4(rbx, rax, 8),%edx == lea (rbx + 8*rax + 4),%edx == lea(14),%edx
        printf("Result of lea instruction: %d\n", result_of_lea);

        asm volatile ("mov 4(%%rbx, %%rax, 8), %%edx"
                       :
                       : "a"(1), "b"(2)
                       : "edx"  // if it didn't segfault, it would write EDX
          );
}

Результат выполнения

Result of lea instruction: 14
inst at 0x4007b5 tries to access memory at 14, but failed

Ответ 15

Все обычные "расчетные" инструкции, такие как добавление умножения, исключение или установка флагов состояния, таких как ноль, знак. Если вы используете сложный адрес, AX xor:= mem[0x333 +BX + 8*CX] флаги устанавливаются в соответствии с операцией xor.

Теперь вы можете использовать адрес несколько раз. Загрузка таких адресов в реестр никогда не предназначена для установки флагов состояния, и, к счастью, это не так. Фраза "загрузить эффективный адрес" заставляет программиста осознать это. Отсюда и странное выражение.

Понятно, что когда процессор способен использовать сложный адрес для обработки своего контента, он может рассчитывать его для других целей. Действительно, его можно использовать для выполнения преобразования x <- 3*x+1 в одной инструкции. Это общее правило в программировании на ассемблере: используйте инструкции, однако это раскачивает вашу лодку. Единственное, что имеет значение, это то, полезно ли вам конкретное преобразование, воплощенное в инструкции.

Итог

MOV, X| T| AX'| R| BX|

и

LEA, AX'| [BX]

одинаково влияет на AX, но не на флаги состояния. (Это запись ciasdis.)

Ответ 16

потому что вместо этого вы пишете код

mov dx,offset something

вы можете просто написать

lea dx,something