Третий вариант команды IMUL позволяет указать и результат, и оба сомножителя. Однако результат может быть помещён только в регистр, а второй сомножитель может быть только непосредственным операндом. Первый сомножитель может быть регистром или ячейкой памяти.
Четвёртый вариант команды IMUL позволяет указать оба сомножителя. Первый должен быть регистром, а второй – регистром или ячейкой памяти. Результат помещается в регистр, являющийся первым операндом.
Команда IMUL устанавливает флаги так же, как и команда MUL. Однако расширение результата в регистр EDX/DX происходит только при использовании первого варианта команды IMUL. В остальных случаях часть произведения, не помещающаяся в регистр-результат, теряется, даже если в качестве результата указан регистр EAX/AX. При умножении двух 1-байтовых чисел, произведение которых больше байта, но меньше слова, в регистре-результате получается корректное произведение.
mov eax, 5
mov ebx, -7
imul ebx ; EAX = ffffffdd, EDX = ffffffff, CF = 0
mov ebx, 3
imul ebx, 6 ; EBX = EBX * 6
mov ebx, 500000
imul eax, ebx, 100000 ; EAX = EBX * 100000, старшая часть результата теряется
x dd 40
mov eax, 55
imul eax, x ; EAX = EAX * x
3.4.2. Команды деления
Деление, как и умножение, реализуется двумя командами, предназначенными для знаковых и беззнаковых чисел:
DIV <операнд> ; Беззнаковое деление
IDIV <операнд> ; Знаковое деление
В командах указывается только один операнд – делитель, который может быть регистром или ячейкой памяти, но не может быть непосредственным операндом. Местоположение делимого и результата для команд деления фиксировано.
Если делитель имеет размер 1 байт, то делимое берётся из регистра AX. Если делитель имеет размер 2 байта, то делимое берётся из регистровой пары DX:AX. Если же делитель имеет размер 4 байта, то делимое берётся из регистровой пары EDX:EAX.
Поскольку процессор работает с целыми числами, то в результате деления получается сразу два числа – частное и остаток. Эти два числа также помещаются в определённые регистры. Если делитель имеет размер 1 байт, то частное помещается в регистр AL, а остаток – в регистр AH. Если делитель имеет размер 2 байта, то частное помещается в регистр AX, а остаток – в регистр DX. Если же делитель имеет размер 4 байта, то частное помещается в регистр EAX, а остаток – в регистр EDX.
mov ax, 127
mov bl, 5
div bl ; AL = 19h = 25, AH = 02h = 2
mov ax, 127
mov bl, -5
idiv bl ; AL = e7h = -25, AH = 02h = 2
mov ax, -127
mov bl, 5
idiv bl ; AL = e7h = -25, AH = feh = -2
mov ax, -127
mov bl, -5
idiv bl ; AL = 19h = 25, AH = feh = -2
; x = a * b + c
mov eax, a
imul b
add eax, c ; Операнды команды сложения вычисляются слева направо
mov x, eax
; x = a + b * c
mov eax, b
imul c
add eax, a ; Операнды команды сложения вычисляются справа налево
mov x, eax
3.5. Изменение размера числа
В операциях деления размер делимого в два раза больше, чем размер делителя. Поэтому нельзя просто загрузить данные в регистр 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 раз быстрее, чем деление
Чем обусловлено получение такого результата? Всегда ли будет работать этот механизм?
Лекция №5. Переходы и циклы
Для изменения порядка выполнения команд в языке ассемблера используются команды условного и безусловного перехода, а также команды управления циклом. Все эти команды не меняют флаги.
4.1. Безусловный переход
Команда безусловного перехода имеет следующий синтаксис:
JMP <операнд>
Операнд указывает адрес перехода. Существует два способа указания этого адреса, соответственно различают прямой и косвенный переходы.
4.1.1. Прямой переход
Если в команде перехода указывается метка команды, на которую надо перейти, то переход называется прямым.
jmp L
...
L: mov eax, x
Вообще, любой переход заключается в изменении адреса следующей исполняемой команды, т. е. в изменении значения регистра EIP. Казалось бы, в команде перехода должен задаваться именно адрес перехода. Однако в команде прямого перехода задаётся не абсолютный адрес, а разность между адресом перехода и адресом команды перехода. Действие команды перехода заключается в прибавлении этой величины к текущему значению регистра EIP2. Операнд команды перехода рассматривается как поле со знаком, поэтому при сложении его со значением регистра EIP значение в этом регистре может как увеличиться, так и уменьшиться, т. е. возможен переход и вперёд, и назад.
Запись в команде перехода не абсолютного, а относительного адреса перехода позволяет уменьшить размер команды перехода. Абсолютный адрес должен быть 32-битным, а относительный может быть и 8-битным, и 16-битным.
|
Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |


