Смотрите также Использование LEA для значений, которые не являются адресами / указателями? : LEA - это просто инструкция сдвига и добавления. Вероятно, он был добавлен в 8086, потому что аппаратное обеспечение уже существует для декодирования и вычисления режимов адресации, а не потому, что оно «предназначено» только для использования с адресами. Помните, что указатели - это просто целые числа в сборке.
Питер Кордес
Ответы:
798
Как уже отмечали другие, LEA (эффективный адрес загрузки) часто используется как «хитрость» для выполнения определенных вычислений, но это не является его основной целью. Набор команд x86 был разработан для поддержки языков высокого уровня, таких как Pascal и C, где массивы - особенно массивы целых или небольших структур - распространены. Рассмотрим, например, структуру, представляющую (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, компилятор может генерировать
Разве не было бы чище расширить movинструкцию и убрать скобки? MOV EDX, EBX + 8*EAX + 4
Натан Еллин
14
@imacake Заменив LEA специализированным MOV, вы сохраните синтаксис в чистоте: скобки [] всегда эквивалентны разыменованию указателя в C. Без скобок вы всегда имеете дело с самим указателем.
Натан Еллин
139
Выполнение математических операций в инструкции MOV (EBX + 8 * EAX + 4) недопустимо. LEA ESI, [EBX + 8 * EAX + 4] действительна, потому что это режим адресации, поддерживаемый x86. en.wikipedia.org/wiki/X86#Addressing_modes
Эрик
30
@JonathanDickinson LEA похож MOVна косвенный источник, за исключением того, что он только косвенный, а не MOV. На самом деле он не читает с вычисленного адреса, просто вычисляет его.
Хоббс
24
Эрик, комментарий к туру не точный. MOV eax, [ebx + 8 * ecx + 4] действителен. Однако MOV возвращает содержимое ячейки памяти, тогда как LEA возвращает адрес
Олорин,
562
Из «Дзен Собрания» Абраша:
LEAединственная инструкция, которая выполняет вычисления адресации памяти, но фактически не обращается к памяти. LEAпринимает стандартный операнд адресации памяти, но не делает ничего, кроме сохранения вычисленного смещения памяти в указанном регистре, который может быть любым регистром общего назначения.
Что это нам дает? Две вещи, которые ADDне обеспечивают:
способность выполнять сложение с двумя или тремя операндами, и
возможность сохранения результата в любом регистре; не только один из исходных операндов.
LEA EAX, [ EBX + ECX ]рассчитывает EBX + ECXбез переопределения либо с результатом.
умножение на константу (на два, три, пять или девять), если вы используете это как LEA EAX, [ EBX + N * EBX ](N может быть 1,2,4,8).
Другой вариант использования удобен в циклах: разница между LEA EAX, [ EAX + 1 ]и INC EAXзаключается в том, что последний меняется, EFLAGSа первый нет; это сохраняет CMPсостояние.
@AbidRahmanK несколько примеров: LEA EAX, [ EAX + EBX + 1234567 ]вычисляет сумму EAX, EBXи 1234567(это три операнда). LEA EAX, [ EBX + ECX ]рассчитывает EBX + ECXбез переопределения либо с результатом. Третье, для чего LEAиспользуется (не перечислено Фрэнком) умножение на константу (на два, три, пять или девять), если вы используете это как LEA EAX, [ EBX + N * EBX ]( Nможет быть 1,2,4,8). Другой вариант использования удобен в циклах: разница между LEA EAX, [ EAX + 1 ]и INC EAXзаключается в том, что последний меняется, EFLAGSа первый нет; это сохраняет CMPсостояние
FrankH.
@FrankH. Я до сих пор не понимаю, поэтому он загружает указатель куда-то еще?
6
@ ripDaddy69 да, вроде как - если под «нагрузкой» вы подразумеваете «выполняет вычисление адреса / арифметику указателя». Он не обращается к памяти (то есть не «разыменовывает» указатель, как его называют в терминах программирования на языке Си).
ФрэнкХ.
2
+1: Это ясно LEAпоказывает, какие виды «уловок» можно использовать для ... (см. «LEA (эффективный адрес загрузки) часто используется в качестве« уловки »для выполнения определенных вычислений» в популярном ответе IJ Кеннеди выше)
Асад Эбрахим
3
Существует большая разница между 2 операндами LEA, которые являются быстрыми, и 3 операндами LEA, которые являются медленными. В руководстве по оптимизации Intel говорится, что LEA с быстрым путем - это один цикл, а LEA с быстрым путем - три цикла. Кроме того, в Skylake есть два функциональных блока быстрого доступа (порты 1 и 5) и только один функциональный блок медленного пути (порт 1). Кодировка сборки / компиляции Правило 33 в руководстве даже предостерегает от использования 3 операндов LEA.
Олсонист
110
Еще одной важной особенностью LEAинструкции является то, что она не изменяет коды состояния, такие как CFи ZF, при вычислении адреса с помощью арифметических команд, таких как ADDили MULделает. Эта функция снижает уровень зависимости между инструкциями и тем самым освобождает место для дальнейшей оптимизации компилятором или аппаратным планировщиком.
Да, leaиногда полезно, чтобы компилятор (или человеческий кодер) выполнял математику, не забивая результат флага. Но leaне быстрее чем add. Большинство инструкций x86 пишут флаги. Высокопроизводительные реализации x86 должны переименовывать EFLAGS или иным образом избегать опасности записи после записи для нормального выполнения кода, поэтому инструкции, которые избегают флаговых записей, не лучше из-за этого. ( неполный флаг может создать проблемы, см. инструкцию INC против ADD 1: имеет ли это значение? )
Питер Кордес
2
@PeterCordes: Ненавижу поднимать это здесь, но - я один думаю, что этот новый тег [x86-lea] является избыточным и ненужным?
Майкл Петч
2
@MichaelPetch: Да, я думаю, что это слишком конкретно. Кажется, смущает новичка, который не понимает машинный язык, и что все (включая указатели) являются просто битами / байтами / целыми числами, поэтому есть много вопросов об этом с огромным количеством голосов. Но наличие тега для него подразумевает, что есть место для неограниченного числа будущих вопросов, когда на самом деле их около 2 или 3, которые не являются просто дубликатами. (что это такое? Как использовать его для умножения целых чисел? и как оно работает внутри AGU против ALU и с какой задержкой / пропускной способностью. И, возможно, это «намеченная» цель)
Питер Кордес,
@PeterCordes: Я согласен, и в любом случае все эти редактируемые посты в значительной степени дублируют некоторые из существующих вопросов, связанных с LEA. Вместо тэга любые дубликаты должны быть идентифицированы и помечены как imho.
Майкл Петч
1
@EvanCarroll: повесьте теги на все вопросы LEA, если вы еще не закончили. Как уже говорилось выше, мы считаем, что x86-lea слишком специфична для тега, и у нас не так много возможностей для вопросов, не повторяющихся в будущем. Я думаю, что было бы много работы, чтобы на самом деле выбрать «лучшие» вопросы и ответы в качестве цели дублирования для большинства из них, или же решить, какие из них получить моды для слияния.
Питер Кордес
93
Несмотря на все объяснения, LEA является арифметической операцией:
LEA Rt, [Rs1+a*Rs2+b] => Rt = Rs1 + a*Rs2 + b
Просто его название крайне глупо для операции shift + add. Причина этого уже была объяснена в самых рейтинговых ответах (т. Е. Она была разработана для непосредственного сопоставления высокоуровневых ссылок на память).
И что арифметика выполняется аппаратным обеспечением для вычисления адреса.
Бен Фойгт
30
@BenVoigt Раньше я это говорил, потому что я старый парень :-) Традиционно процессоры x86 для этого использовали единицы адресации, согласились. Но «разделение» стало очень размытым в наши дни. У некоторых процессоров больше нет выделенных AGU, другие решили не выполнять их LEAна AGU, а на обычных целочисленных ALU. В наши дни нужно очень внимательно прочитать спецификации процессора, чтобы выяснить, «где все работает» ...
ФрэнкХ.
2
@FrankH .: ЦП с ошибками обычно запускают LEA на ALU, в то время как некоторые ЦП (например, Atom) работают с AGU (потому что они не могут быть заняты обработкой доступа к памяти).
Питер Кордес
3
Нет, имя не глупое. LEAдает вам адрес, который возникает в любом режиме адресации, связанном с памятью. Это не операция сдвига и добавления.
Каз,
3
FWIW очень мало (если таковые имеются) текущих процессоров x86, которые выполняют операции на AGU. Большинство или все просто используют ALU, как и любой другой арифметический оператор.
BeeOnRope
77
Может быть, просто еще одна вещь о инструкции LEA. Вы также можете использовать LEA для быстрого умножения регистров на 3, 5 или 9.
+1 за трюк. Но я хотел бы задать вопрос (может быть, глупо), почему бы не умножить прямо на три, как это LEA EAX, [EAX*3]?
Абид Рахман К
13
@ Абид Рахман К. Нет таких инструкций как набор инструкций для процессора x86.
ГДж.
50
@AbidRahmanK, несмотря на то, что синтаксис intel asm выглядит как умножение, инструкция lea может кодировать только операции сдвига. Код операции имеет 2 бита для описания сдвига, поэтому вы можете умножить его только на 1,2,4 или 8.
ithkuil
6
@Koray Tugay: Вы можете использовать shlкоманду shift left как инструкцию для умножения регистров на 2,4,8,16 ... это быстрее и короче. Но для умножения на числа, отличающиеся степенью 2, мы обычно используем mulинструкцию, которая более претенциозна и медленнее.
ГДж.
8
@GJ. хотя такой кодировки нет, некоторые ассемблеры принимают это как ярлык, например, fasm. Так, например, lea eax,[eax*3]будет переводить в эквивалент lea eax,[eax+eax*2].
Руслан
59
leaэто сокращение от "эффективный адрес загрузки". Он загружает адрес ссылки на местоположение исходного операнда в целевой операнд. Например, вы можете использовать его для:
lea ebx, [ebx+eax*8]
перемещать элементы ebxуказателя eaxдальше (в 64-битном массиве / элементном массиве) с помощью одной инструкции. По сути, вы получаете преимущества от сложных режимов адресации, поддерживаемых архитектурой x86, для эффективного управления указателями.
Самая большая причина, по которой вы используете « LEAа», MOVзаключается в том, что вам нужно выполнить арифметику с регистрами, которые вы используете для вычисления адреса. По сути, вы можете выполнить то, что равнозначно арифметике указателей в нескольких регистрах в комбинации, для «бесплатно».
Что действительно сбивает с толку, так это то, что вы обычно пишете LEAкак a, MOVно на самом деле вы не разыменовываете память. Другими словами:
MOV EAX, [ESP+4]
Это переместит содержание того, на что ESP+4указывает EAX.
LEA EAX, [EBX*8]
Это переместит эффективный адрес EBX * 8в EAX, а не тот, который находится в этом месте. Как вы можете видеть, также можно умножить на два (масштабирование), в то время как a MOVограничено сложением / вычитанием.
Простите всех. @ big.heart одурачил меня, дав ответ на это три часа назад, и он стал «новым» в моем поиске вопросов на Ассамблее.
Дэвид Хоэлзер
1
Почему в синтаксисе используются скобки, если он не выполняет адресацию памяти?
Голопот
3
@ q4w56 Это одна из тех вещей, где ответ: «Вот как ты это делаешь». Я полагаю, что это одна из причин того, что людям так сложно понять, что LEAделает.
Дэвид Хоэлзер
2
@ q4w56: это инструкция shift + add, использующая синтаксис операнда памяти и кодирование машинного кода. На некоторых процессорах он может даже использовать аппаратное обеспечение AGU, но это историческая деталь. Все еще актуальным фактом является то, что аппаратное обеспечение декодера уже существует для декодирования этого вида shift + add, и LEA позволяет нам использовать его для арифметики вместо адресации памяти. (Или для вычисления адреса, если один вход фактически является указателем).
Питер Кордес
20
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. К сожалению.
Как уже упоминалось в существующих ответах, LEAимеет преимущества выполнения арифметики адресации памяти без обращения к памяти, сохранения арифметического результата в другом регистре вместо простой формы инструкции добавления. Реальное основное преимущество в производительности заключается в том, что современный процессор имеет отдельный блок LEA ALU и порт для эффективной генерации адреса (включая LEAи другой ссылочный адрес памяти), это означает, что арифметическая операция в LEAи другая обычная арифметическая операция в ALU может выполняться параллельно в одном ядро.
Другим важным моментом, который не упоминается в других ответах, является LEA REG, [MemoryAddress]инструкция PIC (позиционно-независимый код), которая кодирует относительный адрес ПК в этой инструкции для справки MemoryAddress. Это отличается от того, MOV REG, MemoryAddressчто кодирует относительный виртуальный адрес и требует перемещения / исправления в современных операционных системах (например, ASLR является обычной функцией). Так что LEAможет быть использован для преобразования таких не PIC в PIC.
«Отдельная часть LEA ALU» в основном не соответствует действительности. Современные процессоры выполняются leaна одном или нескольких из тех же ALU, которые выполняют другие арифметические инструкции (но, как правило, их меньше, чем других арифметических операций). Например, упомянутый процессор Haswell может выполнять addили выполнять subбольшинство других основных арифметических операций на четырех различных ALU, но может выполняться только leaна одном (сложном lea) или двух (простых lea). Что еще более важно, эти два- leaспособных ALU - это просто два из четырех, которые могут выполнять другие инструкции, поэтому, как утверждается, преимущества параллелизма нет.
BeeOnRope
Статья, которую вы связали (правильно), показывает, что LEA находится на том же порту, что и целочисленный ALU (add / sub / boolean), и целочисленный модуль MUL в Haswell. (И векторные ALU, включая FP ADD / MUL / FMA). Простое устройство LEA находится на 5-м порту, который также запускает ADD / SUB / что угодно, векторное перемешивание и другие вещи. Единственная причина, по которой я не одобряю это то, что вы указываете на использование REA-относительного LEA (только для x86-64).
Питер Кордес
8
Инструкция LEA может использоваться, чтобы избежать трудоемких вычислений эффективных адресов процессором. Если адрес используется неоднократно, более эффективно сохранять его в регистре, а не вычислять эффективный адрес каждый раз, когда он используется.
Не обязательно на современном x86. Большинство режимов адресации имеют одинаковую стоимость, с некоторыми оговорками. Так [esi]редко бывает дешевле, чем сказать, [esi + 4200]и только редко дешевле, чем [esi + ecx*8 + 4200].
BeeOnRope
@BeeOnRope [esi]не дешевле чем [esi + ecx*8 + 4200]. Но зачем сравнивать? Они не эквивалентны. Если вы хотите, чтобы первое указывало ту же ячейку памяти, что и второе, вам нужны дополнительные инструкции: вам нужно прибавить к esiзначению, ecxумноженному на 8. Ой, умножение приведет к засорению ваших флагов ЦП! Затем вы должны добавить 4200. Эти дополнительные инструкции увеличивают размер кода (занимают место в кеше команд, циклы выборки).
Каз
2
@Kaz - я думаю, что вы упустили мою точку зрения (иначе я пропустил точку зрения ОП). Насколько я понимаю, ОП говорит, что если вы собираетесь использовать что-то подобное [esi + 4200]в последовательности инструкций, то лучше сначала загрузить эффективный адрес в регистр и использовать его. Например, вместо того, чтобы писать add eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200], вы должны предпочесть lea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi], что редко бывает быстрее. По крайней мере, это простое толкование этого ответа.
BeeOnRope
Итак, причина, по которой я сравнивал [esi]и [esi + 4200](или [esi + ecx*8 + 4200]заключается в том, что это упрощение, которое предлагает OP (насколько я понимаю): что N инструкций с одинаковым комплексным адресом преобразуются в N инструкций с простой (одна рег) адресацией плюс одна lea, так как сложная адресация «отнимает много времени». На самом деле она медленнее даже на современном x86, но только с задержкой, что вряд ли будет иметь значение для последовательных инструкций с одним и тем же адресом
BeeOnRope
1
Возможно, вы ослабили некоторое давление в регистре, да, но может быть и обратное: если регистры, с которыми вы сгенерировали эффективный адрес, являются действующими, вам нужен другой регистр, чтобы сохранить результат, leaчтобы в этом случае он увеличился. В общем, хранение промежуточных звеньев - причина регистрационного давления, а не его решение - но я думаю, что в большинстве случаев это промывка. @Kaz
BeeOnRope
7
Инструкция 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не влияет на флаги ЦП.)
Нет причины LEAисключать эту возможность, хотя бы потому, что есть более короткая альтернатива; это просто объединение в ортогональной форме с доступными режимами адресации.
// 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 (optimize) в качестве опции компилятора gcc найдет инструкцию lea для указанной строки кода.
Кажется, что многие ответы уже завершены, я хотел бы добавить еще один пример кода для демонстрации того, как команды 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
Разбивать ваш встроенный ассемблер на отдельные операторы небезопасно, и ваши списки саблберов неполные. Блок basic-asm сообщает компилятору, что он не имеет клобберов, но на самом деле он изменяет несколько регистров. Кроме того, вы можете использовать, =dчтобы сообщить компилятору результат в EDX, сохранив a mov. Вы также пропустили раннюю клобберную декларацию на выходе. Это действительно демонстрирует то, что вы пытаетесь продемонстрировать, но также является вводящим в заблуждение плохим примером встроенного asm, который сломается, если используется в других контекстах. Это плохая вещь для ответа переполнения стека.
Питер Кордес
Если вы не хотите записывать %%все эти имена регистров в Extended asm, используйте ограничения ввода. как asm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));. Разрешение регистров инициализации компилятора означает, что вам также не нужно объявлять клобберы. Вы слишком усложняете вещи, обнуляя xor до того, как mov-немедленное перезаписывает также весь регистр.
Питер Кордес
@PeterCordes Спасибо, Питер, вы хотите, чтобы я удалил этот ответ или изменил его после ваших комментариев?
Jaehyuk Lee
1
Если вы исправите встроенный ассм, это не принесет никакого вреда и, возможно, станет хорошим конкретным примером для начинающих, которые не понимают других ответов. Нет необходимости удалять, и это легко исправить, как я показал в своем последнем комментарии. Я думаю, что было бы оправданно, если бы плохой пример встроенного asm был исправлен в «хороший» пример. (Я не понизил)
Питер Кордес
1
Где кто-нибудь говорит, что mov 4(%ebx, %eax, 8), %edxэто недействительно? В любом случае, да, поскольку movбыло бы разумно написать "a"(1ULL)компилятору, что у вас есть 64-битное значение, и поэтому ему нужно убедиться, что оно расширено для заполнения всего регистра. На практике он все еще будет использоваться mov $1, %eax, потому что написание EAX с нулевым расширением распространяется на RAX, если только у вас нет странной ситуации с окружающим кодом, когда компилятор знал, что RAX = 0xff00000001или что-то еще. Ведь leaвы все еще используете 32-битный размер операнда, поэтому любые старшие биты во входных регистрах не влияют на 32-битный результат.
Питер Кордес
4
LEA: просто "арифметическая" инструкция ..
MOV передает данные между операндами, но Леа только вычисляет
LEA явно перемещает данные; у него есть целевой операнд. LEA не всегда рассчитывает; он вычисляет, рассчитывает ли эффективный адрес, указанный в операнде источника. LEA EAX, GLOBALVAR не рассчитывается; он просто перемещает адрес GLOBALVAR в EAX.
Каз
@Kaz спасибо за ваш отзыв. мой источник был «LEA (эффективный адрес загрузки) - это, по сути, арифметическая инструкция - она не выполняет никакого фактического доступа к памяти, но обычно используется для вычисления адресов (хотя вы можете вычислять целые числа общего назначения с ее помощью)». Форма книги Эльдад-Эйлам стр. 149
бухгалтер
@Kaz: Вот почему LEA является избыточным, когда адрес уже является постоянной времени соединения; используйте mov eax, offset GLOBALVARвместо. Вы можете использовать LEA, но он немного больше по размеру кода, чем mov r32, imm32и работает на меньшем количестве портов, потому что он все еще проходит процесс вычисления адреса . lea reg, symbolполезно только в 64-битном режиме для REA-относительного LEA, когда вам нужны PIC и / или адреса за пределами младших 32-битных. В 32 или 16-битном коде преимущество нулевое. LEA - это арифметическая инструкция, раскрывающая способность ЦП декодировать / вычислять режимы адресации.
Питер Кордес
@Kaz: тем же аргументом можно сказать, что imul eax, edx, 1он не рассчитывается: он просто копирует edx в eax. Но на самом деле он пропускает ваши данные через множитель с задержкой в 3 цикла. Или это rorx eax, edx, 0просто копии (повернуть на ноль).
Питер Кордес
@PeterCordes Моя точка зрения такова, что LEA EAX, GLOBALVAL и MOV EAX, GLOBALVAR просто получают адрес из непосредственного операнда. Здесь не применяется множитель 1 или смещение 0; Это может быть так на аппаратном уровне, но это не видно на языке ассемблера или в наборе команд.
Каз
1
Все обычные «расчетные» инструкции, такие как добавление умножения, исключение или установка флагов состояния, таких как ноль, знак. Если вы используете сложный адрес, AX xor:= mem[0x333 +BX + 8*CX] флаги устанавливаются в соответствии с операцией xor.
Теперь вы можете использовать адрес несколько раз. Загрузка таких адресов в реестр никогда не предназначена для установки флагов состояния, и, к счастью, это не так. Фраза «загрузить эффективный адрес» заставляет программиста осознать это. Отсюда и странное выражение.
Понятно, что когда процессор способен использовать сложный адрес для обработки своего контента, он может рассчитывать его для других целей. Действительно, его можно использовать для выполнения преобразования x <- 3*x+1в одной инструкции. Это общее правило в программировании на ассемблере: используйте инструкции, однако это раскачивает вашу лодку.
Единственное, что имеет значение, это то, полезно ли вам конкретное преобразование, воплощенное в инструкции.
Нижняя граница
MOV, X| T| AX'| R| BX|
а также
LEA, AX'| [BX]
имеют такой же эффект на AX, но не на флаги состояния. (Это запись ciasdis .)
«Это общее правило в программировании на ассемблере: используйте инструкции, как бы это ни крутило вашу лодку». Я бы лично не раздавал этот совет из-за таких вещей, как call lbllbl: pop raxтехническая «работа» как способ получить ценность rip, но вы сделаете предсказание ветвлений очень несчастным. Используйте инструкции по
своему
@ The6P4C Это полезная оговорка. Однако, если нет альтернативы тому, чтобы сделать предсказание ветвления несчастным, нужно пойти на это. Есть еще одно общее правило в программировании на ассемблере. Могут быть альтернативные способы сделать что-то, и вы должны мудро выбирать из альтернатив. Существуют сотни способов получить содержимое регистра BL в регистр AL. Если остаток RAX не требуется сохранять, LEA может быть вариантом. Не влияет на флаги может быть хорошей идеей на некоторых из тысяч типов процессоров x86. Groetjes Альберт
Альберт ван дер Хорст
-1
Простите, если кто-то уже упоминал, но во времена x86, когда сегментация памяти все еще была актуальна, вы можете не получить те же результаты из этих двух инструкций:
«Эффективный адрес» - это просто «смещенная» часть seg:offпары. LEA не зависит от базы сегмента; обе эти инструкции будут (неэффективно) помещены 0x1234в AX. К сожалению, в x86 нет простого способа вычислить полный линейный адрес (эффективная + сегментная база) в регистр или пару регистров.
Питер Кордес
@PeterCordes Очень полезно, спасибо, что поправили меня.
Ответы:
Как уже отмечали другие, LEA (эффективный адрес загрузки) часто используется как «хитрость» для выполнения определенных вычислений, но это не является его основной целью. Набор команд x86 был разработан для поддержки языков высокого уровня, таких как Pascal и C, где массивы - особенно массивы целых или небольших структур - распространены. Рассмотрим, например, структуру, представляющую (x, y) координаты:
Теперь представьте себе заявление вроде:
где
points[]
массивPoint
. Предполагая , что база массива ужеEBX
, и переменныйi
вEAX
, аxcoord
иycoord
каждый представляет 32 бит (такycoord
как по смещению 4 байта в структурах), это утверждение может быть составлено с:который приземлится
y
вEDX
. Коэффициент масштабирования равен 8, потому что каждыйPoint
имеет размер 8 байт. Теперь рассмотрим то же выражение, которое используется с оператором "address of" &:В этом случае вам нужно не значение
ycoord
, а его адрес. Вот гдеLEA
(эффективный адрес загрузки) приходит. ВместоMOV
, компилятор может генерироватькоторый загрузит адрес в
ESI
.источник
mov
инструкцию и убрать скобки?MOV EDX, EBX + 8*EAX + 4
MOV
на косвенный источник, за исключением того, что он только косвенный, а неMOV
. На самом деле он не читает с вычисленного адреса, просто вычисляет его.Из «Дзен Собрания» Абраша:
И
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
состояние.источник
LEA EAX, [ EAX + EBX + 1234567 ]
вычисляет суммуEAX
,EBX
и1234567
(это три операнда).LEA EAX, [ EBX + ECX ]
рассчитываетEBX + ECX
без переопределения либо с результатом. Третье, для чегоLEA
используется (не перечислено Фрэнком) умножение на константу (на два, три, пять или девять), если вы используете это какLEA EAX, [ EBX + N * EBX ]
(N
может быть 1,2,4,8). Другой вариант использования удобен в циклах: разница междуLEA EAX, [ EAX + 1 ]
иINC EAX
заключается в том, что последний меняется,EFLAGS
а первый нет; это сохраняетCMP
состояниеLEA
показывает, какие виды «уловок» можно использовать для ... (см. «LEA (эффективный адрес загрузки) часто используется в качестве« уловки »для выполнения определенных вычислений» в популярном ответе IJ Кеннеди выше)Еще одной важной особенностью
LEA
инструкции является то, что она не изменяет коды состояния, такие какCF
иZF
, при вычислении адреса с помощью арифметических команд, таких какADD
илиMUL
делает. Эта функция снижает уровень зависимости между инструкциями и тем самым освобождает место для дальнейшей оптимизации компилятором или аппаратным планировщиком.источник
lea
иногда полезно, чтобы компилятор (или человеческий кодер) выполнял математику, не забивая результат флага. Ноlea
не быстрее чемadd
. Большинство инструкций x86 пишут флаги. Высокопроизводительные реализации x86 должны переименовывать EFLAGS или иным образом избегать опасности записи после записи для нормального выполнения кода, поэтому инструкции, которые избегают флаговых записей, не лучше из-за этого. ( неполный флаг может создать проблемы, см. инструкцию INC против ADD 1: имеет ли это значение? )Несмотря на все объяснения, LEA является арифметической операцией:
Просто его название крайне глупо для операции shift + add. Причина этого уже была объяснена в самых рейтинговых ответах (т. Е. Она была разработана для непосредственного сопоставления высокоуровневых ссылок на память).
источник
LEA
на AGU, а на обычных целочисленных ALU. В наши дни нужно очень внимательно прочитать спецификации процессора, чтобы выяснить, «где все работает» ...LEA
дает вам адрес, который возникает в любом режиме адресации, связанном с памятью. Это не операция сдвига и добавления.Может быть, просто еще одна вещь о инструкции LEA. Вы также можете использовать LEA для быстрого умножения регистров на 3, 5 или 9.
источник
LEA EAX, [EAX*3]
?shl
команду shift left как инструкцию для умножения регистров на 2,4,8,16 ... это быстрее и короче. Но для умножения на числа, отличающиеся степенью 2, мы обычно используемmul
инструкцию, которая более претенциозна и медленнее.lea eax,[eax*3]
будет переводить в эквивалентlea eax,[eax+eax*2]
.lea
это сокращение от "эффективный адрес загрузки". Он загружает адрес ссылки на местоположение исходного операнда в целевой операнд. Например, вы можете использовать его для:перемещать элементы
ebx
указателяeax
дальше (в 64-битном массиве / элементном массиве) с помощью одной инструкции. По сути, вы получаете преимущества от сложных режимов адресации, поддерживаемых архитектурой x86, для эффективного управления указателями.источник
Самая большая причина, по которой вы используете «
LEA
а»,MOV
заключается в том, что вам нужно выполнить арифметику с регистрами, которые вы используете для вычисления адреса. По сути, вы можете выполнить то, что равнозначно арифметике указателей в нескольких регистрах в комбинации, для «бесплатно».Что действительно сбивает с толку, так это то, что вы обычно пишете
LEA
как a,MOV
но на самом деле вы не разыменовываете память. Другими словами:MOV EAX, [ESP+4]
Это переместит содержание того, на что
ESP+4
указываетEAX
.LEA EAX, [EBX*8]
Это переместит эффективный адрес
EBX * 8
в EAX, а не тот, который находится в этом месте. Как вы можете видеть, также можно умножить на два (масштабирование), в то время как aMOV
ограничено сложением / вычитанием.источник
LEA
делает.8086 имеет большое семейство инструкций, которые принимают операнд регистра и эффективный адрес, выполняют некоторые вычисления, чтобы вычислить смещенную часть этого эффективного адреса, и выполняют некоторые операции, включающие регистр и память, на которые ссылается вычисленный адрес. Было довольно просто заставить одну из инструкций в этом семействе вести себя так же, как указано выше, за исключением того, что пропускала эту фактическую операцию с памятью. Это, инструкции:
были реализованы почти одинаково внутри. Разница - пропущенный шаг. Обе инструкции работают примерно так:
Что касается того, почему Intel считает, что эта инструкция стоит того, чтобы ее включить, я не совсем уверен, но тот факт, что ее реализация была дешевой, был бы важным фактором. Другим фактором мог быть тот факт, что ассемблер Intel позволял определять символы относительно регистра BP. Если бы он
fnord
был определен как символ, относящийся к BP (например, BP + 8), можно сказать:Если кто-то хотел использовать что-то вроде stosw для хранения данных по адресу, относящемуся к BP, он мог сказать
было удобнее чем:
Обратите внимание, что если забыть о «смещении» мира, в DI будет добавлено содержимое местоположения [BP + 8], а не значение 8. К сожалению.
источник
Как уже упоминалось в существующих ответах,
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.источник
lea
на одном или нескольких из тех же ALU, которые выполняют другие арифметические инструкции (но, как правило, их меньше, чем других арифметических операций). Например, упомянутый процессор Haswell может выполнятьadd
или выполнятьsub
большинство других основных арифметических операций на четырех различных ALU, но может выполняться толькоlea
на одном (сложномlea
) или двух (простыхlea
). Что еще более важно, эти два-lea
способных ALU - это просто два из четырех, которые могут выполнять другие инструкции, поэтому, как утверждается, преимущества параллелизма нет.Инструкция LEA может использоваться, чтобы избежать трудоемких вычислений эффективных адресов процессором. Если адрес используется неоднократно, более эффективно сохранять его в регистре, а не вычислять эффективный адрес каждый раз, когда он используется.
источник
[esi]
редко бывает дешевле, чем сказать,[esi + 4200]
и только редко дешевле, чем[esi + ecx*8 + 4200]
.[esi]
не дешевле чем[esi + ecx*8 + 4200]
. Но зачем сравнивать? Они не эквивалентны. Если вы хотите, чтобы первое указывало ту же ячейку памяти, что и второе, вам нужны дополнительные инструкции: вам нужно прибавить кesi
значению,ecx
умноженному на 8. Ой, умножение приведет к засорению ваших флагов ЦП! Затем вы должны добавить 4200. Эти дополнительные инструкции увеличивают размер кода (занимают место в кеше команд, циклы выборки).[esi + 4200]
в последовательности инструкций, то лучше сначала загрузить эффективный адрес в регистр и использовать его. Например, вместо того, чтобы писатьadd eax, [esi + 4200]; add ebx, [esi + 4200]; add ecx, [esi + 4200]
, вы должны предпочестьlea edi, [esi + 4200]; add eax, [edi]; add ebx, [edi]; add ecx, [edi]
, что редко бывает быстрее. По крайней мере, это простое толкование этого ответа.[esi]
и[esi + 4200]
(или[esi + ecx*8 + 4200]
заключается в том, что это упрощение, которое предлагает OP (насколько я понимаю): что N инструкций с одинаковым комплексным адресом преобразуются в N инструкций с простой (одна рег) адресацией плюс однаlea
, так как сложная адресация «отнимает много времени». На самом деле она медленнее даже на современном x86, но только с задержкой, что вряд ли будет иметь значение для последовательных инструкций с одним и тем же адресомlea
чтобы в этом случае он увеличился. В общем, хранение промежуточных звеньев - причина регистрационного давления, а не его решение - но я думаю, что в большинстве случаев это промывка. @KazИнструкция LEA (Load Effective Address) - это способ получения адреса, который возникает в любом из режимов адресации памяти процессора Intel.
То есть, если у нас есть движение данных, как это:
он перемещает содержимое назначенной ячейки памяти в целевой регистр.
Если заменить свой
MOV
путьLEA
, то адрес ячейки памяти рассчитывается точно так же, по<MEM-OPERAND>
адресации выражения. Но вместо содержимого ячейки памяти мы получаем само местоположение в месте назначения.LEA
не является конкретной арифметической инструкцией; это способ перехвата эффективного адреса, возникающего в любом из режимов адресации памяти процессора.Например, мы можем использовать
LEA
только простой прямой адрес. Никакая арифметика не используется вообще:Это действительно; мы можем проверить это в командной строке Linux:
Здесь нет добавления масштабированного значения и смещения. Ноль перемещается в EAX. Мы могли бы сделать это, используя MOV с непосредственным операндом.
Это причина, почему люди, которые считают, что квадратные скобки
LEA
излишни, сильно ошибаются; квадратные скобки не являютсяLEA
синтаксисом, но являются частью режима адресации.LEA реально на аппаратном уровне. Сгенерированная инструкция кодирует фактический режим адресации, и процессор выполняет его до момента вычисления адреса. Затем он перемещает этот адрес к месту назначения вместо генерации ссылки на память. (Поскольку вычисление адреса в режиме адресации в любой другой инструкции не влияет на флаги ЦП,
LEA
не влияет на флаги ЦП.)Контраст с загрузкой значения с нулевого адреса:
Это очень похожая кодировка, понимаете? Просто
8d
изLEA
изменилось в8b
.Конечно, эта
LEA
кодировка длиннее, чем перемещение непосредственного нуля вEAX
:Нет причины
LEA
исключать эту возможность, хотя бы потому, что есть более короткая альтернатива; это просто объединение в ортогональной форме с доступными режимами адресации.источник
Вот пример.
С опцией -O (optimize) в качестве опции компилятора gcc найдет инструкцию lea для указанной строки кода.
источник
Кажется, что многие ответы уже завершены, я хотел бы добавить еще один пример кода для демонстрации того, как команды lea и move работают по-разному, когда они имеют одинаковый формат выражения.
Короче говоря, можно использовать как инструкции Le, так и инструкции MOV с круглыми скобками, включающими операнд src инструкций. Когда они заключены в () , выражение в () вычисляется таким же образом; однако две инструкции по-разному интерпретируют вычисленное значение в операнде src.
Независимо от того, используется ли выражение с lea или mov, значение src рассчитывается, как показано ниже.
Однако, когда он используется с инструкцией mov, он пытается получить доступ к значению, указанному адресом, сгенерированным вышеприведенным выражением, и сохранить его в месте назначения.
В отличие от этого, когда инструкция lea выполняется с вышеприведенным выражением, она загружает сгенерированное значение в том виде, в котором оно находится, к месту назначения.
Приведенный ниже код выполняет инструкцию lea и инструкцию mov с одним и тем же параметром. Однако, чтобы уловить разницу, я добавил обработчик сигнала уровня пользователя, чтобы отследить ошибку сегментации, вызванную доступом к неправильному адресу в результате команды mov.
Пример кода
Результат выполнения
источник
=d
чтобы сообщить компилятору результат в EDX, сохранив amov
. Вы также пропустили раннюю клобберную декларацию на выходе. Это действительно демонстрирует то, что вы пытаетесь продемонстрировать, но также является вводящим в заблуждение плохим примером встроенного asm, который сломается, если используется в других контекстах. Это плохая вещь для ответа переполнения стека.%%
все эти имена регистров в Extended asm, используйте ограничения ввода. какasm("lea 4(%%ebx, %%eax, 8), %%edx" : "=d"(result_of_lea) : "a"(1), "b"(2));
. Разрешение регистров инициализации компилятора означает, что вам также не нужно объявлять клобберы. Вы слишком усложняете вещи, обнуляя xor до того, как mov-немедленное перезаписывает также весь регистр.mov 4(%ebx, %eax, 8), %edx
это недействительно? В любом случае, да, посколькуmov
было бы разумно написать"a"(1ULL)
компилятору, что у вас есть 64-битное значение, и поэтому ему нужно убедиться, что оно расширено для заполнения всего регистра. На практике он все еще будет использоватьсяmov $1, %eax
, потому что написание EAX с нулевым расширением распространяется на RAX, если только у вас нет странной ситуации с окружающим кодом, когда компилятор знал, что RAX =0xff00000001
или что-то еще. Ведьlea
вы все еще используете 32-битный размер операнда, поэтому любые старшие биты во входных регистрах не влияют на 32-битный результат.LEA: просто "арифметическая" инструкция ..
MOV передает данные между операндами, но Леа только вычисляет
источник
mov eax, offset GLOBALVAR
вместо. Вы можете использовать LEA, но он немного больше по размеру кода, чемmov r32, imm32
и работает на меньшем количестве портов, потому что он все еще проходит процесс вычисления адреса .lea reg, symbol
полезно только в 64-битном режиме для REA-относительного LEA, когда вам нужны PIC и / или адреса за пределами младших 32-битных. В 32 или 16-битном коде преимущество нулевое. LEA - это арифметическая инструкция, раскрывающая способность ЦП декодировать / вычислять режимы адресации.imul eax, edx, 1
он не рассчитывается: он просто копирует edx в eax. Но на самом деле он пропускает ваши данные через множитель с задержкой в 3 цикла. Или этоrorx eax, edx, 0
просто копии (повернуть на ноль).Все обычные «расчетные» инструкции, такие как добавление умножения, исключение или установка флагов состояния, таких как ноль, знак. Если вы используете сложный адрес,
AX xor:= mem[0x333 +BX + 8*CX]
флаги устанавливаются в соответствии с операцией xor.Теперь вы можете использовать адрес несколько раз. Загрузка таких адресов в реестр никогда не предназначена для установки флагов состояния, и, к счастью, это не так. Фраза «загрузить эффективный адрес» заставляет программиста осознать это. Отсюда и странное выражение.
Понятно, что когда процессор способен использовать сложный адрес для обработки своего контента, он может рассчитывать его для других целей. Действительно, его можно использовать для выполнения преобразования
x <- 3*x+1
в одной инструкции. Это общее правило в программировании на ассемблере: используйте инструкции, однако это раскачивает вашу лодку. Единственное, что имеет значение, это то, полезно ли вам конкретное преобразование, воплощенное в инструкции.Нижняя граница
а также
имеют такой же эффект на AX, но не на флаги состояния. (Это запись ciasdis .)
источник
call lbl
lbl: pop rax
техническая «работа» как способ получить ценностьrip
, но вы сделаете предсказание ветвлений очень несчастным. Используйте инструкции поПростите, если кто-то уже упоминал, но во времена x86, когда сегментация памяти все еще была актуальна, вы можете не получить те же результаты из этих двух инструкций:
а также
источник
seg:off
пары. LEA не зависит от базы сегмента; обе эти инструкции будут (неэффективно) помещены0x1234
в AX. К сожалению, в x86 нет простого способа вычислить полный линейный адрес (эффективная + сегментная база) в регистр или пару регистров.