Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на языке ассемблера

.pdf
Скачиваний:
79
Добавлен:
08.05.2015
Размер:
1.66 Mб
Скачать

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 раз быстрее,

чем деление

Чем обусловлено получение такого результата? Всегда ли будет работать этот механизм?

4. Переходы и циклы

Для изменения порядка выполнения команд в языке ассемблера используются команды условного и безусловного перехода, а также команды управления циклом. Все эти команды не меняют флаги.

4.1. Безусловный переход

Команда безусловного перехода имеет следующий синтаксис:

JMP <операнд>

Операнд указывает адрес перехода. Существует два способа указания этого адреса, соответственно различают прямой и косвенный переходы.

4.1.1. Прямой переход

Если в команде перехода указывается метка команды, на которую надо перейти, то переход называется прямым.

jmp L

...

L: mov

eax, x

Вообще, любой переход заключается в изменении адреса следующей исполняемой команды, т.е. в изменении значения регистра EIP. Казалось бы, в команде перехода должен задаваться именно адрес перехода. Однако в команде прямого перехода задаётся не абсолютный адрес, а разность между адресом перехода и адресом команды перехода. Действие команды перехода заключается в прибавлении этой величины к текущему значению регистра EIP2. Операнд команды перехода рассматривается как поле со знаком, поэтому при сложении его со значением регистра EIP значение в этом регистре может как увеличиться, так и уменьшиться, т.е. возможен переход и вперёд, и назад.

Запись в команде перехода не абсолютного, а относительного адреса перехода позволяет уменьшить размер команды перехода. Абсолютный адрес должен быть 32-битным, а относительный может быть и 8-битным, и 16-битным.

4.1.2. Косвенный переход

При косвенном переходе в команде перехода указывается не адрес перехода, а регистр или ячейка памяти, где этот адрес находится. Содержимое указанного регистра или ячейки памяти рассматривается как абсолютный адрес перехода. Косвенные переходы используются в

тех случаях, когда адрес перехода становится известен только во время работы программы.

jmp ebx

4.2. Команды сравнения и условного перехода

Команды условного перехода осуществляют переход, который выполняется только в случае истинности некоторого условия. Истинность условия проверяется по значениям флагов. Поэтому обычно непосредственно перед командой условного перехода ставится команда сравнения, которая формирует значения флагов:

CMP <операнд1>, <операнд2>

Команда сравнения эквивалентна команде SUB за исключением того, что вычисленная разность никуда не заносится. Назначение команды CMP– установка и сброс флагов.

Что касается команд условного перехода, то их достаточно много, но все они записываются единообразно:

Jxx <метка>

Все команды условного перехода можно разделить на три группы.

В первую группу входят команды, которые обычно ставятся после команды сравнения. В их мнемокодах указывается тот результат сравнения, при котором надо делать переход.

 

Мнемокод

 

 

 

Название

 

 

 

Условие перехода после к

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JE

 

 

 

Переход если равно

 

 

 

op1 = op2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JNE

 

 

 

Переход если не равно

 

 

 

op1 ≠ op2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JL/JNGE

 

 

 

Переход если меньше

 

 

 

op1 < op2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JLE/JNG

 

 

 

Переход если меньше или равно

 

 

 

op1 ≤ op2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JG/JNLE

 

 

 

Переход если больше

 

 

 

op1 > op2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JGE/JNL

 

 

 

Переход если больше или равно

 

 

 

op1 ≥ op2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JB/JNAE

 

 

 

Переход если ниже

 

 

 

op1 < op2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JBE/JNA

 

 

 

Переход если ниже или равно

 

 

 

op1 ≤ op2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JA/JNBE

 

 

 

Переход если выше

 

 

 

op1 > op2

 

 

 

 

 

 

 

 

 

 

JAE/JNB

 

Переход если выше или равно

 

op1 ≥ op2

 

 

 

 

 

 

 

 

 

 

Рассмотрим

пример:

даны

две

переменные x и y,

в

переменную z нужно записать максимальное из чисел x и y.

 

mov

eax, x

 

 

 

 

 

cmp

eax, y

 

 

 

 

 

jge/jae L

 

; Используем

JGE для знаковых чисел

и JAE – для беззнаковых

 

 

 

 

mov

eax, y

 

 

 

 

 

L: mov

z, eax

 

 

 

 

 

Во вторую группу команд условного перехода входят те, которые обычно ставятся после команд, отличных от команды сравнения, и которые реагируют на то или иное значение какого-либо флага.

 

Мнемокод

 

 

 

Условие перехода

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JZ

 

 

ZF = 1

 

 

 

JNZ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JS

 

 

SF = 1

 

 

 

JNS

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JC

 

 

CF = 1

 

 

 

JNC

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JO

 

 

OF = 1

 

 

 

JNO

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

JP

 

 

PF = 1

 

 

 

JNP

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Рассмотрим

пример: пусть a, b и c

беззнаковые переменные

размером 1 байт,

требуется вычислить c = a * a + b, но если результат

превосходит размер байта, передать управление на метку ERROR.

mov al, a

mul al

jc ERROR

add al, b

jc ERROR

mov c, al

И, наконец, в третью группу входят две команды условного перехода, проверяющие не флаги, а значение регистра ECX или CX:

JCXZ <метка>

;

Переход,

если

значение

регистра

CX

равно 0

 

 

 

 

 

 

JECXZ

<метка>

;

Переход,

если

значение

регистра

ECX

равно

0

 

 

 

 

 

 

Однако эта команда выполняется достаточно долго. Выгоднее провести сравнение с нулём и использовать обычную команду условного перехода.

С помощью команд перехода можно реализовать любые разветвления и циклы.

; if (x > 0) S

cmp x, 0

jle L

...

; S

L:

; if (x) S1 else S2

cmp

x, 0

je

L1

...

; S1

jmp

L2

L1: ...

; S2

L2:

 

; if (a > 0 && b > 0) S

cmp a, 0

jle L

cmp b, 0

jle L

...

; S

L:

; if (a > 0 || b > 0) S

cmp a, 0

jg L1

cmp b, 0

jle L2

L1: ...

; S

L2:

; if (a > 0 || b > 0 && c > 0) S

cmp a, 0

jg L1

cmp b, 0

jle L2

cmp c, 0

jle L2

L1: ...

; S

L2:

; while (x > 0) do S

L1: cmp x, 0 jle L2

...

; S

jmp L1

L2:

; do S while (x > 0)

 

L: ...

; S

cmp x, 0

jg L

4.3. Команды управления циклом

4.3.1. Команда LOOP

Команда LOOP позволяет организовать цикл с известным числом повторений:

mov ecx, n

L:...

...

loop L

Команда LOOP требует, чтобы в качестве счётчика цикла использовался регистр ECX. Собственно, команда LOOP вычитает единицу именно из этого регистра, сравнивает полученное значение с нулём и осуществляет переход на указанную метку, если значение в регистре ECX больше 0. Метка определяет смещение перехода, которое не может превышать 128 байт.

При использовании команды LOOP следует также учитывать, что с её помощью реализуется цикл с постусловием, следовательно, тело цикла выполняется хотя бы один раз. Хуже того, если до начала цикла записать в регистр ECX значение 0, то при вычитании единицы, которое выполняется до сравнения с нулём, в регистре ECX окажется ненулевое значение, и цикл будет выполняться 232 раз.

Команда LOOP не относится к самым быстрым командам. В большинстве случаев её можно заменить последовательностью других команд.

4.3.2. Команды LOOPE/LOOPZ и LOOPNE/LOOPNZ

Эти команды похожи на команду LOOP, но позволяют также организовать и досрочный выход из цикла.

LOOPE <метка> ; Команды являются синонимами

LOOPZ <метка>

Действие этой команды можно описать следующим образом: ECX = ECX - 1; if (ECX != 0 && ZF == 1) goto <метка>;