В операциях деления размер делимого в два раза больше, чем размер делителя. Поэтому нельзя просто загрузить данные в регистр EAX и поделить его на какое-либо значение, т.к. в операции деления будет задействован также и регистр EDX. Поэтому прежде чем выполнять деление, надо установить корректное значение в регистр EDX, иначе результат будет неправильным. Значение регистра EDX должно зависеть от значения регистра EAX. Тут возможны два варианта – для знаковых и беззнаковых чисел.
Если мы используем беззнаковые числа, то в любом случае в регистр EDX необходимо записать значение 0: aaaaaaaah → 00000000aaaaaaaah.
Если же мы используем знаковые числа, то значение регистра EDX будет зависеть от знака числа:55555555h → 0000000055555555h, aaaaaaaah → ffffffffaaaaaaaah.
Записать значение 0 не сложно, а вот для знакового расширения необходимо анализировать знак числа. Однако нет необходимости делать это вручную, т.к. язык ассемблера имеет ряд команд, позволяющих расширять байт до слова, слово до двойного слова и двойное слово до учетверённого слова.
|
|
cbw; Знаковое расширение AL до AX
cwd; Знаковое расширение AX до DX:AX
cwde; Знаковое расширение AX до EAX
cdq; Знаковое расширение EAX до EDX:EAX
Таким образом, если делитель имеет размер 2 или 4 байта, то нужно устанавливать значение не только регистра AX/EAX, но и регистра DX/EDX. Если же делитель имеет размер 1 байт, то можно просто записать делимое в регистр AX.
x dd?
mov eax, x; Заносим в регистр EAX значение переменной x, которое заранее неизвестно
cdq; Знаковое расширение EAX в EDX:EAX
mov ebx, 7
idiv ebx
В языке ассемблера существуют также команды, позволяющие занести в регистр значение другого регистра или ячейки памяти со знаковым или беззнаковым расширением.
MOVSX <операнд1>, <операнд2>; Знаковое расширение – старшие биты заполняются знаковым битом
MOVZX <операнд1>, <операнд2>; Беззнаковое расширение – старшие биты заполняются нулём
Операнд1 и операнд2 могут иметь любой размер. Понятно, что операнд1 должен быть больше, чем операнд2. В случае равенства размера операндов следует использовать обычную команду пересылки MOV, которая выполняется быстрее.
Рассмотрим пример: необходимо вычислить x * x * x, где x – 1-байтовая переменная.
; Первый вариант
mov al, x; Пересылаем x в регистр AL
imul al; Умножаем регистр AL на себя, AX = x * x
movsx bx, x; Пересылаем x в регистр BX со знаковым расширением
imul bx; Умножаем AX на BX. Но! – результат размещается в DX:AX
; Второй вариант
mov al, x; Пересылаем x в регистр AL
imul al; Умножаем регистр AL на себя, AX = x * x
cwde; Расширяем AX до EAX
movsx ebx, x; Пересылаем x в регистр EBX со знаковым расширением
imul ebx; Умножаем EAX на EBX. Поскольку x – 1-байтовая переменная, результат благополучно помещается в EAX
|
|
Рассмотрим ещё один пример.
mov eax, x
mov ebx, 429496730; 429496730 = 4294967296 / 10
imul ebx; EDX = x / 10. Выполняется в ≈5 раз быстрее, чем деление
Чем обусловлено получение такого результата? Всегда ли будет работать этот механизм?
Переходы и циклы
Для изменения порядка выполнения команд в языке ассемблера используются команды условного и безусловного перехода, а также команды управления циклом. Все эти команды не меняют флаги.
Безусловный переход
Команда безусловного перехода имеет следующий синтаксис:
JMP <операнд>
Операнд указывает адрес перехода. Существует два способа указания этого адреса, соответственно различают прямой и косвенный переходы.
Прямой переход
Если в команде перехода указывается метка команды, на которую надо перейти, то переход называется прямым.
jmp L
...
L: mov eax, x
Вообще, любой переход заключается в изменении адреса следующей исполняемой команды, т.е. в изменении значения регистра EIP. Казалось бы, в команде перехода должен задаваться именно адрес перехода. Однако в команде прямого перехода задаётся не абсолютный адрес, а разность между адресом перехода и адресом команды перехода. Действие команды перехода заключается в прибавлении этой величины к текущему значению регистра EIP 2. Операнд команды перехода рассматривается как поле со знаком, поэтому при сложении его со значением регистра EIP значение в этом регистре может как увеличиться, так и уменьшиться, т.е. возможен переход и вперёд, и назад.
Запись в команде перехода не абсолютного, а относительного адреса перехода позволяет уменьшить размер команды перехода. Абсолютный адрес должен быть 32-битным, а относительный может быть и 8-битным, и 16-битным.
Косвенный переход
При косвенном переходе в команде перехода указывается не адрес перехода, а регистр или ячейка памяти, где этот адрес находится. Содержимое указанного регистра или ячейки памяти рассматривается как абсолютный адрес перехода. Косвенные переходы используются в тех случаях, когда адрес перехода становится известен только во время работы программы.
jmp ebx