Какова цель инструкции LEA? - PullRequest
609 голосов
/ 01 ноября 2009

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

Ответы [ 14 ]

730 голосов
/ 03 ноября 2009

Как уже отмечали другие, LEA (эффективный адрес загрузки) часто используется в качестве «уловки» для выполнения определенных вычислений, но это не является его основной целью. Набор команд x86 был разработан для поддержки языков высокого уровня, таких как Pascal и C, где обычно используются массивы & mdash; особенно массивы int или небольшие структуры & mdash ;. Рассмотрим, например, структуру, представляющую (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 байт. Теперь рассмотрим то же выражение, которое используется с оператором "address of" &:

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

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

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

, который загрузит адрес в ESI.

519 голосов
/ 02 ноября 2009

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

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).

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

96 голосов
/ 09 октября 2012

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

82 голосов
/ 16 августа 2011

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

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

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

70 голосов
/ 02 ноября 2009

Может быть, просто еще одна вещь в инструкции 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
56 голосов
/ 02 ноября 2009

lea - это сокращение от «эффективный адрес загрузки». Он загружает адрес ссылки на местоположение исходного операнда в целевой операнд. Например, вы можете использовать его для:

lea ebx, [ebx+eax*8]

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

22 голосов
/ 06 мая 2015

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

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

MOV EAX, [ESP+4]

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

LEA EAX, [EBX*8]

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

18 голосов
/ 29 апреля 2012

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

Обратите внимание, что если забыть о «смещении» мира, в DI будет добавлено содержимое местоположения [BP + 8], а не значение 8. К сожалению.

10 голосов
/ 31 июля 2014

Как уже упоминалось в существующих ответах, LEA обладает преимуществами выполнения арифметики адресации памяти без доступа к памяти, сохранения арифметического результата в другом регистре вместо простой формы инструкции добавления. Реальное преимущество в производительности заключается в том, что современный процессор имеет отдельный блок 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.

7 голосов
/ 26 апреля 2017

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

То есть, если у нас есть данные, перемещающиеся следующим образом:

MOV EAX, <MEM-OPERAND>

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

Если мы заменим MOV на LEA, то адрес ячейки памяти будет точно таким же образом вычислен с помощью выражения адресации <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

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

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

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

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

$ 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 исключать эту возможность, хотя бы потому, что есть более короткая альтернатива; он просто комбинируется ортогонально с доступными режимами адресации.

...