2009-11-01 3 views

ответ

51

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

lea ebx, [ebx+eax*8] 

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

445

Из «дзен» Ассамблеи по Абрашем:

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

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

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

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

+33

@AbidRahmanK несколько примеров: 'LEA EAX, [EAX + EBX + 1234567]' вычисляет сумму 'EAX',' EBX' и '1234567' (это три операнда). 'LEA EAX, [EBX + ECX]' вычисляет 'EBX + ECX' _without_ переопределение либо с результатом. Третья вещь «LEA» используется (не указана Фрэнком) - это умножение на константу_ (на два, три, пять или девять), если вы используете ее как 'LEA EAX, [EBX + N * EBX]' ('N 'может быть 1,2,4,8). Другая usecase удобна в циклах: разница между 'LEA EAX, [EAX + 1]' и 'INC EAX' заключается в том, что последний изменяет' EFLAGS', но первый не делает; это сохраняет состояние «CMP» –

+0

@FrankH.Я все еще не понимаю, так что он загружает указатель на другое место? –

+4

@ ripDaddy69 да, вроде - если под «нагрузкой» вы подразумеваете «выполняет расчет адреса/арифметику указателя». Он не делает доступ к памяти_ (т. Е. Не «разыгрывает» указатель, так как он будет вызываться в терминах программирования C). –

61

Возможно, что-то еще в инструкции 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 
+9

+1 за трюк. Но я хотел бы задать вопрос (может быть, глупый), почему бы не размножать его с тремя, как этот 'LEA EAX, [EAX * 3]'? –

+10

@Abid Rahman K: Нет такой команды инструкций unde x86 CPU. –

+34

@AbidRahmanK, несмотря на синтаксис intel asm, делает его похожим на умножение, инструкция lea может кодировать только операции смены. Код операции имеет 2 бита для описания сдвига, поэтому вы можете умножать только на 1,2,4 или 8. – ithkuil

614

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

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.

+70

Не было бы более чистым продлить инструкцию «mov» и оторваться от скобок? 'MOV EDX, EBX + 8 * EAX + 4' –

+7

@imacake. Заменяя LEA специализированным MOV, вы сохраняете синтаксис чистым: [] скобки всегда эквивалентны разыменованию указателя в C. Без скобок вы всегда имеете дело с самого указателя. –

+0

@Natan Эй ASM никогда не бывает тривиальным! : D – imacake

68

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

LEA Rt, [Rs1 + а * Rs2 + Ь] => Rt = Rs1 + а * Rs2 + б

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

+2

И что арифметика выполняется аппаратным обеспечением расчета адресов. –

+16

@BenVoigt Я говорил это, потому что я старый процессор :-) Традиционно, процессоры x86 действительно использовали адресационные единицы для этого, согласились. Но «разделение» в наши дни стало очень размытым. Некоторые процессоры больше не имеют _dedicated_ AGU, другие выбрали не выполнять 'LEA 'в AGU, но в обычных ALU с целыми числами. В эти дни нужно очень внимательно прочитать спецификации процессора, чтобы выяснить,« где работает »... –

+0

@FrankH: процессоры нестандартного типа обычно запускают LEA на ALU, в то время как некоторые процессоры в порядке (например, Atom) иногда запускают его на AGU (becaus e они не могут быть заняты обработкой доступа к памяти). –

16

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 

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

81

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

7

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

+0

Не обязательно на современном x86. Большинство режимов адресации имеют одинаковую стоимость с некоторыми оговорками. Поэтому '[esi]' редко дешевле, чем '' esi + 4200 ''и редко бывает дешевле, чем' [esi + ecx * 8 + 4200] '. – BeeOnRope

+0

@BeeOnRope '[esi]' не дешевле, чем '[esi + ecx * 8 + 4200]'. Но зачем сравнивать? Они не эквивалентны. Если вы хотите, чтобы первое обозначало ту же ячейку памяти, что и последнее, вам нужны дополнительные инструкции: вам нужно добавить к значению 'esi' значение' ecx', умноженное на 8.Uh oh, умножение будет собирать ваши флагов CPU! Затем вы должны добавить 4200. Эти дополнительные инструкции добавляют к размеру кода (занимая место в кеше команд, циклы для извлечения). – Kaz

+1

@ Kaz - Я думаю, что вам не хватало моей точки (иначе я пропустил точку OP). Я понимаю, что OP говорит, что если вы собираетесь использовать что-то вроде '[esi + 4200]' несколько раз в последовательности инструкций, тогда лучше сначала загрузить эффективный адрес в регистр и использовать его. Например, вместо написания 'add eax, [esi + 4200]; добавьте ebx, [esi + 4200]; добавьте ecx, [esi + 4200] ', вы должны предпочесть' lea edi, [esi + 4200]; add eax, [edi]; добавить ebx, [edi]; add ecx, [edi] ', что редко бывает быстрее. По крайней мере, это простая интерпретация этого ответа. – BeeOnRope

-6

, потому что вместо того, чтобы вы написать код

mov dx,offset something 

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

lea dx,something 
+0

Помогите объяснить разницу? 'mov dx, offset something' полностью действует, так как адрес' something' известен во время Linking. – Gunner

+0

Если вы не пишете x86-64 позиционно-независимый код (с RIP-относительным LEA), вы не должны использовать LEA, получая адреса статических данных в регистры. 'mov dx, offset something' быстрее и меньше байтов кода. –

6

Вот пример.

// 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 для указанной строки кода.

11

В соответствии с существующими ответами 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.

+2

«Отдельная часть LEA ALU» в большинстве случаев неверна. Современные процессоры выполняют 'lea' на одном или нескольких из ALU, которые выполняют другие арифметические команды (но, как правило, меньше, чем другие арифметические). Например, упомянутый CPU Haswell может выполнять 'add' или' sub' или большинство других основных арифметических операций для _four different_ ALU, но может выполнять только 'lea' на одном (сложный' lea') или два (простой 'lea') , Что еще более важно, эти два ALU с lea-способностью - это просто два из четырех, которые могут выполнять другие команды, поэтому нет возможности параллелизма, как заявлено. – BeeOnRope

+0

В статье, которую вы связали (правильно), показано, что LEA находится на том же порту, что и целочисленный ALU (add/sub/boolean), и целочисленный блок MUL в Haswell. (И векторные ALU, включая FP ADD/MUL/FMA). Простое единственное устройство LEA находится на порту 5, который также запускает ADD/SUB/что угодно, а также перетаскивает вектор и другие вещи. Единственная причина, по которой я не занимаюсь, заключается в том, что вы указываете на использование RIP-относительного LEA (только для x86-64). –

5

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

MOV передает данные между операндами, но LEA только вычислением

+0

LEA явно перемещает данные; он имеет операнд назначения. LEA не всегда вычисляет; он вычисляет, вычисляет ли эффективный адрес, выраженный в исходном операнде. LEA EAX, GLOBALVAR не вычисляет; он просто перемещает адрес GLOBALVAR в EAX. – Kaz

+0

@Kaz благодарит вас за отзыв. мой источник был «LEA (эффективный адрес загрузки), по сути, является арифметической инструкцией - он не выполняет никакого фактического доступа к памяти, но обычно используется для вычисления адресов (хотя вы можете рассчитать целые числа общего назначения с ним)». форма [Eldad-Eilam book] (https://www.amazon.com/Reversing-Secrets-Engineering-Eldad-Eilam/dp/0764574817) стр. 149 –

18

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

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

MOV EAX, [ESP+4]

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

LEA EAX, [EBX*8]

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

+0

Простите всех. @ big.heart одурачил меня, давая ответ на это три часа назад, заставляя его появляться как «новый» в моем вопросе о сборе в Ассамблее. –

+1

Почему синтаксис использует скобки, когда он не выполняет адресацию памяти? – golopot

+1

@ q4w56 Это одна из тех вещей, где ответ: «Вот как вы это делаете». Я считаю, что это одна из причин того, что людям так сложно понять, что делает «LEA». –

5

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

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

Смежные вопросы