Страницы

Поиск по вопросам

четверг, 13 декабря 2018 г.

Ассемблерная команда LEA

Не знаю почему, но эта ассемблерная команда не дает мне покоя LEA
C++
int f(int t) { return t+1; }
int f(int*t) { return *t+1; }
int f(int& t) { return t+1; }
Ассемблер
f(int): # @f(int) lea eax, [rdi + 1] ret
f(int*): # @f(int*) mov eax, dword ptr [rdi] inc eax ret
f(int&): # @f(int&) mov eax, dword ptr [rdi] inc eax ret
Если команда MOV ясна как день, то команда LEA не ясна!
Я знаю что команда LEA выполняет вычисление адреса второго операнда и записывание его в первый операнд (это все что мне известно)
В том примере что по ссылке, а именно: lea eax,[rdi + 1] это явно не вычисление адреса и не запись в первый операнд, нет запись то будет но скорее всего что-то другое. Или я что-то не правильно понял? Объясните пожалуйста в соответствии с С++ кодом.
P.S Искал, но исчерпывающего ответа на мой вопрос не нашел, искал даже в книжке Калашникова, а там даже этой команды не нашел...эхх.


Ответ

lea eax, [rdi+1]
Эта команда загружает в eax адрес значения, лежащего под адресу rdi + 1. Т.е. она загружает в eax просто rdi+1
Выглядит странно, и чтобы понять зачем именно нужна lea, и чем она лучше просто аналогичного вызова mov или ручного вычисления адреса, нужно понять как команды записываются в памяти и выполняются процессором.
Например, у вас есть команда чтения значения:
mov eax, [rdi+1]; взять значение по адресу "rdi + 1"
Она компилируется в что-то вроде
[опкод mov][флаг что складываем в eax][флаг что берем по адресу rdi][+1]

Т.е. в 66 67 8B 47 01
Предположим что вам нужно получить сам адрес rdi+1 в eax
Вы можете сделать одно из двух:
Высчитать его руками:
mov eax, rdi + 1; не работает, move не умеет плюс!
и вам придется написать:
mov eax, rdi inc eax; 66 05 01 00 00 00
т.е. выполнить две инструкции. Возможно, хороший вариант, но только для простых +1. А для адресов вида [bp+si+4]?
mov eax, bp add eax, si add eax, 4; да, некрасиво!
или выполнить lea
lea eax, [rdi+1]

Сравните с mov

Байткод: 66 67 8D 47 01
Отличается только opcode, 8B -> 8D.
В процессоре есть готовый, очень эффективный механизм для базовых операций с адресами. И он уже реализован для операции mov - ведь mov умеет доставать значение по адресу!.
При использовании lea процессор делает все, что делает при mov, но пропускает последний шаг - извлечение значения по адресу. Вместо этого он складывает в eax сам адрес. Это гораздо удобнее и быстрее чем считать вещи вроде rdi + 1 отдельными командами.

Какое это отношение имеет к вашему примеру?
В вашем примере параметр лежит в rdi, а результат вы должны вернуть в eax. По-честному, компилятор должнен был написать
mov eax, rdi; 66 A1 add eax, 1; 66 05 01 00 00 00
Ну ок, для 1 можно использовать inc
mov eax, rdi; 66 A1 inc eax; 66 40
Но это все еще две команды. Процессор будет выполнять их по очереди.
Компилятор умный. Он знает, что процессор умеет складывать значения регистров с небольшими константами при обработке команды lea. И он вставляет одну команду, которая выдаст тот же результат.
lea eax, [rdi + 1]
Неважно, что никакой адрес на самом деле никуда не загружается - главное что работаеть будет точно так же, и чуть быстрее - т.к. процессор вычисляет адреса в памяти быстрее, чем складывает числа :)

Комментариев нет:

Отправить комментарий