Быстрый ответ на ваш вопрос: что такое shr eax, 1Fh
, - это то, что он служит для изоляции самого верхнего разряда eax
. Это может быть проще понять, если вы конвертируете шестнадцатеричный 1Fh
в десятичный 31
. Теперь вы видите, что вы смещаете eax
справа на 31. Так как eax
- это 32-битное значение, смещение его битов на 31 будет изолировать самый верхний бит, так что eax
будет содержать 0 или 1, в зависимости от того, что исходное значение было бит 31 (предполагая, что мы начинаем нумерацию битов с 0).
Это общий трюк для изоляции знака . Когда значение интерпретируется как целое число со знаком на двухкомпонентной машине, самый старший бит является битом знака. Он установлен (== 1), если значение отрицательное, или clear (== 0) в противном случае. Конечно, если значение интерпретируется как целое число без знака, самый верхний бит - это еще один бит, используемый для хранения его значения, поэтому самый старший бит имеет произвольное значение.
Going построчно через разборке, вот что делает этот код:
mov eax, edx
Очевидно, что вход был в EDX
. Эта инструкция копирует значение от EDX
до EAX
. Это позволяет последующему коду манипулировать значением в EAX
без потери оригинала (в EDX
).
shr eax, 1Fh
Сдвиг EAX
вправо на 31 мест, таким образом, изолировать верхний бит. Предполагая, что входное значение представляет собой целое число со знаком, это будет бит знака. EAX
теперь будет содержать 1, если исходное значение было отрицательным, или 0 в противном случае.
add eax, edx
Добавить исходное значение (EDX
) для нашего временного значения в EAX
. Если исходное значение было отрицательным, это добавит к нему 1. В противном случае он добавит 0.
sar eax, 1
Сдвиг EAX
вправо на 1-е место. Разница здесь в том, что это арифметика сдвиг вправо, тогда как SHR
- это логический правая смена. Логический сдвиг заполняет вновь выставленные биты с 0s. Арифметический сдвиг копирует самый верхний бит (знаковый бит) на вновь открытый бит.
Собирает все вместе, это стандартные идиомы для деления целочисленного значения на 2, чтобы гарантировать, что отрицательные значения правильно закругленные.
Когда вы разделите значение без знака на 2, вам понадобится простой бит-сдвиг. Таким образом:
unsigned Foo(unsigned value)
{
return (value/2);
}
эквивалентен:
shr eax, 1
Но при делении подписанного значения, вы должны иметь дело с битом знака. Вы можете использовать sar eax, 1
для реализации знакового целочисленного деления на 2, но это приведет к округлению округленного значения к отрицательной бесконечности. Обратите внимание, что это отличается от поведения команды DIV
/IDIV
, которая всегда округляется до нуля. Если вы хотите эмулировать поведение «круг-нуль-ноль», вам нужна специальная обработка, и именно этот код у вас есть. На самом деле, GCC, Clang, MSVC, и, вероятно, каждый компилятор будет генерировать все именно этот код при компиляции следующей функции:
int Foo(int value)
{
return (value/2);
}
Это является очень старый трюк. Майкл Абраш обсуждал это в своем Zen of Assembly Language, опубликовано около 1990. (Here is the relevant section в онлайн-копии его книги.) Это было, безусловно, общее знание среди ассемблерных гуру задолго до этого.