КулЛиб - Классная библиотека! Скачать книги бесплатно 

Ассемблер в примерах и задачах [Наталья Юрьевна Добровольская] (pdf) читать онлайн

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]
О.В. Гаркуша
Н.Ю. Добровольская

Ассемблер
в примерах и задачах

Краснодар
2022

Министерство науки и высшего образования
Российской Федерации
КУБАНСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ

О.В. ГАРКУША
Н.Ю. ДОБРОВОЛЬСКАЯ

АССЕМБЛЕР В ПРИМЕРАХ
И ЗАДАЧАХ
Учебное пособие

Краснодар
2022

УДК 004.431.4
ББК 32.973.2
Г 204
Рецензенты:
Доктор физико-математических наук, профессор
Е.Н. Калайдин
Кандидат физико-математических наук, доцент
С.Е. Рубцов
Гаркуша, О.В., Добровольская, Н.Ю.
Г 204
Ассемблер в примерах и задачах: учебное пособие /
О.В. Гаркуша, Н.Ю. Добровольская; Министерство науки и
высшего образования Российской Федерации, Кубанский
государственный университет. − Краснодар: Кубанский гос.
ун-т, 2022. − 134 с. − 500 экз.
ISBN 978-5-8209-2052-3
Изложены
фундаментальные
темы:
организация
современного компьютера, устройство процессоров семейства
IA-32,
синтаксис
языка
ассемблера,
макросредства,
программирование типовых управляющих структур, сложные
структуры данных, оптимизация программ. Приведены
многочисленные примеры, иллюстрирующие материал.
Адресуется студентам факультета компьютерных технологий
и
прикладной
математики,
изучающим
основы
программирования.

УДК 004.431.4
ББК 32.973.2
ISBN 978-5-8209-2052-3

© Кубанский государственный
университет, 2022
© Гаркуша О.В.,
© Добровольская Н.Ю., 2022

ВВЕДЕНИЕ

ВВЕДЕНИЕ
Изучение архитектуры современных ПК, программирование
на машинно-ориентированном языке — необходимая часть
подготовки профессиональных программистов. Знание языка
ассемблера позволяет лучше понять принципы работы ЭВМ,
операционных систем и трансляторов с языков высокого уровня,
разрабатывать высокоэффективные программы.
Masm32 – специализированный пакет для программирования
на языке ассемблера IA-32. Являясь продуктом фирмы Microsoft,
он максимально приспособлен для создания Windows-приложений
на ассемблере. Кроме транслятора, компоновщика и необходимых
библиотек пакет Masm32 включает сравнительно простой
текстовый редактор и некоторые инструменты, предназначенные
для облегчения программирования на ассемблере. Однако набор
инструментов не содержит 32-разрядного отладчика и
предполагает работу в командном режиме, что не очень удобно.
Для
создания
программ
можно
использовать
специализированную интегрированную среду RADAsm, которая
помимо других ассемблеров позволяет использовать Masm32.
Точнее, используется специально настроенная среда – «сборка»
RADAsm + OlleDBG, где OlleDBG – 32-разрядный отладчик.
В учебном пособии рассматривается последовательность
действий при разработке приложений на ассемблере в среде
RADAsm, кроме того, указываются особенности архитектуры
процессоров семейства IA-32.

3

Ассемблер в примерах и задачах

1. НАЧАЛО РАБОТЫ СО СРЕДОЙ
Программная среда инициируется запуском программы
RadASM.exe.
Для создания нового проекта необходимо выбрать пункт
меню File > New Project, после чего на экране появится первое окно
Мастера создания проекта.
В этом окне необходимо выбрать тип проекта – в нашем
случае Console App (консольное приложение), а также ввести его
имя, например, HelloWord, описание, например, «Привет мир», и
путь к создаваемой средой новой папке с именем проекта.

В следующем окне Мастера выбирается шаблон проекта
(SIOConsoleApp.tpl), специально созданный для лабораторных
работ шаблон консольного приложения.

4

ВВЕДЕНИЕ

Далее предлагается выбрать типы создаваемых файлов —
выбираем Asm (исходные файлы ассемблера), Inc (подключаемые
библиотеки) и создаваемые папки — выбираем папку Bak для
хранения предыдущих версий файлов.

По окончании создания проекта Мастер определяет
доступные для работы с проектом пункты меню запуска
приложения.
5

Ассемблер в примерах и задачах

В этом окне рекомендуем использовать настройки по
умолчанию.

Полученный шаблон консольного приложения Windows
содержит:
− директивы, определяющие набор команд и модель
памяти;
− директивы подключения библиотек;
− разделы констант, инициализированных данных с
минимально необходимыми директивами определения данных;
6

Формирование исполняемого приложения

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

1.1. ФОРМИРОВАНИЕ ИСПОЛНЯЕМОГО ПРИЛОЖЕНИЯ
Для запуска шаблона необходимо выполнить:
− трансляцию Make > Assemble;
− компоновку Make > Link;
− запуск на выполнение Make > Run.

В процессе трансляции (ассемблирования) исходная
программа на ассемблере преобразуется в двоичный эквивалент.
7

Ассемблер в примерах и задачах

Если трансляция проходит без ошибок, то в окне Output, которое
появляется под окном программы, выводится текст:
C:\Masm32\Bin\ML.EXE /c /coff /Cp /nologo
/I"C:\Masm32\Include" "HelloWord.asm"
Assembling: HelloWord.asm
Make finished.
Total compile time 297 ms

Окно Output появляется на время ассемблирования и
закрывается. Чтобы повторно увидеть результаты, необходимо
курсор мыши перевести в нижнюю часть активного окна среды
RadASM.
Первая строка сообщения об ассемблировании — вызов
ассемблера:
C:\Masm32\Bin\ML.EXE — полное имя файла транслятора
ассемблера masm32 (путь + имя), за которым следуют опции:
/c — заказывает ассемблирование без автоматической
компоновки;
/coff — определяет формат объектного модуля Microsoft
(coff);
/Cp — означает сохранение регистра строчных и прописных
букв всех идентификаторов программы;
/nologo — осуществляет подавление вывода сообщений на
экран в случае успешного завершения ассемблирования;
/I"C:\Masm32\Include" — определяет местонахождение
вставляемых (.inc) файлов;
"HelloWord.asm" — имя обрабатываемого файла.
Остальные строки — сообщение о начале и завершении
процесса ассемблирования и времени выполнения этого процесса.
Результатом нормального завершения ассемблирования
является создание файла, содержащего объектный модуль
программы, — файла HelloWord.obj.
Если при ассемблировании обнаружены ошибки, то
объектный модуль не создается и после сообщения о начале
ассемблирования идут сообщения об ошибках, например:
HelloWord.asm(14):error A2006: undefined symbol: EDY
8

Формирование исполняемого приложения

В сообщении указывается:
− номер строки исходного текста (в скобках);
− номер ошибки, под которым она описана в документации;
− возможная причина.
После исправления ошибок процесс ассемблирования
повторяют.
Следующий этап — компоновка программы. На этом этапе к
объектному (двоичному) коду программы добавляются объектные
коды используемых процедур. При этом в тех местах программы,
где происходит вызов процедур, указывается их относительный
адрес в модуле. Сведения о компоновке также выводятся в окно
Output:
C:\Masm32\Bin\LINK.EXE /SUBSYSTEM:CONSOLE /RELEASE
/VERSION:4.0 /LIBPATH:"C:\Masm32\Lib"
/OUT:"HelloWord.exe" "HelloWord.obj"
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights
reserved.
Make finished.
Total compile time 109 ms

Первая строка вывода также является командной строкой
вызова компоновщика:
C:\Masm32\Bin\LINK.EXE — полное имя компоновщика, за
которым следуют опции:
/SUBSYSTEM:CONSOLE — подключить стандартное окно
консоли;
/RELEASE — создать реализацию (а не отладочный вариант);
/VERSION:4.0 — минимальная версия компоновщика;
/LIBPATH:"C:\Masm32\Lib" — путь к файлам библиотек;
/OUT:"HelloWord.exe" — имя результата компоновки —
загрузочного файла и параметр "HelloWord.obj" — имя
объектного файла.
После устранения ошибки программу необходимо
перетранслировать и заново скомпоновать.
Если процессы трансляции и компоновки прошли нормально,
то ее можно запустить на выполнение. При этом открывается окно
консоли, в которое выводится строка запроса.
9

Ассемблер в примерах и задачах

Окно закрывается при нажатии любой клавиши.
1.2. СТРУКТУРА ПРОГРАММЫ НА ЯЗЫКЕ АССЕМБЛЕРА
Программа
структуру:

на

языке

ассемблера

имеет

следующую

.686
.model flat, stdcall
option casemap: none
.data

.data?

.const

.code


end

Директива .686 указывает компилятору ассемблера, что
необходимо
использовать
набор
операций
процессора
определённого поколения.
Директива .model позволяет указывать используемую
модель памяти и соглашение о вызовах. Как уже было сказано, на
архитектуре Win32 используется только одна модель памяти –
flat, что и указано в приведённом примере. Соглашения о вызовах
определяют порядок передачи параметров и порядок очистки
стека.

10

Структура программы на языке ассемблера

Директива option casemap: none заставляет компилятор
языка ассемблера различать большие и маленькие буквы в метках
и именах процедур.
Директивы .data, .data?, .const и .code определяют то,
что называется секциями. В Win32 нет сегментов, но адресное
пространство можно поделить на логические секции. Начало
одной секции отмечает конец предыдущей. Есть две группы
секций: данных и кода.
Секция .data содержит инициализированные данные
программы.
Секция .data? содержит неинициализированные данные
программы. Иногда нужно только предварительно выделить
некоторое количество памяти, не инициализируя её. Эта секция
для
этого
и
предназначается.
Преимущество
неинициализированных данных в том, что они не занимают места
в исполняемом файле. Вы всего лишь сообщаете компилятору,
сколько места вам понадобится, когда программа загрузится в
память.
Секция
.const
содержит
объявления
констант,
используемых программой. Константы не могут быть изменены.
Попытка изменить константу вызывает аварийное завершение
программы.
Задействовать все три секции не обязательно.
Есть только одна секция для кода: .code. В ней содержится
весь код.
Предложения и end устанавливают
границы кода. Обе метки должны быть идентичны. Весь код
должен располагаться между этими предложениями.
2. ЯЗЫК АССЕМБЛЕРА
Программа, написанная символическими мнемокодами,
которые используются в языке ассемблер (ЯА), представляет
собой исходный модуль. Для формирования исходного модуля
применяют любой текстовый редактор. Затем программу подают
на вход специальному транслятору, называемому ассемблером,
который переводит ее на машинный язык, и далее полученную
машинную программу выполняют.
11

Ассемблер в примерах и задачах

При описании синтаксиса ЯА мы будем использовать
формулы Бэкуса – Наура (БНФ) со следующими дополнениями:
− в квадратных скобках будем указывать конструкции,
которые можно опускать; например, запись А[В]С означает либо
текст ABC, либо текст АС;
− в фигурные скобки будем заключать конструкции,
которые могут быть повторены любое число раз, в том числе и ни
разу; например, запись А{ВС} означает любой из следующих
текстов: А, АВС, АВСВС, АВСВСВС и т. д.
2.1. РЕГИСТРЫ ПРОЦЕССОРОВ СЕМЕЙСТВА IA-32
К регистрам общего назначения (РОН) относится группа из 8
регистров, которые можно использовать в программе на языке
ассемблера. Все регистры имеют размер 32 бита и могут быть
разделены на 2 части или более.
Регистры данных (32 разряда)
AH
BH
CH
DH
SI
DI
BP
SP

AL
BL
CL
DL

EAX
EBX
ECX
EDX
ESI
EDI
EBP
ESP

Регистры EAX, EBX, ECX и EDX позволяют обращаться как к
младшим 16 битам (по именам AX, BX, CX и DX), так и к двум
младшим байтам по отдельности (по именам AH/AL, BH/BL, CH/CL
и DH/DL).
Регистры ESI, EDI, ESP и EBP позволяют обращаться к
младшим 16 битам по именам SI, DI, SP и BP соответственно.
Названия регистров происходят от их назначения:
EAX/AX/AH/AL (accumulator register) — аккумулятор;
EBX/BX/BH/BL (base register) — регистр базы;
ECX/CX/CH/CL (counter register) — счётчик;
EDX/DX/DH/DL (data register) — регистр данных;
ESI/SI (source index register) — индекс источника;
12

Регистры процессоров семейства IA-32

EDI/DI (destination index register) — индекс приёмника

(получателя);
ESP/SP (stack pointer register) — регистр указателя стека;
EBP/BP (base pointer register) — регистр указателя базы стека.
Несмотря на существующую специализацию, все регистры
можно использовать в любых машинных операциях. Однако надо
учитывать тот факт, что некоторые команды работают только с
определёнными регистрами. Например, команды умножения и
деления используют регистры EAX и EDX для хранения исходных
данных и результата операции. Команды управления циклом
используют регистр ECX в качестве счётчика цикла.
Ещё один нюанс состоит в использовании регистров в
качестве базы, т.е. хранилища адреса оперативной памяти. В
качестве регистров базы можно использовать любые регистры, но
желательно использовать регистры EBX, ESI, EDI или EBP. В этом
случае размер машинной команды обычно бывает меньше.
К сожалению, количество регистров катастрофически мало,
и зачастую бывает трудно подобрать способ их оптимального
использования.
Любые регистры общего назначения могут использоваться
для сложения и вычитания как 8-, 16-, так и 32-битовых значений.
2.1.1. Сегментные регистры CS, DS, SS и ES
Процессор имеет 6 так называемых сегментных регистров:
CS, DS, SS, ES, FS и GS. Их существование обусловлено спецификой
организации и использования оперативной памяти.
16-битные регистры могли адресовать только 64 Кб
оперативной памяти, что явно недостаточно для более или менее
приличной программы. Поэтому память программе выделялась в
виде нескольких сегментов, которые имели размер 64 Кб. При
этом абсолютные адреса были 20-битными, что позволяло
адресовать уже 1 Мб оперативной памяти. Для решения задачи
адресации 20-битных адресов 16-битными регистрами адрес
разбивался на базу и смещение. База –адрес начала сегмента, а
смещение –номер байта внутри сегмента. На адрес начала
сегмента накладывалось ограничение – он должен быть кратен 16.
При этом последние 4 бита были равны 0 и не хранились, а
13

Ассемблер в примерах и задачах

подразумевались. Таким образом, получались две 16-битные части
адреса. Для получения абсолютного адреса к базе добавлялись
четыре нулевых бита, и полученное значение складывалось со
смещением.
Сегментные регистры использовались для хранения адреса
начала сегмента кода (CS – code segment), сегмента данных (DS
— data segment) и сегмента стека (SS – stack segment).
Регистры ES, FS и GS были добавлены позже. Существовало
несколько моделей памяти, каждая из которых подразумевала
выделение программе одного или нескольких сегментов кода и
одного или нескольких сегментов данных: tiny, small, medium,
compact, large и huge. Для команд языка ассемблера существовали
определённые соглашения: адреса перехода сегментировались по
регистру CS, обращения к данным сегментировались по регистру
DS, а обращения к стеку – по регистру SS. Если программе
выделялось несколько сегментов для кода или данных, то
приходилось менять значения в регистрах CS и DS для обращения
к другому сегменту. Существовали так называемые «ближние» и
«дальние» переходы. Если команда, на которую надо совершить
переход, находилась в том же сегменте, то для перехода
достаточно было изменить только значение регистра IP. Такой
переход назывался ближним. Если же команда, на которую надо
совершить переход, находилась в другом сегменте, то для
перехода необходимо было изменить как значение регистра CS, так
и значение регистра IP. Такой переход назывался дальним и
осуществлялся дольше.
32-битные регистры позволяют адресовать 4 Гб памяти, что
уже достаточно для любой программы. Каждую Win32-программу
Windows запускает в отдельном виртуальном пространстве. Это
означает, что каждая Win32-программа будет иметь
4-гигабайтовое адресное пространство, но вовсе не означает, что
каждая программа имеет 4 Гб физической памяти, а только то, что
программа может обращаться по любому адресу в этих пределах.
А Windows сделает все необходимое, чтобы память, к которой
программа обращается, «существовала».
Под архитектурой Win32 отпала необходимость в разделении
адреса на базу и смещение и необходимость в моделях памяти. На
32-битной архитектуре существует только одна модель памяти —
14

Регистры процессоров семейства IA-32

flat (сплошная или плоская). Сегментные регистры остались, но
используются по-другому.
2.1.2. Регистр командного указателя EIP
Регистр указателя команд EIP1 содержит смещение на
команду, которая должна быть выполнена следующей.
EIP d

CS →

Сегмент команд

d
IP →

Текущая команда

2.1.3. Регистр флагов
Флаг — это бит, принимающий значение 1 («флаг
установлен»), если выполнено некоторое условие, и значение 0
(«флаг сброшен») в противном случае. Процессор имеет регистр
флагов, содержащий набор флагов, отражающий текущее
состояние процессора.
31

22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
I
V V
Зарезервированы I
A V R
N O
O D I T S Z
A
P
C
I I
0
0
0
1
C MF
F
F
F
(установлены в 0) D
T P
F F F F F F
P P
L
FLAGS



Флаг

Название

Описание

Тип флага

FLAGS
0

CF

1

1

1

Carry Flag

Флаг переноса
Зарезервирован

Instruction pointer – указатель команд.
15

Состояние

Ассемблер в примерах и задачах



Флаг

Название

Описание

Parity Flag Флаг чётности

Тип флага
Состояние

2

PF

3

0

4

AF

5

0

6

ZF

Zero Flag

Флаг нуля

Состояние

7

SF

Sign Flag

Флаг знака

Состояние

8

TF

Trap Flag

Флаг трассировки

Системный

9

IF

10

DF

11

OF

12
13

IOPL

14

NT

15

0

Зарезервирован
Auxiliary
Carry Flag

Вспомогательный
флаг переноса

Состояние

Зарезервирован

Interrupt
Флаг разрешения
Enable Flag прерываний
Direction
Флаг направления
Flag
Overflow
Флаг переполнения
Flag
I/O
Уровень приоритета
Privilege
ввода-вывода
Level
Nested Task Флаг вложенности
задач

Системный
Управляющий
Состояние
Системный
Системный

Зарезервирован

EFLAGS
16

RF

Resume Flag Флаг возобновления

Системный

17

VM

Virtual8086 Mode

Режим виртуального
процессора 8086

Системный

18

AC

Alignment
Check

Проверка
выравнивания

Системный

VIF

Virtual
Interrupt
Flag

Виртуальный флаг
разрешения
прерываний

Системный

VIP

Virtual
Interrupt
Pending

Ожидающее
виртуальное
прерывание

Системный

19

20

16

Регистры процессоров семейства IA-32


21

Флаг
ID

Название
ID Flag

Описание
Проверка на
доступность
инструкции CPUID

Тип флага
Системный

22
...
31

Зарезервированы

Значение флагов CF, DF и IF можно изменять напрямую в
регистре флагов с помощью специальных инструкций (например,
CLD для сброса флага направления), но нет инструкций, которые
позволяют обратиться к регистру флагов как к обычному регистру.
Однако можно сохранять регистр флагов в стек или регистр AH и
восстанавливать регистр флагов из них с помощью инструкций
LAHF, SAHF, PUSHF, PUSHFD, POPF и POPFD.
Флаги состояния (биты 0, 2, 4, 6, 7 и 11) отражают результат
выполнения арифметических инструкций, таких как ADD, SUB,
MUL, DIV.
− Флаг переноса CF устанавливается при переносе из
старшего значащего бита / заёма в старший значащий бит и
показывает наличие переполнения в беззнаковой целочисленной
арифметике. Также используется в длинной арифметике;
− Флаг чётности PF устанавливается, если младший
значащий байт результата содержит чётное число единичных
битов. Изначально этот флаг был ориентирован на использование
в коммуникационных программах: при передаче данных по
линиям связи для контроля мог также передаваться бит чётности и
инструкции для проверки флага чётности облегчали проверку
целостности данных;
− Вспомогательный флаг переноса AF устанавливается при
переносе из бита 3-го результата / заёма в 3-й бит результата. Этот
флаг ориентирован на использование в двоично-десятичной
(binary coded decimal, BCD) арифметике;
− Флаг нуля ZF устанавливается, если результат равен нулю;

17

Ассемблер в примерах и задачах

− Флаг знака SF равен значению старшего значащего бита
результата, который является знаковым битом в знаковой
арифметике;
− Флаг
переполнения
OF
устанавливается,
если
целочисленный результат слишком длинный для размещения в
целевом операнде (регистре или ячейке памяти). Этот флаг
показывает наличие переполнения в знаковой целочисленной
арифметике.
Из перечисленных флагов только флаг CF можно изменять
напрямую с помощью инструкций STC, CLC и CMC.
Флаги состояния позволяют одной и той же арифметической
инструкции выдавать результат трёх различных типов:
беззнаковое, знаковое и двоично-десятичное (BCD) целое число.
Если результат считать беззнаковым числом, то флаг CF
показывает условие переполнения (перенос или заём), для
знакового результата перенос или заём показывает флаг OF, а для
BCD-результата перенос / заём показывает флаг AF. Флаг SF
отражает знак знакового результата, флаг ZF – и беззнаковый, и
знаковый нулевой результат.
В длинной целочисленной арифметике флаг CF используется
совместно с инструкциями сложения с переносом (ADC) и
вычитания с заёмом (SBB) для распространения переноса или заёма
из одного вычисляемого разряда длинного числа в другой.
Инструкции условного перехода Jcc (переход по условию
cc), SETcc (установить значение байта-результата в зависимости
от условия cc), LOOPcc (организация цикла) и CMOVcc (условное
копирование) используют один или несколько флагов состояния
для проверки условия. Например, инструкция перехода JLE (jump
if less or equal – переход, если «меньше или равно»)
проверяет условие «ZF = 1 или SF ≠ OF».
Флаг PF был введён для совместимости с другими
микропроцессорными архитектурами и по прямому назначению
используется редко. Более распространено его использование
совместно с остальными флагами состояния в арифметике с
плавающей запятой: инструкции сравнения (FCOM, FCOMP и т. п.) в
математическом сопроцессоре устанавливают в нём флагиусловия C0, C1, C2 и C3, и эти флаги можно скопировать в регистр
18

Регистры процессоров семейства IA-32

флагов. Для этого рекомендуется использовать инструкцию
FSTSW AX для сохранения слова состояния сопроцессора в регистре
AX и инструкцию SAHF для последующего копирования
содержимого регистра AH в младшие 8 битов регистра флагов, при
этом C0 попадает во флаг CF, C2 – в PF, а C3 – в ZF. Флаг C2
устанавливается, например, в случае несравнимых аргументов
(NaN или неподдерживаемый формат) в инструкции сравнения
FUCOM.
Флаг направления DF (бит 10 в регистре флагов) управляет
строковыми инструкциями (MOVS, CMPS, SCAS, LODS и STOS) –
установка флага заставляет уменьшать адреса (обрабатывать
строки от старших адресов к младшим), обнуление заставляет
увеличивать адреса. Инструкции STD и CLD соответственно
устанавливают и сбрасывают флаг DF.
Системные флаги и поле IOPL управляют операционной
средой и не предназначены для использования в прикладных
программах.
− Флаг разрешения прерываний IF – обнуление этого флага
запрещает отвечать на маскируемые запросы на прерывание;
− Флаг трассировки TF – установка этого флага разрешает
пошаговый режим отладки, когда после каждой выполненной
инструкции происходит прерывание программы и вызов
специального обработчика прерывания;
− поле IOPL показывает уровень приоритета ввода-вывода
исполняемой программы или задачи: чтобы программа или задача
могла выполнять инструкции ввода-вывода или менять флаг IF, её
текущий уровень приоритета (CPL) должен быть ≤ IOPL;
− Флаг вложенности задач NT – устанавливается, когда
текущая задача «вложена» в другую, прерванную задачу, и
сегмент состояния TSS текущей задачи обеспечивает обратную
связь с TSS предыдущей задачи. Флаг NT проверяется инструкцией
IRET для определения типа возврата – межзадачного или
внутризадачного;
− Флаг возобновления RF используется для маскирования
ошибок отладки;
− VM – установка этого флага в защищённом режиме
вызывает переключение в режим виртуального 8086.
19

Ассемблер в примерах и задачах

− Флаг проверки выравнивания AC – установка этого флага
вместе с битом AM в регистре CR0 включает контроль
выравнивания операндов при обращениях к памяти: обращение к
невыровненному операнду вызывает исключительную ситуацию;
− VIF – виртуальная копия флага IF; используется совместно
с флагом VIP;
− VIP – устанавливается для указания наличия отложенного
прерывания;
− ID – возможность программно изменить этот флаг в
регистре флагов указывает на поддержку инструкции CPUID.
2.2. ФОРМАТЫ МАШИННЫХ КОМАНД IA-32
2.2.1. Формат RR «регистр – регистр»
КОП r1, r2

Команды этого формата описывают действие r1 = rl * r2
или r2 = r2 * r1, где r1 и r2 – регистры общего назначения.
Поле КОП (код операции) задает конкретную операцию (*).
2.2.2. Формат RS «регистр – память»
КОП reg, adr

Эти команды описывают операции
reg = reg * adr
или adr = adr * reg,
где reg – регистр, a adr – адрес ячейки памяти.

2.2.3. Формат RI «регистр – непосредственный операнд»
Размер команд этого формата 3–4 байта.
КОП

s w

11

КОП' reg

I (1 – 2 б)

Команды этого формата описывают операции
reg = reg * I,
где reg – регистр, a I – непосредственный операнд.

20

Идентификаторы

2.2.4. Формат SI «память – непосредственный операнд»
КОП adr, I

Команды этого формата описывают операции типа
adr = adr * I.

Смысл всех полей тот же, что и в предыдущих форматах.
Несомненно, записывать машинные команды ПК в цифровом
виде с использованием перечисленных форматов команд
достаточно сложно. Более высоким уровнем кодирования является
уровень ассемблера, в котором программист пользуется
символическими мнемокодами вместо машинных команд и
описательными именами для полей данных и адресов памяти.
2.3. ИДЕНТИФИКАТОРЫ
Понятие идентификатора в языке ассемблера ничем не
отличается от понятия идентификатора в других языках. Можно
использовать латинские буквы, цифры и знаки _ . ? @ $, причём
точка может быть только первым символом идентификатора.
Большие и маленькие буквы считаются эквивалентными.
2.4. ЦЕЛЫЕ ЧИСЛА
В программе допускается использование целых чисел в
десятичной, двоичной, восьмеричной и шестнадцатеричной
системах счисления. Десятичные числа записываются как обычно,
а при записи чисел в других системах в конце числа ставится
спецификатор – буква, которая указывает, в какой системе
записано это число: в конце двоичного числа ставится буква b
(binary), в конце восьмеричного числа – буква о (octal) или буква
q, в конце шестнадцатеричного числа – буква h (hexadecimal).
Ради общности спецификатор, а именно букву d (decimal),
разрешается указывать и в конце десятичного числа, но обычно
этого не делают.

21

Ассемблер в примерах и задачах

Примеры записи чисел:
10-чные
25, +4,
–386d

2-чные
101b,
–11000b

8-ричные
74q,
–74q

16-ричные
1AFh,
–1AFh

В случае использования шестнадцатеричных чисел
необходимо применение следующего требования. Если
шестнадцатеричное число начинается с цифры A – F, то в начале
числа обязательно должен быть записан хотя бы один незначащий
ноль: 0A5h — число, A5h — идентификатор. Такая запись
позволяет определить, что рассматривается — число или
идентификатор.
2.5. ВНУТРЕННЕЕ ПРЕДСТАВЛЕНИЕ ДАННЫХ
Система команд ПК поддерживает работу с числами только
размером байт и слово и частично размером в двойное слово.
Целые беззнаковые числа полностью используют
соответствующее поле для представления значения числа в
двоичной системе счисления. Для ячейки из k разрядов можно
представить 2k различных комбинаций.
Поэтому в байте можно представить целые числа 0–255
8
(2 – 1), в слове: 0–65535 (216 – 1), в двойном слове: 0–
4 294 967 295 (232 – 1).
Знаковые целые числа представлены в дополнительном коде.
Дополнительный код целого числа a:
 a2, a ≥ 0
D(a ) =  k
 2 − a 2,a < 0

(

)

Рассмотрим получение дополнительного кода числа –11 в
поле байт:
28 – 11 = 245(10) = F5(16) = 11110101(2).

Или иначе:
Прямой код
Обратный код

11 0 0 0 0 0 1 0 1 1

0B

1 1 1 1 1 0 1 0 0

F4

1

1

Дополнительный код –11 1 1 1 1 1 0 1 0 1

F5

+

Получим сумму 11 + ( – 11 ) = 0:
22

Внутреннее представление данных

11
–11

0 0 0 0 0 1 0 1 1
1 1 1 1 1 0 1 0 1
1 0 0 0 0 0 0 0 0 0

0B
F5
0

При сложении положительных чисел можем получить
отрицательное значение. Так, например, в языке C++ возможна
такая ситуация:
#include
#include
using namespace std;
int main()
{
int a = pow(2, 31)-1;
int b = 1;
int c = a + b;
printf("a=%11d \n", a);
printf("b=%11d \n", b);
printf("c=%11d \n \n", c);
printf("a=%11x \n", a);
printf("b=%11x \n", b);
printf("c=%11x \n", c);
return 0;
}

Далее представлен результат работы программы как в
10-чном, так и в 16-ричном виде. При представлении в 16-ричном
виде легко заметить, что изменяется значение знакового бита, т.е.
число становится отрицательным.

Числа, размером слово или двойное слово, представляются
аналогично. Однако в памяти байты полей хранятся в «обратном
порядке». Так, двухбайтовое число –11 будет иметь вид FFF5, а
храниться в памяти так (A — адрес слова)
23

Ассемблер в примерах и задачах

A
F5

A+1
FF

При этом в регистрах числа размером слово хранится в
«нормальном» порядке.
BH
FF

BX

BL
F5

Для отображения данных в памяти или регистрах будем
использовать шестнадцатеричную систему счисления.
Например, число 12345678h хранится в памяти так:
A
78

A+1
56

A+2
34

A+3
12

2.6. СИМВОЛЬНЫЕ ДАННЫЕ
Символы (последовательности символов) заключаются либо
в одинарные, либо в двойные кавычки: 'А' или "А"; 'А+B' или
"А+В".

В качестве символов можно использовать русские буквы.
В строках одноименные большие и малые буквы не
отождествляются ('А+В' и 'а+B' — разные строки).
2.7. ОПИСАНИЕ ДАННЫХ
Все данные, используемые в программах на ассемблере,
обязательно должны быть объявлены с использованием
соответствующих директив, которые определяют тип данных и
количество байт, необходимое для размещения этих данных в
памяти:
[] [ DUP(
[])]
где: — имя поля данных, которое может не присваиваться;
— команда, объявляющая тип описываемых

данных;
— используется при описании
повторяющихся данных, тогда константа
определяет количество повторений;



DUP

24

Описание данных

— последовательность
инициализирующих констант через запятую
или символ «?», если инициализирующее
значение не определяется.
Директивы определения данных:



Директива
BYTE / SBYTE
WORD / SWORD
DWORD / SDWORD

Описание типа данных
8-разрядное целое без знака / со знаком
16-разрядное целое без знака / со знаком
32-разрядное целое без знака / со знаком или
ближний указатель
48-разрядное целое или дальний указатель
64-разрядное целое
80-разрядное целое
32-разрядное короткое вещественное
64-разрядное длинное вещественное
80-разрядное расширенное вещественное

FWORD
QWORD
TBYTE
REAL4
REAL8
REAL10

В качестве директив также могут применяться:
− DB – определить байт;
− DW – определить слово;
− DD – определить двойное слово (4 байта);
− DQ – определить четыре слова (8 байт);
− DT – определить 10 байт.
Например:
A
В
С

DB
DB
DB

254
-2
17h

; 0FEh
; 0FEh (= 256 – 2 = 254)
; 17h

Директива
может
содержать
несколько
констант,
разделенных запятыми и ограниченных только длиной строки.
М

DB
DB
DB
DB

2
-2
?
'*'

А можно указать:
М

DB

2, -2, ?, '*'

Ассемблер
определяет
эти
константы
в
виде
последовательности смежных байтов и записывает в эти байты
25

Ассемблер в примерах и задачах

значения операндов (для операнда ? ничего не записывает). В
нашем примере ассемблер следующим образом заполнит память:
M
2

M+1
FE

M+2

M+3
2A

В массивах имя дается только первому элементу, а остальные
оставляют безымянными. Если в директиве DB не указано имя, то
по ней байт в памяти отводится, но он остается безымянным.
Например, для описания байтового массива R из 8 элементов
с начальным значением 0 для каждого из них это можно сделать
так:
R DB 0, 0, 0, 0, 0, 0, 0, 0

Или эту директиву можно записать короче:
R DB 8 DUP(0)

В общем случае эта конструкция имеет следующий вид:
A DB 4 DUP(1, 2) ; 1, 2, 1, 2, 1, 2, 1, 2
A DB 20 DUP(30 DUP(?))

Здесь резервируется 600 байт, значения которых
неопределенны. Их можно рассматривать как место, отведенное
для хранения элементов матрицы А размером 20 × 30, в которой
элементы расположены в памяти следующим образом: первые 30
байтов – элементы первой строки матрицы, следующие 30 байтов
– элементы второй строки и т. д.
Например:
В DW 1234h
С DW -2

При
размещении
числовых
констант
ассемблер
автоматически меняет местами значения старшего и младшего
байтов.
На ЯА такие числа записываются в нормальном,
неперевернутом виде, а «переворачиванием» их занимается сам
ассемблер, поэтому по нашим двум директивам память заполнится
следующим образом:
26

Директивы эквивалентности и присваивания

B
34

12

C
FE

FF

Символьный операнд в DW ограничен двумя символами,
которые ассемблер представляет в «перевернутом» виде.
2.8. ДИРЕКТИВЫ ЭКВИВАЛЕНТНОСТИ И ПРИСВАИВАНИЯ
Рассмотрим, как в ЯА описываются константы. Это делается
с помощью директивы эквивалентности.
2.8.1. Директива EQU1
Имеет следующий синтаксис:
EQU

Здесь обязательно должны быть указаны имя и только один
операнд.
Эта директива аналогична описанию константы в языке
Паскаль:
Сonst = ;

С ее помощью программист информирует ассемблер о
способе интерпретации некоторого имени.
Возможны три основных способа задания операндов:
Операнд — константное выражение
A EQU 10

В этом случае все последующие вхождения имени A в
программу ассемблер заменяет на 10.
X DB A DUP(?)
Y DB A*5+1 ; резервируется 51 байт

Операнд — имя
A EQU N
1

Equal – равно.
27

Ассемблер в примерах и задачах

В этом случае имена A и N являются синонимами.
Операнд — произвольный текст, не являющийся
константным выражением или именем. В этом случае всякое
вхождение имени ассемблер заменяет на соответствующий текст.
Отметим, что директива EQU носит чисто информационный
характер, по ней ассемблер ничего не записывает в машинную
программу. Поэтому директиву EQU можно ставить в любое место
программы — и между командами, и между описаниями
переменных, и в других местах.
Примеры:
HELLO EQU ‘Большой привет’
LX EQU Х + (N – 1)
WP EQU WORD PTR

В данном случае считается, что указанное имя обозначает
операнд в том виде, как он записан (операнд не вычисляется).
Именно на этот текст и будет заменяться каждое вхождение
данного имени в программу. Например, следующие предложения
эквивалентны
PR

DB
NEG
INC

HELLO, ‘!’ ≡ PR EQU ‘Большой привет’,’!’
LX

NEG X+(N-1)
WP[BX]

INC WORD PTR[BX]

Такой вариант директивы EQU обычно используется для того,
чтобы ввести более короткие обозначения для часто
встречающихся длинных текстов. Введя короткое имя, мы далее в
программе можем им пользоваться, а уж ассемблер сам будет его
заменять на соответствующий текст.
Отметим, что текст, указанный в правой части директивы
EQU, должен быть сбалансирован по скобкам и кавычкам и не
должен содержать вне скобок и кавычек символа «;». Кроме этого,
поскольку текст не вычисляется, то в нем можно использовать как
имена, описанные до этой директивы EQU, так и имена, описанные
после нее.

28

Директивы эквивалентности и присваивания

2.8.2. Директива присваивания
=

Директива определяет константу с именем, указанным в
левой части, и с числовым значением, равным значению
выражения справа. Но в отличие от констант, определенных по
директиве EQU, данная константа может менять свое значение,
принимая в тексте программы различные значения. Например:
K
N
A
K
A

= 1
EQU K ; N и K - синонимы
DB N ; A = 1
= 2
DB N ; A = 2

Директива присваивания, в отличие от директивы
эквивалентности, определяет только числовую константу. Кроме
того, если имя указано в левой части директивы EQU, то оно не
может появляться в левой части других директив (его нельзя
переопределять). А имя, появившееся в левой части директивы
присваивания, может снова появиться в начале другой такой
директивы (но только такой!). Поэтому ошибочными являются три
следующих фрагмента программы:
К EQU 1
К EQU 2

К EQU 1
К = 2

К = 1
К EQU 2

Появление в языке констант, которые могут менять свои
значения, вносит некоторую неопределенность. Рассмотрим, к
примеру, фрагмент слева:
K = 1
N EQU К
A DW N ; А = 1
К = 2
В DW N ; В = 2

K = 1
N EQU K + 10
С DW N
; С = 11
К = 2
D DW N
; D = 11

Таким образом, необходимо внести следующее уточнение в
действие EQU:
− если в правой части директивы указано имя константы, то
имя слева надо понимать не как имя константы (не как имя числа),
а как синоним имени справа;
29

Ассемблер в примерах и задачах

− если же в правой части указано любое другое константное
выражение, тогда имя слева действительно становится именем
константы (обозначением числа).
Что же касается директивы присваивания, то ее правая часть
всегда вычисляется сразу и полученное число тут же становится
новым значением константы.
2.9. СТРУКТУРА ПРОГРАММЫ
Программа на ЯА – это последовательность инструкций,
каждая из которых записывается в отдельной строке.
Инструкции ЯА делятся на три группы:
− комментарии;
− директивы;
− команды.
2.9.1. Комментарии
Использование комментариев в программе улучшает ее
ясность, поясняет смысл набора команд.
Комментарий начинается на любой строке программы со
знака «;» (точка с запятой) и ассемблер полагает в этом случае, что
все символы, находящиеся справа от «;», являются комментарием.
Комментарий может содержать любые символы, включая пробел.
Комментарий может занимать всю строку или следовать за
командой на той же строке. Например:
; Эта строка является комментарием
ADD AX, BX ; Комментарий вместе с командой

Комментарии появляются только в исходном коде
программы и не приводят к генерации машинных кодов, поэтому
можно включать любое количество комментариев, не влияя на
эффективность выполнения программы.
В ЯА допустим и многострочный комментарий. Он должен
начинаться со строки
COMMENT
30

Структура программы

В качестве маркера берется первый за словом COMMENT
символ, отличный от пробела; этот символ начинает комментарий.
Концом такого комментария считается конец первой из
последующих строк программы, в которой (в любой позиции)
снова встретился этот же маркер. Например:
comment * все
это является комментарием * и это тоже

Такой вид комментария позволяет временно исключить из
программы некоторый ее фрагмент (например, при отладке).
2.9.2. Директивы
Ассемблер имеет ряд операторов, которые позволяют
управлять процессом ассемблирования. Эти операторы
называются директивами. Они действуют только в процессе
ассемблирования программы и не генерируют машинных кодов.
Синтаксис директив следующий:
[] [] [;]

Пример:
A DW 200 ; число А

Названия директив, как и мнемокоды, – служебные слова.
2.9.3. Команды
Основной формат кодирования команд языка ассемблер
имеет следующий вид:
[:] [][;]

Метка (если имеется), команда и операнд (если имеется)
разделяются по крайней мере одним пробелом или символом
табуляции.
Примеры:
L1: MOV AX, 2

; изменение значения
31

Ассемблер в примерах и задачах

В качестве операндов команд языка ассемблера могут
использоваться:
− регистры, обращение к которым осуществляется по
именам;
− непосредственные операнды – константы, записываемые
непосредственно в команде;
− ячейки памяти – в команде записывается адрес нужной
ячейки.
Для задания адреса существуют следующие возможности:
− Имя переменной по сути является адресом этой
переменной. Встретив имя переменной в операндах команды,
компилятор понимает, что нужно обратиться к оперативной
памяти по определённому адресу. Обычно адрес в команде
указывается в квадратных скобках, но имя переменной является
исключением и может быть указано как в квадратных скобках, так
и без них. Например, для обращения к переменной x в команде
можно указать x или [x];
− если переменная была объявлена как массив, то к
элементу массива можно обратиться, указав имя и смещение. Для
этого существует ряд синтаксических форм, например:
[] и [ + ].

Однако следует понимать, что смещение – это вовсе не индекс
элемента массива. Индекс элемента массива – это его номер, и этот
номер не зависит от размера самого элемента. Смещение же
задаётся в байтах, и при задании смещения программист сам
должен учитывать размер элемента массива;
− адрес ячейки памяти может храниться в регистре. Для
обращения к памяти по адресу, хранящемуся в регистре, в команде
указывается имя регистра в квадратных скобках, например,
[EBX]. Как уже говорилось, в качестве регистров базы
рекомендуется использовать регистры EBX, ESI, EDI и EBP;
− адрес может быть вычислен по определённой формуле.
Для этого в квадратных скобках можно указывать достаточно
сложные выражения, например, [EBX+ECX] или [EBX+4*ECX].

32

Обозначения операндов команд

2.10. ОБОЗНАЧЕНИЯ ОПЕРАНДОВ КОМАНД
При описании команд будем пользоваться стандартными
сокращениями.
i8, i16, i32 Непосредственные операнды (т.е. задаваемые в самой
команде) длиной соответственно 8, 16 или 32 бита.
Регистры общего назначения.
r8 – байтовые регистры (AH, AL, BH и т.п.).
r8, r16, r32 r16 – регистры размером в слово (AX, BX, SI и т.п.).
r32 – регистры размером в двойное слово (EAX, EBX,
ESI и т.п.).
sr
Сегментные регистры (CS, DS, SS, ES).
m8, m16, m32 Адрес памяти соответствующей длины.

В описаниях команд языка ассемблера для обозначения
возможных операндов будем использовать эти сокращения.
Например:
ADD
ADD
ADD
ADD
ADD

r8/r16/r32,
r8/r16/r32,
r8/r16/r32,
m8/m16/m32,
m8/m16/m32,

r8/r16/r32
m8/m16/m32
i8/i16/i32
r8/r16/r32
i8/i16/i32

;
;
;
;
;

Регистр + Регистр
Регистр + Память
Регистр + неп.операнд
Память + Регистр
Память + Неп.операнд

Команды языка ассемблера обычно имеют 1 или 2 операнда
или не имеют операндов вообще. Во многих, хотя не во всех,
случаях операнды (если их два) должны иметь одинаковый размер.
Команды языка ассемблера обычно не работают с двумя ячейками
памяти.
Размер операндов при этомопределяется:
− объемом регистра, хранящего число – если хотя бы один
операнд находится в регистре;
− размером числа, заданным директивой определения
данных;
− специальными описателями, например, BYTE PTR (байт),
WORD PTR (слово) и DWORD PTR (двойное слово), если ни один
операнд не находится в регистре и размер операнда отличен от
размера, определенного директивой определения данных.
33

Ассемблер в примерах и задачах

3. ВВОД И ВЫВОД
Для организации ввода-вывода удобно использовать
библиотеку ввода вывода, которая была модифицирована из
библиотеки io.asm для 16-битной версии для TAsm студентами
2-го курса ФКТиПМ Кирий Георгием и Хурамшиным Рашидом
(io.asm — 2138 байтов). Эти ребята настолько увлеклись данной
работой, что одному из них пришлось уйти в академический
отпуск, а другому перевестись на другой факультет. Расширение
возможностей и исправление неизбежных неточностей сделал
Зырянов Максим (io.asm — 6760 байтов).
Репозиторий библиотеки и инсталляторов среды указан в [3].
Для использования процедур ввода-вывода следует
подключить файл io.asm, который содержит необходимые
макросы.
Обращаем
внимание,
что
все
идентификаторы
регистрозависимые.
3.1. ВВОД. МАКРОСЫ inint*
Макрос
inint8 op
inint16 op
inint32 op
inch op
inint op

Описание
Ввод целого. op – r8 | m8
Ввод целого. op – r16 | m16
Ввод целого. op – r32 | m32
Ввод символа. op – r8 | m8
Ввод целого. op – r32, m32. Автоматически
определяется размер op. Для вывода массива
рекомендуется
использовать
inint8,
inint16, inint32.

3.2. ВЫВОД. МАКРОСЫ outint*
Макрос
Описание
outint8 op [,n]
Вывод целого. op – r8, m8 или i8.
outint16 op [,n]
Вывод целого. op – r16, m16 или i16.
outint32 op [,n]
Вывод целого. op – r32, m32 или i32.
Параметр n — (необязательный) количество позиций, отводимых для
числа (если длина числа меньше, то дополняется пробелами).

34

Макрос
outint op [,n]

outch c

Описание
Вывод целого. op

r32,
m32.
Автоматически определяется размер op. Для
вывода массива рекомендуется использовать
outint8, outint16, outint32.
Вывод символа. c – r8, m8 или i8.
Например, outch 'A'

print [arg]

Выводит строку arg. Если arg отсутствует,
то макрос ждёт нажатия любой клавиши.
Особенности:
нет
необходимости
использовать offset. Нельзя выводить
пустую строку. Необходимо использовать
только двойные кавычки.

println [arg]

Аналогично print, но с переходом на новую
строку после вывода.

Read arg

Если arg регистр или переменная размером в
байт, то макрос считывает в неё код нажатой
клавиши.
Особенности: рекомендуется использовать в
качестве аргумента регистр, так как тогда
обеспечивается ввод по горизонтали. Нельзя
использовать регистры или переменные
размером больше, чем байт.

readln arg

Аналогично read, но с переходом на новую
строку после ввода.

3.3. ДОПОЛНИТЕЛЬНЫЕ ВОЗМОЖНОСТИ
Макрос
newline
outstr adr_str

Описание

Перевод строки.
Вывод строки, которая расположена по адресу
adr_str (т.е. offset строки). Строка
обязательно должна оканчиваться 0 байтом.
Например Text DB "Text",0.
outstrln adr_str То же самое, с переходом на следующую строку.
inkey [текст]
Выводим [текст] и ждем нажатия любой
клавиши.

35

Ассемблер в примерах и задачах

4. КОМАНДЫ ЦЕЛОЧИСЛЕННОЙ АРИФМЕТИКИ
4.1. ОПЕРАТОР УКАЗАНИЯ ТИПА PTR1
Могут возникнуть ситуации, когда ассемблер не может
однозначно определить размер пересылаемой величины.
MOV [SI], 0

; В память по адресу, который
; содержится в SI, помещается 0

По этой команде ассемблер не может однозначно определить,
что необходимо сформировать по адресу, содержащемуся в
регистре SI: нулевой байт или нулевое слово. В таких случаях
программист должен явно сообщить ассемблеру такую
информацию c помощью оператора указания типа, который
записывается следующим образом:
PTR
где – это BYTE, WODR или DWORD, а выражение может быть

константным или адресным.
MOV BYTE PTR [SI], 0
MOV WORD PTR [SI], 0

;
;

нулевой байт
нулевое слово

;

нулевой байт

Можно записать
MOV [SI], BYTE PTR 0

Оператор PTR полезен в ситуации, когда требуется не
уточнить, а изменить тип непосредственного операнда.
Пусть есть переменная
Z DW 1234h

; Z: 34h, Z+1: 12h
и требуется записать 0 в первый байт этого слова (там, где
хранится 34h). По команде MOV Z, 0 ноль запишется в оба байта.

Команда
MOV BYTE PTR Z, 0 ; Z: 00h, Z+1: 12h
позволяет рассматривать Z как байт только в данной конкретной

команде.

1

Pointer – указатель.
36

Команды пересылки MOV

4.2. КОМАНДЫ ПЕРЕСЫЛКИ MOV
Команда пересылки данных – пересылает число размером 1,
2 или 4 байта из op2 в op1:
MOV Op1, Op2

Примеры
MOV
MOV
MOV
MOV
MOV

AX, BX
ESI, 1000
0[DI], AL
AX, code
DS, AX

Команда перемещения и дополнения нулями – при
перемещении значение op2 помещается в младшие разряды, а в
старшие заносятся нули:
MOVZX оp1, оp2

Допустимые варианты

MOVZX r16/r32, r/m8
MOVZX r32, r/m16

Примеры
MOVZX EAX, BX
MOVZX SI, AH

Команда перемещения и дополнения знаковым разрядом –
команда выполняется аналогично, но в старшие разряды заносятся
знаковые биты:
MOVSX Op1, Op2

4.3. КОМАНДА ОБМЕНА ДАННЫХ XCHG1
Меняет местами содержимое операндов (они должны быть
одного размера).
XCHG Op1, Op2

Допустимые варианты
XCHG reg, reg
1

Exchange – перестановка.
37

Ассемблер в примерах и задачах

XCHG mem, reg
XCHG reg, mem

Например:
MOV AX, 10
MOV SI, 15
XCHG AX, SI

; AX=10
; SI=15
; AX=15, SI=10

4.4. КОМАНДЫ РАБОТЫ СО СТЕКОМ PUSH И POP
Команды записи слова или двойного слова в стек и
извлечения из стека.
PUSH i16 / i32 / r16 / r32 / m16 / m32
POP r16 / r32 / m16 / m32

ESP
ESP
ESP
ESP

Если в стек помещается 16-разрядное значение, то значение
= ESP – 2, если помещается 32-разрядное значение, то
= ESP – 4.
Если из стека извлекается 16-разрядное значение, то значение
= ESP + 2, если помещается 32-разрядное значение, то
= ESP + 4.
Примеры

PUSH SI
POP word ptr [EBX]

4.5. КОМАНДЫ СЛОЖЕНИЯ И ВЫЧИТАНИЯ
4.5.1. ADD1, SUB2
ADD op1, op2 ; op1 = op1 + op2
SUB op1, op2 ; op1 = op1 – op2

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

Addition – сложение.
Subtract – вычитание.
38

полученного результата следует анализировать содержимое битов
флагового регистра CF (перенос) и OF (переполнение).
Если работа происходит с беззнаковыми числами, то следует
анализировать флаг CF. Если же работаем со знаковыми числами,
то анализируется флаг OF.
Если результат операции с беззнаковыми числами не
помещается в соответствующий байт или слово, то CF = 1 (это
означает, что результат сформирован по модулю 216).
Аналогичная ситуация для знаковых чисел приводит к
установке OF = 1.
Термины «установить флаг» и «сбросить флаг» трактуются
так:
«установить флаг» – флаг = 1;
«сбросить флаг» – флаг = 0.
Кроме указанных флагов, команды сложения или вычитания
меняют флаги ZF и SF.
Если результат равен 0, то устанавливается флаг ZF, иначе ZF
сбрасывается.
Если результат меньше 0, то устанавливается флаг SF, иначе
SF сбрасывается.
4.5.2. INC1, DEC2
INC op
DEC op

; op = op + 1
; op = op – 1

Иначе эти команды можно записать:
ADD op, 1
SUB op, 1

; op = op + 1
; op = op – 1

4.5.3. NEG3
NEG op

; op = -op

Increment – увеличение на 1.
Decrement – уменьшение на 1.
3
Negative – изменение знака.
39
1
2

Ассемблер в примерах и задачах

Команда NEG рассматривает свой операнд как число со
знаком и меняет его на противоположный.
Есть особый случай: если op байт и op=–128(80h), то
операнд не меняется, так как нет знакового
числа +128
(байт:
–128..127).
Аналогично
если
op
слово
и
op=–32768 (8000h).

В этом особом случае флаг OF получает значение 1 (при
других операндах 0). При нулевом операнде флаг CF = 0, при
других – 1.
При этом обычным образом устанавливаются флаги SF и ZF.
4.5.4. ADC1, SBB2
Следующие команды предназначены для моделирования
сложения и вычитания длинных целых чисел, т.е. целых чисел,
занимающих двойное слово.
Сложение с переносом

ADC op1, op2 ; op1 = op1 + op2 + CF

Вычитание с учетом заёма
SBB op1, op2 ; op1 = op1 - op2 - CF
При выполнении ADC к результату прибавляется содержимое
флага CF, а при SBB из результата вычитается содержимое флага CF.
xxx…x 1xx…x
+ xxx…x 1xx…x
xxx…x 0xx…x

CF=1

Пример 1. Сложение «длинных» чисел.

X DD ?
Y DD ?
Z DD ?

; x, y – некоторые числа
; z = x + y

Напомним, что значения ассемблер записывает в память в
«перевернутом» виде.
x
мл.
1
2

разр.

x+2
старш. разр.

Add with carry – сложение с переносом.
Subtract with borrow – вычитание с учетом заёма.
40

Изменение размера регистров

y
мл.

разр.

MOV AX, WORD PTR x
ADD AX, WORD PTR y

y+2
старш. разр.
; CF = 0 | 1

MOV BX, WORD PTR x+2
ADC BX, WORD PTR y+2
MOV WORD PTR z, AX
MOV WORD PTR z+2, BX

Аналогично можно реализовать вычитание «длинных»
беззнаковых целых чисел.
4.6. ИЗМЕНЕНИЕ РАЗМЕРА РЕГИСТРОВ
В операциях деления размер делимого в два раза больше, чем
размер делителя. Поэтому нельзя просто загрузить данные в
регистр EAX и поделить его на какое-либо значение, так как в
операции деления будет задействован также и регистр EDX.
Поэтому прежде чем выполнять деление, надо установить
корректное значение в регистр EDX, иначе результат будет
неправильным. Значение регистра EDX должно зависеть от
значения регистра EAX. Тут возможны два варианта — для
знаковых и беззнаковых чисел.
Если мы используем беззнаковые числа, то в любом случае в
регистр EDX необходимо записать значение 0:
AAAAAAAAh → 00000000AAAAAAAAh.

Если же мы используем знаковые числа, то значение регистра
EDX будет зависеть от знака числа:
55555555h → 0000000055555555h,
AAAAAAAAh → FFFFFFFFAAAAAAAAh.

Записать значение 0 не сложно, а вот для знакового
расширения необходимо анализировать знак числа. Однако нет
необходимости делать это вручную, так как язык ассемблера имеет
41

Ассемблер в примерах и задачах

ряд команд, позволяющих расширять байт до слова, слово до
двойного слова и двойное слово до учетверённого слова.
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
CDQ

; EAX = x, которое заранее неизвестно
; Знаковое расширение EAX в EDX:EAX

В языке ассемблера существуют также команды,
позволяющие занести в регистр значение другого регистра или
ячейки памяти со знаковым или беззнаковым расширением.
MOVSX op1, op2; Заполнение знаковым битом
MOVZX op1, op2; Старшие биты заполняются нулём
op1, op2 могут иметь любой размер. Понятно, что op1 должен
быть больше, чем op2. В случае равенства размера операндов
следует использовать обычную команду пересылки MOV, которая

выполняется быстрее.
4.7. КОМАНДЫ УМНОЖЕНИЯ И ДЕЛЕНИЯ
В отличие от сложения и вычитания, умножение и деление
для беззнаковых и знаковых чисел выполняются по разным
алгоритмам.
4.7.1. Команды умножения
Операции умножения для беззнаковых данных выполняются
командой MUL1, а для знаковых – IMUL2.
1
2

Multiply – умножение.
Integer multiply – целочисленное умножение.
42

Команды умножения и деления

Эти команды реализованы для процессора 80186+.
MUL op ; умножение целых без знака
IMUL op ; умножение целых со знаком

Если размерность операнда op 8 бит, то команды MUL и IMUL
умножают содержимого регистра AL на значение операнда и
помещают результат в регистр AX.
Если операнд – 16-битное слово, команды MUL и IMUL
умножают содержимого регистра AX на значение операнда и
помещают результат в пару регистров DX:AX.
Если операнд – двойное слово, команды MUL и IMUL
умножают содержимого регистра EAX на значение операнда и
помещают результат в пару регистров EDX:EAX.
Команда знакового умножения имеет еще две формы.
IMUL op1, op2; op1 = op1 * op2
op1: (r16, r32)
op2: (r16, m16, r32, m32, i8, i16, i32)

Команда выполняет умножение первого операнда на второй
и помещает результат в первый операнд.
Разрядность операндов должна совпадать. Исключением
является использование в качестве второго операнда
непосредственного 8-битного значения.
IMUL op1, op2, op3; op1 = op2 * op3
op1: (r16, r32)
op2: (r16, m16, r32, m32)
op3: (i8, i16, i32)

Команда выполняет умножение второго операнда на третий
операнд и помещает результат в первый операнд.
Разрядность операндов должна совпадать. Исключением
является использование в качестве второго операнда
непосредственного 8-битного значения.
Ответственность за контроль над форматом обрабатываемых
чисел и за выбор подходящей команды умножения лежит на
программисте.
; Пример использования MUL, IMUL
43

Ассемблер в примерах и задачах

.686
include /masm32/include/io.asm
.data
A_DB DB ?
A_DW DW ?
.code
start:
; MUL op = = = = = = = = = = = = = = = = = = = = = =
println "MUL op ; AX = AL * op"
; op1 (r8, r16, r32)
MOV AL,
15
MOV A_DB, 3
print "Res = "
outint8 AL
print "*"
outint8 A_DB
print " = "
MUL A_DB
; AX = AL * A_DB
outint16 AX
newline
MOV AL, -15
MOV A_DB, 3
print "Res = "
outint8 AL
print "*"
outint8 A_DB
print " = "
MUL A_DB
; AX = AL * A_DB
outint16 AX
println "
!!! MUL для op меньше 0 не работает"
newline
; IMUL op1, op2 = = = = = = = = = = = = = = = = = =
println "IMUL op1, op2;
op1 = op1 * op2"
; op1: (r16)
; op2: (r16)
MOV AX, 15
MOV BX, 3
print "Res = "
outint16 AX
print "*"
outint16 BX
44

Команды умножения и деления

print " = "
IMUL AX, BX
outint16 AX
newline

; AX = AX * BX

MOV EAX, 15
MOV EBX, -3
print "Res = "
outint32 EAX
print "*"
outint32 EBX
print " = "
IMUL EAX, EBX
outint32 EAX
newline

; EAX = EAX * EBX

; IMUL op1, op2, op3;
op1 = op2 * op3 = = = = = =
println "IMUL op1, op2, op3; op1 = op2 * op3"
; op1: (r8, r16, r32)
; op2: (i8, i16, i32)
; op3: (r8, r16, r32), (m8,m16,m32), (i8,i16,i32)
MOV AX, 15
MOV A_DW, 3
print "Res = "
outint16 AX
print "*"
outint16 A_DW
print " = "
IMUL BX, AX, 3
outint16 BX
newline
inkey
exit
end start

; BX = AX * 3

; ожидание нажатия клавиши

45

Ассемблер в примерах и задачах

Команда MUL при AL = –15 и op=3 дает результат 723, так как
отрицательное значение –15 представлено в дополнительном коде:
15
Прямой код
00001111(2)
Инверсия
11110000(2)
+1
–15 Дополнительный код
11110001(2) = F1h
В десятичной системе счисления: F1h = 15*16+1 = 241(10),
тогда умножение 241*3=723.

4.7.2. Команды деления
Как и умножение, деление чисел без знака и со знаком также
реализуется двумя командами:
DIV1 op ; деление целых без знака
IDIV2 op ; деление целых со знаком

Деление слова на байт (op DB ?):
AH = АХ % op,

AL = AX / op

Деление двойного слова на слово (op DW ?):

DX = (DX,AX) % op,

AX = (DX,AX) / op

Деление четверного слова на двойное слово (op DD ?):
EDX=(EDX:EAX) % op,

EAX=(EDX:EAX) / op

При делении слова на байт делимое находится в регистре АХ,
а делитель – в байте памяти или в однобайтовом регистре. После
деления остаток получается в регистре АН, а частное – в АL. Так как
однобайтовое частное очень мало – максимально 255 для
беззнакового деления и 127 для знакового, то данная операция
имеет ограниченное использование.
При делении двойного слова на слово делимое находится в
регистровой паре DX:АХ, а делитель – в слове памяти или регистре.
После деления остаток получается в регистре DX, а частное – в

1
2

Divide – деление.
Integer divide – целочисленное деление.
46

Команды умножения и деления

регистре АХ. Частное в одном слове допускает максимальное
значение 65535 для беззнакового деления и 32767 для знакового.
В единственном операнде команд DIV и IDIV указывается
делитель.
Пример 2. Использование DIV
.686
include /masm32/include/io.asm
.data
A_DB DB ?
A_DW DW ?
A_DD DD ?
.code
start:
; op DB = = = = = = = = = = = = = = = = = = = = = = =
println "op DB: DIV op ; AH=AX%op
AL=AX/op"
MOV AL, 63 ; AL = 3Fh = 63
MOV A_DB, 5
MOV AH, 0 ; !!!
print "Res = "
outint16 AX
print "/"
outint8 A_DB
print "
:
"
DIV A_DB ; AH = AX%A_DB
print "
AH = "
outint8 AH
print " AL = "
outint8 AL
newline

AL=AX/A_DB

; op DW = = = = = = = = = = = = = = = = = = = = = = =
println "op DW: DIV op; DX=(DX:AX)%op
AX=(DX:AX)/op"
MOV AX, 63 ; AX = 003Fh = 63
MOV A_DW, 5
MOV DX, 0 ; !!!
print "Res = "
outint16 AX
print "/"
outint16 A_DW
print "
:
"
DIV A_DW

; DX=(DX:AX)%A_DW
47

AX=(DX:AX)/A_DW

Ассемблер в примерах и задачах

print "
DX = "
outint16 DX
print " AX = "
outint16 AX
newline
; op DD = = = = = = = = = = = = = = = = = = = = = = =
println "op DD: DIV op; EDX=(EDX:EAX)%op
EAX=(EDX:EAX)/op"
MOV EAX, 63
MOV A_DD, 5
MOV EDX, 0 ; !!!
print "Res = "
outint16 AX
print "/"
outint32 A_DD
print "
:
"
DIV A_DD

; DX = (EDX:EAX) % A_DD
; AX = (EDX:EAX) / A_DD

print "
EDX = "
outint32 EDX
print " EAX = "
outint32 EAX
newline
inkey
exit
end start

; Ожидание нажатия клавиши

Пример 3. Использование IDIV
.686
include /masm32/include/io.asm
; .data - Сегмент данных не описываем,
; т.к. используются регистры
.code
start:
48

Команды умножения и деления

; op DB = = = = = = = = = = = = = = = = = = = = = = =
println "= = =
op DB: IDIV op; AH = AX % op
AL = AX / op"
MOV AL, 63 ; AL = 3Fh = 63
MOV BL, 5
CBW ; Знаковое расширение AL до AX: AX=003Fh = 63
; !!! В данном случае не обязательно !!!
print "Res = "
outint16 AX
print "/"
outint8 BL
print "
:
"
; AX = 003Fh = 63
IDIV
BL
; AH = 03h = 3,
print "
AH = "
outint8 AH
print " AL = "
outint8 AL
newline

AL = 0Ch = 12

; op DW = = = = = = = = = = = = = = = = = = = = = = =
println "= = = op DW: IDIV op; DX = (DX:AX) % op
AX = (DX:AX) / op"
MOV AL, -63 ; AL = C1h = -63
MOV BL, 5
CBW ; Знаковое расширение AL до AX: AX=FFC1h=-63
print "Res = "
outint8 AL
print "/"
outint8 BL
print "
:
"
; AX = FFC1h =
IDIV
BL
;
print "
AH =
outint8 AH, 5
print "
AL =
outint8 AL, 5
newline

-63
AH = 03h = 3,
"

AL = F4h = -12

"

; op DD = = = = = = = = = = = = = = = = = = = = = = =
MOV AX, 63 ; AX = 003Fh = 63
MOV BX, -5
CWD
; Знаковое расширение AX до (DX:AX)=0000003Fh=63
; !!! В данном случае не обязательно !!!
print "Res = "
outint16 AX
print "/"
49

Ассемблер в примерах и задачах

outint16 BX
print "
:
IDIV
BX
print "
AX
outint16 AX,
print "
DX
outint16 DX,
newline

"
; DX = 0003h = 3,
= "
5
= "
5

AX = FFF4h = -12

; op DD = = = = = = = = = = = = = = = = = = = = = = =
println "= = op DD: IDIV op; EDX = (EDX:EAX) % op
EAX = (EDX:EAX) / op"
newline
MOV EAX, -63 ; EAX = FFFF FFC1h = -63
MOV EBX, 5
CDQ ; Знаковое расширение EAX до (EDX:EAX)
; (EDX:EAX) = FFFF FFFF FFFF FFC1h = -63
print "Res = "
outint32 EAX
print "/"
outint32 EBX
print "
:
"
IDIV

EBX ; EDX=FFFF FFFDh=-3, EAX=FFFF FFF4h=-12

print "
EAX = "
outint32 EAX, 5
print "
EDX = "
outint32 EDX, 5
newline
; op DD = = = = = = = = = = = = = = = = = = = = = = =
MOV EAX, 63 ; EAX = 0000 003Fh = 63
MOV EBX, -5
CDQ ; Знаковое расширение EAX до (EDX:EAX)
; (EDX:EAX) = 0000 0000 0000 003Fh = 63
; !!! В данном случае не обязательно !!!
print "Res = "
outint32 EAX
print "/"
outint32 EBX
print "
:
"
IDIV
EBX ; EDX = 0000 0003h=3, EAX=FFFF FFF4h=-12
print "
EAX = "
outint32 EAX, 5
print "
EDX = "
outint32 EDX, 5
50

Команды умножения и деления

newline
inkey
exit
end start

; ожидание нажатия клавиши

Отметим, что при выполнении деления возможно появление
ошибки «деление на 0 или переполнение». Она возникает в двух
случаях:
− делитель равен 0 (ор = 0);
− неполное частное не вмещается в отведенное ему место
(регистр AL или АХ); это, например, произойдет при делении 600
на 2:
MOV АХ, 600
MOV ВН, 2
DIV BH; 600 div 2=300, но 300 не вмещается в AL

При такой ошибке ПК прекращает выполнение программы.
Пример 4. Дано целое трехзначное беззнаковое число. Найти
сумму цифр этого числа.
X
DB ?
Sum DB 0
Ten DB 10
MOV AL, X
SUB AH, AH
DIV Ten
ADD Sum, AH
SUB AH, AH
DIV Ten
ADD Sum, AH
ADD Sum, AL

; Исходное число
;
;
;
;
;
;
;

Деление на 10
AH – последняя цифра
Очистка AH
Деление на 10
AH – средняя цифра
AL – первая цифра числа
Вывод Sum – Сумму цифр числа

51

Ассемблер в примерах и задачах

5. ПЕРЕХОДЫ И ЦИКЛЫ
Процессор выполняет команды машинной программы в том
порядке, как они записаны в памяти.
Выполнение любой команды начинается с того, что
содержимое регистра EIP/IP увеличивается на длину текущей
команды и, таким образом, в регистре адресов команд оказывается
адрес следующей команды.
Если команда во время своего выполнения меняет
содержимое EIP/IP, то в результате за данной командой будет
выполняться не обязательно следующая команда.
Такие команды называются командами перехода, или
командами передачи управления.
Отметим, что команды перехода не изменяют флаги: какое
значение флаги имели до команды перехода, такое же значение
они будут иметь и после нее.
5.1. БЕЗУСЛОВНЫЙ ПЕРЕХОД
Команда
синтаксис:
JMP1 op

безусловного

перехода

имеет

следующий

; безусловный переход

Операнд op указывает адрес перехода. Существует два
способа указания этого адреса, соответственно различают прямой
и косвенный переходы.
5.1.1. Прямой переход
Если в команде перехода указывается метка команды, на
которую надо перейти, то переход называется прямым.
JMP
...
L: MOV

1

L
EAX, x

Jump – прыжок.
52

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

Вообще, любой переход заключается в изменении адреса
следующей исполняемой команды, т.е. в изменении значения
регистра EIP/IP.
Запись в команде перехода не абсолютного, а относительного
адреса перехода позволяет уменьшить размер команды перехода.
Абсолютный адрес должен быть 32-битным, а относительный
может быть и 8-битным, и 16-битным.
5.1.2. Косвенный переход
При косвенном переходе в команде указывается не адрес
перехода, а регистр или ячейка памяти, где этот адрес находится.
Содержимое как абсолютный адрес перехода. Косвенные
переходы используются в тех случаях, когда адрес перехода
становится известен только во время работы программы.
JMP

EBX

5.2. КОМАНДЫ СРАВНЕНИЯ И УСЛОВНОГО ПЕРЕХОДА
Команды условного перехода осуществляют переход,
который выполняется только в случае истинности некоторого
условия. Истинность условия проверяется по значениям флагов.
Поэтому обычно непосредственно перед командой условного
перехода ставится команда сравнения, которая формирует
значения флагов:
CMP1 op1, op2

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

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

Compare – сравнивать.
53

Ассемблер в примерах и задачах

В первую группу входят команды, которые обычно ставятся
после команды сравнения. В их мнемокодах указывается тот
результат сравнения, при котором надо делать переход.
Мнемокод
JE
JNE
JL/JNGE
JLE/JNG
JG/JNLE
JGE/JNL
JB/JNAE
JBE/JNA
JA/JNBE
JAE/JNB

Примечание

CMP op1, op2
op1
op1
op1
op1
op1
op1
op1
op1
op1
op1

=

<

>

<

>


op2
op2
op2
op2
op2
op2
op2
op2
op2
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
JS
JC
JO
JP

ZF
SF
CF
OF
PF

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

Мнемокод
JNZ
JNS
JNC
JNO
JNP
54

ZF
SF
CF
OF
PF

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

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

Рассмотрим пример: пусть A, B и C – беззнаковые
переменные
размером
1
байт,
требуется
вычислить
C = A * A + B, но если результат превосходит размер байта,
передать управление на метку ERROR.
MOV
MUL
JC
ADD
JC
MOV

AL, A
AL
ERROR
AL, B
ERROR
C, AL

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

; Переход, если CX = 0
; Переход, если ECX = 0

Отметим общую особенность команд условного перехода:
все они осуществляют только короткий переход, т.е. с их помощью
можно передать управление не далее, чем на 128 байтов вперед
или назад. Это примерно 30–40 команд (в среднем одна команда
ПК занимает 3–4 байта).
Для реализации длинных условных переходов надо
привлекать команду длинного безусловного перехода. Например,
при «далекой» метке М оператор
if AX=BX then

goto M

следует реализовывать так:
if АХВХ
goto M
L:


M:


then

goto

L ; Короткий переход
; Длинный переход

На ЯА это записывается следующим образом:
CMP AX, BX
JNE L
JMP M
L:


M:


; Короткий переход
; Длинный переход

55

Ассемблер в примерах и задачах

Отметим, что использовать в командах условного перехода
оператор SHORT не надо, так как все эти переходы и так короткие.
Пример 5. Дана последовательность чисел. Признак
завершения ввода — 0. Найти количество чисел, кратных 3.
A
K
TRI

DW ? ; Переменная для ввода числа
DW 0 ; Результат – количество чисел
DB 3 ; Делитель

L1: ; Ввод числа а
CMP A, 0
; Проверка на окончание ввода
JE EX
MOV AX, A
DIV TRI
; Проверка свойства кратности 3
CMP AH,0
JNE L2
INC K
; Подсчет количества
L2: JMP L1
EX: ; Вывод числа k

Рассмотренный пример некорректно работает при вводе
отрицательных чисел. Модифицируем пример так, чтобы
отрицательное число перед проверкой на кратность изменяло знак.
Пример 6.
A
K
TRI

DW ? ; переменная для ввода числа
DW 0 ; результат – количество чисел
DB 3 ; делитель

L1: ; Ввод числа A
CMP A, 0
JE EXIT
JGE L3
NEG A
; Если A < 0, то меняем знак
L3:
MOV AX, A
DIV TRI
CMP AH, 0
JNE L2
INC K
L2:
JMP L1
EXIT: ; Вывод числа k

56

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

Пример 7. Найти сумму цифр заданного натурального числа.
.686
include /masm32/include/io.asm
.data
MsgInput db "Введите положительное число > ",0
MsgOutput db "Сумма цифр равна ",0
ten dw 10
.code
start:
print "Введите положительное число > "
inint EAX
; Ввод числа
MOV EBX,
L:
MOV EDX,
DIV ten
ADD EBX,
цифру)
CMP EAX,
jne L

0

; EBX = 0

0

; Делим EDX:EAX
; на 10
; Прибавляем остаток (последнюю

EDX
0

; Если число не равно нулю, то
; возврат к метке L

print "Сумма цифр равна "
outint EBX
; вывод результата
exit
end start

Пример 8.
Дано
натуральное число
N.
Дана
последовательность, состоящая из N чисел. Найти количество
чисел, кратных пяти.
.686
include /masm32/include/io.asm
.data
MsgInput db "Введите количество элементов
последовательности > ",0
MsgInput2 db "Введите элементы > ",0
MsgOutput db "Количество : ",0
five dw 5
a dd ?
N dd ?
count dd ?
.code
start:
print "Количество элементов последовательности >"
inint N
; Ввод N
57

Ассемблер в примерах и задачах

print "Введите
MOV ECX, N
;
MOV count, 0 ;
L:
inint a
;
MOV EDX, 0
MOV EAX, a
;
DIV five
;
CMP EDX, 0
;
JNE LL
;
INC count
;
LL: LOOP L

элементы > "
Число повторений цикла
Обнуляем счетчик
Ввод элемента
Деление элемента
на 5
Если не делится нацело,
то переход в конец цикла,
иначе увеличиваем количество

; следующая итерация цикла
; (возврат к метке L)

print "Количество : "
outint count ; вывод результата
exit
end start

5.3. ВЫЧИСЛЕНИЕ ЛОГИЧЕСКИХ ВЫРАЖЕНИЙ
При программировании на ЯА сложных булевских
выражений можно обойтись без логических команд, достаточно
лишь команд сравнения и условных переходов. Например,
условный оператор
if (AX>0) or (DX=l) then goto L

можно запрограммировать так:
CMP
JG
CMP
JE

AX, 0
L
DX, 1
L

; AХ>0 → L
; DX=1 → L

Для оператора
if (AX > 0) && (DX = 0) {
goto L
}

допустима такая последовательность команд:
CMP
JLE
CMP
JNE
JMP

AX,
M
DX,
M
L

0
; AХ0)
{ S1 }
else { S2 }
CMP X, 0
JLE L2
S1
JMP Fin
L2: S2
Fin: …

while (X>0)
{ S }
Beg:
CMP X, 0
JLE Fin
S
JMP Beg
Fin: …

do
{ S }
while (X 0) S

; if (A > 0 || B > 0) S

CMP x, 0
JLE L
...
L:

; if (X = 0) S1 else S2

CMP A, 0
JG L1
CMP B, 0
JLE L2
L1: ...
; S
L2:
; if (A > 0 && B > 0) S

CMP X, 0
JE L1
...
JMP L2
L1:
L2:

CMP
JLE
CMP
JLE
...
L:

; S

; S1
; S2

59

A, 0
L
B, 0
L
; S

Ассемблер в примерах и задачах

; while (X > 0) do S

; do S while (X > 0)

L1: CMP X, 0
JLE L2
...
JMP L1
L2:

L:
; S

...
CMP X, 0
JG L

; S

5.5. КОМАНДЫ УПРАВЛЕНИЯ ЦИКЛОМ
5.5.1. Команда LOOP
Наиболее часто используемым является цикл с заранее
известным числом повторений тела цикла.
В качестве счетчика цикла обязательно использовать регистр
CX/ECX. Начальное значение для CX/ECX должно быть присвоено
до цикла.
Описать работу этой команды можно так:
CX = N
L:
… {тело цикла}
CX = CX - 1
if CX 0 then goto L

Тогда повторение N раз (N > 0) некоторой группы команд
(тело цикла) можно реализовать так:
MOV CX/ECX, N
L:
… {тело цикла}
LOOP L

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

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

следовательно, тело цикла выполняется хотя бы один раз. Если до
начала цикла записать в регистр CX/ECX значение ≤ 0, то при
вычитании единицы, которое выполняется до сравнения с нулём,
в регистре CX/ECX окажется ненулевое значение, и цикл будет
выполняться 232 раз.
Команда LOOP не относится к самым быстрым командам. В
большинстве случаев её можно заменить последовательностью
других команд.
Поскольку команда LOOP ставится в конце цикла, то тело
цикла хотя бы раз обязательно выполнится. Поэтому для случая
EСХ = 0 наша схема цикла не подходит. Если возможен вариант,
когда число повторений может быть и нулевым, то при EСХ = 0
надо сделать обход цикла:
MOV
JECXZ
L:

LOOP
L1: …

ECX, N
L1
; ECX=0 -> L1;
; тело цикла
L

Для осуществления таких обходов в ПК была введена
команда условного перехода JECXZ. В иных ситуациях она
используется редко.
Рассмотрим пример использования команды LOOP.
Пример 9. Пусть X и Y – байтовые переменные со значением
от 0 до 6 и надо в регистр АХ записать степень Y числа X: АХ = XY

(отметим, что 66 = 46656 < 216).
Для решения этой задачи надо вначале положить АХ = 1, а
затем Y раз выполнить умножение AX = AX*X. При этом следует
учитывать, что при Y = 0 цикл не должен выполняться.
MOV АХ, 1
MOV CL, Y
MOV CH, 0
JCXZ L1
MOV SI, X
L: MOV DX, 0
MUL SI
LOOP L

; AX=1
; CX=Y как слово (счетчик цикла)
; При N=0 обойти цикл
; (DX,AX)=AX*X (DX=0)
61

Ассемблер в примерах и задачах

L1:



Другой пример использования оператора цикла в программе
— обработка последовательности чисел.
Пример 10. Дана последовательность из N чисел. Найти
сумму положительных чисел последовательности.
A
S
N

DW ?
DW 0
DW ?
MOV CX, N
; Ввод количества чисел N
L1: ; Ввод числа а
CMP A, 0
;если N / 2

EDX
N
0

; Если делитель = > N - составное

2

; Проверяем только нечетные
68

L02:
MOV F, 0
; Устанавливаем флаг в 0
L03:
}
cout