Страницы

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

суббота, 30 ноября 2019 г.

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

#cpp #ассемблер


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


Ответы

Ответ 1



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] Неважно, что никакой адрес на самом деле никуда не загружается - главное что работать будет точно так же, и чуть быстрее - т.к. процессор вычисляет адреса в памяти быстрее, чем складывает числа :)

Ответ 2



Эта команда загружает в eax адрес того, что справа - т.е. rdi+1. Такой хитрый способ объединить mov eax, rdi inc eax Т.е. в каком-то смысле в переводе на C++ это &*(rdi+1) :) Т.е. получение адреса объекта, находящегося по адресу [rdi+1]. См., например, тут.

Ответ 3



Данная команда lea eax,[rdi + 1] заносит в регистр eax значение rdi + 1, где в rdi хранится значение аргумента. В двух остальных случаях, когда аргумент передается по ссылке или передается указатель на исходный аргумент, то регистр rdi содержит адрес аргумента. f(int*): # @f(int*) mov eax, dword ptr [rdi] inc eax ret f(int&): # @f(int&) mov eax, dword ptr [rdi] inc eax ret Поэтому сначала в регистр eax заносится значение аргумента, используя тот адрес, который находится в rdi mov eax, dword ptr [rdi] а затем значение регистра eax увеличивается на 1. inc eax То есть различие между первым определением функции и двумя последующими состоит в том, что в первом случае регистр rdi содержит копию значения аргумента, тогда как в двух последних случаях регистр rdi получает адрес исходного аргумента.

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

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