Вы находитесь на странице: 1из 368

Г.В.

Галисеев

Самоучитель
АССЕМБЛЕР
для WIN32

Sam_Assembler_Title.indd 1 05.02.2007 17:22:00


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

Состав серии
1. Базылев Г.В. Photoshop CS. Самоучитель
2. Бидасюк Ю.М. Mathsoft® MathCAD 12. Самоучитель
3. Варакин А.С. AutoCAD 2006. Самоучитель
4. Галисеев Г.В. Программирование в среде Delphi 2005. Самоучитель
5. Галисеев Г.В. Программирование на языке C#. Самоучитель
6. Гусев В.С. Освоение Internet. Самоучитель
7. Гусев В.С. Поиск в Internet. Самоучитель
8. Дригалкин В.В. HTML в примерах. Как создать свой Web-сайт. Самоучитель
9. Захарченко Н.И. Бизнес-статистика и прогнозирование в MS Excel. Самоучитель
10.Козлин В.И. Музыкальный редактор Sibelius. Самоучитель
11. Курбатова Е.А. MATLAB 7. Самоучитель
12. Курбатова Е.А. Microsoft Excel 2003. Самоучитель
13. Куссуль Н.Н., Шелестов А.Ю. Использование PHP. Самоучитель
14. Меженный О.А. Microsoft Office 2003. Самоучитель
15. Меженный О.А. Microsoft Windows XP. Самоучитель, 2-е издание
16. Меженный О.А. Microsoft Word 2003. Самоучитель
17. Меженный О.А. Turbo Pascal. Самоучитель
18. Полонская Е.Л. Язык HTML. Самоучитель
19. Птицын К.А. Серверы Linux. Самоучитель
20. Сергеев А.П. Офисные локальные сети. Самоучитель
21. Сергеев А.П., Кущенко С.В. Основы компьютерной графики. Adobe Photoshop и CorelDRAW — два в
одном. Самоучитель
22. Сергеев А.П., Терен А.Н. Программирование в Microsoft Visual C++ 2005. Самоучитель
23. Сингаевская Г.И. Microsoft Project 2003. Самоучитель
24. Слепцова Л.Д. Программирование на VBA. Самоучитель
25. Смолина М.А. Adobe Illustrator CS. Самоучитель
26. Смолина М.А. CorelDraw X3. Самоучитель
27. Спека М.В. Microsoft PowerPoint 2003. Самоучитель
28. Спека М.В. Создание Web-сайтов. Самоучитель
29. Ставровский А.Б. Первые шаги в программировании. 2-е издание. Самоучитель
30. Степаненко О.С. Использование Adobe Photoshop CS2. Самоучитель.
31. Степаненко О.С. Настройка персонального компьютера. Установки BIOS. Самоучитель
32. Степаненко О.С. Первая помощь ПК. Анализ сбоев и устранение неполадок. Самоучитель
33. Степаненко О.С. Персональный компьютер. 2-е издание. Самоучитель
34. Степаненко О.С. Установка и настройка Windows XP. Самоучитель
35. Бурлаков М.А. Macromedia Flash 8. Самоучитель
36. Тимошок Т.В. Microsoft Access 2003. Самоучитель
37. Шмидский Я.К. Программирование на языке C++. Самоучитель
38. Футько В.П. ПК — это очень просто. Для тех, кто решил приобрести компьютер. Самоучитель

Sam_Assembler_Title.indd 2 05.02.2007 17:22:01


Г.В. Галисеев

Самоучитель
АССЕМБЛЕР
для WIN32

www.dialektika.com

Москва • Санкт-Петербург • Киев


2007

Sam_Assembler_Title.indd 3 05.02.2007 17:22:01


ББК 32.973.26018.2.75
Г15
УДК 004.038
Компьютерное издательство ‘‘Диалектика’’

Зав. редакцией А.В. Слепцов

По общим вопросам обращайтесь в издательство ‘‘Диалектика’’ по адресу:


info@dialektika.com, http://www.dialektika.com
115419, Москва, а/я 783; 03150, Киев, а/я 152

Галисеев, Г.В.
Г15 Ассемблер для Win 32. Самоучитель :  М. : Издательский дом ‘‘Вильямс’’, 2007. 
368 с. : ил.
ISBN 9785845911971 (рус.)
Эта книгасамоучитель поможет читателю самостоятельно освоить основы языка
ассемблера и научиться создавать программы на этом языке. Здесь подробно расска
зано о том, как начать работать с ассемблером и как писать программы на этом язы
ке. В книге рассматривается в основном 32разрядный режим работы ассемблера,
позволяющий обращаться к процедурам прикладного интерфейса (API) Windows.
Тем не менее, приведены и некоторые сведения, специфичные только для 16раз
рядного режима, например, описание работы прерываний и понятие о программи
ровании для DOS. Однако книга является лишь введением в язык ассемблера, по
этому в ней не отражены расширенные возможности языка ассемблера и команды,
разработанные для новейших версий процессоров.
Книга не является учебником по программированию для начинающих и для ра
боты с ней необходимо иметь базовые понятия о программировании, а также хотя
бы минимальное представление о том, как работает операционная система Windows.

ББК 32.973.26 018.2.75

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


ствующих фирм.
Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни
было форме и какими бы то ни было средствами, будь то электронные или механические, включая фо
токопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства
‘‘Диалектика’’.
Copyright © 2007 by Dialektika Computer Publishing.
All rights reserved including the right of reproduction in whole or in part in any form.

ISBN 9785845911971 (рус.) © Компьютерное издво ‘‘Диалектика’’, 2007,


текст, оформление, макетирование

Стр. 4
Îãëàâëåíèå

Введение 17

Глава 1. Предисловие 18

Глава 2. Архитектура компьютера 21

Глава 3. Введение в язык ассемблера 54

Глава 4. Программирование для Windows 101

Глава 5. Фундаментальные понятия языка ассемблера 140

Глава 6. Процедуры и прерывания 182

Глава 7. Логические вычисления 204

Глава 8. Арифметика целых чисел 224

Глава 9. Структуры и макроопределения 243

Глава 10. Написание графических приложений Windows 263

Глава 11. Работа в DOS 277

Глава 12. Справочный раздел 291

Приложение. Ответы на контрольные вопросы 361

Предметный указатель 352

Стр. 5
Ñîäåðæàíèå

Введение 17
От издательства “Диалектика” 17

Глава 1. Предисловие 18
Что такое язык ассемблера 18
Прикладные программы на языке ассемблера 19
Машинный язык 20

Глава 2. Архитектура компьютера 21


Первые процессоры Intel 21
Ядро процессора 23
Программируемые регистры 24
Регистры состояния и управления 27
Цикл выполнения команды 28
Современные процессоры 28
Улучшенный цикл выполнения команды 29
32&разрядные процессоры 29
Регистры состояния и управления 32&разрядного процессора 30
64&разрядные процессоры 32
Процессоры AMD 32
Устройство компьютера 33
Внутренние компоненты 34
Шинная архитектура 34
Видеоадаптер и монитор 35
Память 36
Устройства хранения данных 36
Платы расширения 37
Процессоры поддержки 37
BIOS 37
Порты 38
Работа компьютера 38
Операционные системы и память 39
Операционная система как диспетчер ресурсов 41
Эволюция операционных систем 42
Современные операционные системы 44
Управление памятью 45
Характеристики современных операционных систем 46

Стр. 6
Архитектура памяти 47
Сегментированная модель памяти 48
Ввод&вывод 50
Резюме 51
Контрольные вопросы 51

Глава 3. Введение в язык ассемблера 53


Представление данных 53
Двоичные числа 54
Биты, байты, слова, двойные и учетверенные слова 54
Команды и данные 55
Числовые системы 55
Правила представления числовых данных 55
Преобразование двоичных чисел в десятичные 56
Шестнадцатеричные числа 57
Числа со знаком 58
Дополнение до двух 58
Максимальные и минимальные значения 59
Хранение символов 60
Хранение чисел 60
Основы языка ассемблера 61
Команды языка ассемблера 61
Константы и выражения 62
Утверждения 63
Имена 64
Разработка программы на языке ассемблера 65
Разработка программы методом “сверху вниз” 66
Работа в DOS под Windows NT 67
Инструментальные средства 68
Ассемблер фирмы Borland (TASM) 69
Ассемблер фирмы Microsoft (MASM) 69
Пример простой программы 70
Анализ программы Hello для Win32 73
Операционная система Windows 73
Ассемблер Microsoft 80
Работа с DOS 81
Командный файл 84
Подготовка к запуску ассемблера 85
Синтаксические ошибки 88
Компоновка программы 89
Запуск программы 89
Отладчик 89
Простая программа 90
Резюме 93
Контрольные вопросы 94

Содержание 7

Стр. 7
Глава 4. Программирование для Windows 95
Программирование для консоли 95
Набор символов и функции Windows API 96
Уровни доступа 96
Типы данных Windows 97
Дескрипторы консоли 98
Функции консоли Win32 99
Использование функций 100
Ввод с консоли 104
Ввод одиночного символа 105
Структуры данных 106
Функция WriteConsoleOutputCharacter 106
Создание библиотеки загрузочных модулей 107
Подготовка модуля для библиотеки 108
Работа с файлами 112
Функция создания файла 112
Параметр DesiredAccess 112
Параметр CreationDisposition 112
Примеры работы с файлами 113
Функция CloseHandle 114
Функция ReadFile 114
Функция WriteFile 115
Пример записи в файл 115
Перемещение указателя файла 116
Пример чтения файла 117
Управление окном 118
Функция SetConsoleTitle 119
Функция GetConsoleScreenBufferlnfo 119
Функция SetConsoleWindowlnfo 120
Функция SetConsoleScreenBufferSize 121
Управление курсором 121
Управление цветом 121
Пример использования значений цвета 122
Функции времени и даты 123
Функции GetLocalTime и SetLocalTime 124
Функция GetTickCount 125
Функция Sleep 125
Процедура получения локального времени 125
Создание секундомера 126
Резюме 127
Контрольные вопросы 128

Глава 5. Фундаментальные понятия языка ассемблера 129


Директивы размещения данных 129
Объявление байтов 130
Множественная инициализация 131

8 Содержание

Стр. 8
Представление строк символов 131
Оператор DUP 132
Объявление слов 132
Указатели 132
Формат перестановки в памяти 133
Объявление двойного слова 133
Символические константы 133
Директива равенства 133
Директива EQU 134
Директива TEXTEQU 134
Модели памяти 135
Выполняемые программы 136
Директивы установки типа процессора 137
Команды пересылки данных 138
Команда MOV 138
Контроль соответствия типов 139
Использование дополнительного смещения в операторах 140
Команда XCHG 140
Арифметические команды 141
Команды INC и DEC 141
Команда ADD 141
Команда SUB 142
Флаги, используемые в командах ADD и SUB 142
Операнды без знака 143
Знаковое переполнение 143
Tипы операндов 144
Регистровые операнды 144
Непосредственные операнды 145
Операнды с прямой адресацией 145
Оператор OFFSET 145
Операнды со смещением 145
Операторы и выражения 146
Арифметические операторы 149
Старшинство и ассоциативность операторов 149
Операторы OFFSET, SEG, PTR, LABEL и EVEN 150
Оператор OFFSET 150
Оператор SEG 151
Оператор PTR 151
Директива LABEL 151
Директивы EVEN и EVENDATA 152
Операторы типа и размера 152
Оператор SHORT 152
Оператор TYPE 152
Оператор LENGTH 153
Оператор SIZE 153
Команды JMP и LOOP 153
Команда JMP 154

Содержание 9

Стр. 9
Команда LOOP 154
Команды LOOP, LOOPW, LOOPD 155
Косвенная адресация 155
Косвенные операнды 155
Сегменты по умолчанию 156
Изменение значений по умолчанию 156
Использование команды LOOP для отображения строки 157
Использование команды LOOP для суммирования массива целых чисел 158
Базовые и индексные операнды 160
32&разрядные регистры 160
Базо&индексные операнды 160
Базо&индексные операнды с дополнительным смещением 161
Команды процессоров 80386 и более старших моделей 162
Команды MOVZX и MOVSX 162
Команда XADD 163
Процедуры 163
Резюме 164
Контрольные вопросы 165

Глава 6. Процедуры и прерывания 166


Операции со стеком 166
Команда PUSH 167
Команда POP 167
Команды PUSHF и POPF 168
Команды PUSHA и PUSHAD 168
Процедуры 169
Директивы PROC и ENDP 169
Простая программа 169
Вложенные вызовы процедур 173
Параметры процедур 174
Передача аргументов в регистры 174
Прерывания 174
Команда INT 175
Таблица векторов прерываний 175
Ввод на уровне BIOS 176
Управляющие коды ASCII 177
Видеоконтроль на уровне BIOS 178
Дисплеи, режимы, атрибуты 178
Видеорежимы 179
Видеоатрибуты 179
Режим цветного текста 179
Видеостраницы 181
Видеофункции INT 10h 181
Прямая запись в видеопамять 186
Резюме 186
Контрольные вопросы 187

10 Содержание

Стр. 10
Глава 7. Логические вычисления 188
Команды логических вычислений и сравнения 188
Команда AND 189
Команда OR 189
Команда XOR 191
Инверсия бит 191
Инверсия флагов клавиатуры 191
Команда NOT 191
Команда NEG 192
Команда TEST 192
Команды BT, BTC, BTR, BTS 192
Команды BSF и BSR 193
Команда CMP 193
Команда CMPXCHG 194
Логические структуры 195
Команда Jcond 196
Типы команд условного перехода 196
Генерация кодов для условных переходов 198
Примеры условных переходов 199
Проверка ввода букв 199
Наибольшее и наименьшее значения в массиве 200
Команда SETcond 201
Циклы с условием 202
Команды LOOPZ и LOOPE 202
Команды LOOPNZ и LOOPNE 202
Директивы для логических вычислений 203
Директива .IF 203
Директивы .REPEAT и .WHILE 205
Резюме 206
Контрольные вопросы 206

Глава 8. Арифметика целых чисел 208


Команды сдвига 208
Команда SHL 208
Быстрое умножение 209
Команды SHLD и SHRD 209
Команда SHR 210
Команды SAL и SAR 211
Команда ROL 211
Команда ROR 212
Команды RCL и RCR 212
Команда RCR 213
Простые прикладные программы 213
Сдвиг нескольких байтов в процессоре 8086 213
Быстрое умножение и деление 214
Строка битов 214

Содержание 11

Стр. 11
Директива RECORD 215
Оператор MASK 216
Оператор WIDTH 216
Сложение и вычитание больших чисел 217
Команда ADC 217
Команда SBB 218
Умножение и деление 219
Команда MUL 220
Команда IMUL 221
Команда DIV 221
Команда IDIV 222
Команды CBW, CWD, CDQ и CWDE 222
Кодировка ASCII и арифметика упакованных чисел 223
Команда AAA 224
Команда AAS 224
Команда AAM 224
Команда AAD 225
Команды DAA и DAS 225
Резюме 226
Контрольные вопросы 226

Глава 9. Структуры и макроопределения 227


Структуры 227
Отображение системного времени 228
Введение в макроопределение 230
Примеры макроопределений 231
Макроопределение mWriteStr 231
Макроопределение mReadStr 232
Макроопределение mGotoxy 232
Макроопределение mDumpMem 233
Макроопределения, содержащие коды и данные 234
Вложенные макроопределения 235
Процедуры вызова макроопределений 235
Директивы условия ассемблера 236
Проверка пропущенного аргумента 236
Аргументы по умолчанию 237
Булевы выражения 237
Директивы IF, ELSE и ENDIF 237
Директивы IFIDN и IFIDNI 238
Операторы для макроопределений 239
Оператор замены 239
Оператор выражения 240
Оператор выделения текста 242
Оператор выделения символа 242
Дополнительные макроопределения и директивы 243
Директива REPT 243

12 Содержание

Стр. 12
Директива IRP 243
Совмещение сдвигов и повторений 244
Директива IRPC 245
Резюме 245
Контрольные вопросы 246

Глава 10. Написание графических приложений Windows 247


Структуры для окон 248
Функция MessageBox 249
Процедура WinMain 249
Процедура WinProc 250
Процедура ErrorHandler 251
Листинг программы 251
Запуск программы 253
Совместное использование языков высокого уровня и ассемблера 255
Согласование вызовов 255
Согласование имен 255
Согласование параметров 256
Простой пример использования языка ассемблера с языком Delphi 256
Резюме 259
Контрольные вопросы 259

Глава 11. Работа в DOS 260


Функции DOS 260
Функции вывода 260
Функции ввода 261
Буфер клавиатуры 262
Управляющие клавиши клавиатуры 265
Функции даты/времени 266
Управляющие клавиши 266
Вызовы процедур 267
Использование моделей памяти 269
Адресация переменных 270
Команда JMP для 16&разрядного режима 271
Перенаправление ввода&вывода 272

Глава 12. Справочный раздел 274


Система команд для процессоров Intel 274
Флаги 274
Формат и описание команд 275
Система команд 275
AAA 275
AAD 276
AAM 276
AAS 276
ADC 276

Содержание 13

Стр. 13
ADD 277
AND 277
BOUND 277
BSF, BSR 277
BSWAP 278
BT, BTC, BTR, BTS 278
CALL 278
CBW 279
CDQ 279
CLC 279
CLD 279
CLI 279
CMC 279
CMP 280
CMPS, CMPSB, CMPSW, CMPSD 280
CMPXCHG 280
CWD 280
CWDE 281
DAA 281
DAS 281
DEC 281
DIV 281
ENTER 282
HLT 282
IDIV 282
IMUL 282
IN 283
INC 283
INS, INSB, INSW, INSD 283
INT 284
INTO 284
IRET 284
Jcondition 284
JCXZ, JECXZ 285
JMP 285
LAHF 285
LDS, LES, LFS, LGS, LSS 286
LEA 286
LEAVE 286
LOCK 286
LODS, LODSB, LODSW, LODSD 286
LOOP, LOOPW 287
LOOP 287
LOOPE, LOOPZ 287
LOOPNE, LOOPNZ 287
MOV 288

14 Содержание

Стр. 14
MOVS, MOVSB, MOVSW, MOVSD 304
MOVSX 304
MOVZX 304
MUL 305
NEG 305
NOP 305
NOT 305
OR 306
OUT 306
OUTS, OUTSB, OUTSW, OUTSD 306
POP 306
POPA, POPAD 307
POPF, POPFD 307
PUSH 307
PUSHA, PUSHAD 307
PUSHF, PUSHFD 308
PUSHW, PUSHD 308
RCL 308
RCR 308
REP 309
REPcondition 309
RET, RETN, RETF 309
ROL 309
ROR 310
SAHF 310
SAL 310
SAR 310
SBB 311
SCAS, SCASB, SCASW, SCASD 311
SETcondition 311
SHL 312
SHLD 312
SHR 312
SHRD 312
STC 313
STD 313
STI 313
STOS, STOSB, STOSW, STOSD 313
SUB 314
TEST 314
WAIT 314
XADD 314
XCHG 315
XLAT, XLATB 315
XOR 315

Содержание 15

Стр. 15
Библиотека функций для работы в 32&разрядном режиме 299
Подключаемый файл для работы с библиотекой функций 327
Подключаемый файл с макроопределениями 336
Перечень команд DOS для Windows XP 341

Приложение. Ответы на контрольные вопросы 344


Глава 2 344
Глава 3 345
Глава 4 346
Глава 5 347
Глава 6 348
Глава 7 349
Глава 8 349
Глава 9 349
Глава 10 350

Предметный указатель 352

16 Содержание

Стр. 16
Введение
Данную книгу можно рассматривать как учебник, который позволит читателю понять
основы языка ассемблера и научиться создавать программы на этом языке. Для практиче&
ской работы необходимо использовать ассемблеры фирмы Microsoft, например MASM615
или другие, но при этом необходимо будет учитывать особенности этих ассемблеров. Хотя
во всех ассемблерах используется один и тот же базовый набор команд процессора, отличия
кроются в директивах, т.е. командах, которые исполняются самим ассемблером.
В книге в основном рассматривается 32&разрядный режим работы, т.е. используется
такой режим ассемблера, который позволяет обращаться к процедурам прикладного ин&
терфейса Windows. Приведены также некоторые сведения, специфичные только для 16&раз&
рядного режима, например, описание работы прерываний и понятие о DOS.
Программирование на языке ассемблера является низкоуровневым и позволяет обра&
щаться непосредственно к отдельным устройствам, поэтому данный язык более всего под&
ходит для разработки драйверов и таких программ, которым необходимо максимальное бы&
стродействие и код, учитывающий особенности работы отдельных устройств компьютера.
Книга является введением в ассемблер, поэтому в ней не отражены более расширен&
ные возможности языка ассемблера и последние команды, разработанные для новейших
версий процессоров. Такая задача и не ставилась, поскольку описать полностью возмож&
ности ассемблера невозможно в одной, даже очень объемной книге.
С целью сокращения объема книги в нее не включены полные тексты программ и много&
численные примеры &&&& такие программы можно в изобилии найти на специализированных
Internet&сайтах. Приведенные в книге примеры служат только для иллюстрации отдель&
ных тем и команд.

От издательства “Диалектика”
Вы, читатель этой книги, и есть главный ее критик. Мы ценим ваше мнение и хотим
знать, что было сделано нами правильно, что можно было сделать лучше и что еще вы хо&
тели бы увидеть изданным нами. Нам интересны любые ваши замечания в наш адрес.
Мы ждем ваших комментариев и надеемся на них. Вы можете прислать нам бумажное или
электронное письмо либо просто посетить наш Web&сервер и оставить свои замечания там.
Одним словом, любым удобным для вас способом дайте нам знать, нравится ли вам эта книга,
а также выскажите свое мнение о том, как сделать наши книги более интересными для вас.
Отправляя письмо или сообщение, не забудьте указать название книги и ее авторов, а так&
же свой обратный адрес. Мы внимательно ознакомимся с вашим мнением и обязательно
учтем его при отборе и подготовке к изданию новых книг.
Наши электронные адреса:
E&mail: info@dialektika.com
WWW: http://www.dialektika.com

Наши почтовые адреса:


в России: 115419, Москва, а/я 783
в Украине: 03150, Киев, а/я 152

Стр. 17
Глава 1

Предисловие
Знание языка ассемблера поможет программисту не только научиться программиро&
вать, но также лучше понять взаимодействие языков высокого уровня с аппаратным
обеспечением, что в конечном итоге приводит к общему пониманию всего процесса
выполнения программы на компьютере и способствует более осмысленному использова&
нию конструкций языка высокого уровня. Язык ассемблера помогает раскрыть все секреты
аппаратного и программного обеспечения, с его помощью можно получить представле&
ние о том, как аппаратная часть взаимодействует с операционной системой и как при&
кладные программы обращаются к операционной системе. Язык ассемблера &&&& это язык
низкого уровня, в котором каждая команда непосредственно интерпретируется в коман&
ду процессора. Поэтому изучение языка ассемблера позволит понять все нюансы обра&
ботки информации в процессоре и поможет программисту в управлении процессором
и операционной системой, а также получении прямого доступа к аппаратуре.
Язык ассемблера незаменим при оптимизации критических блоков в прикладных
программах с целью повышения их быстродействия. Например, многие фрагменты ис&
ходных кодов языка Delphi написаны именно на языке ассемблера, что и определяет бы&
стродействие программ, разработанных на языке Delphi.
Язык ассемблера используется также при изучении курса аппаратного обеспечения
компьютера и операционных систем, когда есть возможность проводить эксперимен&
тальную работу.
Возможно, вам придется создавать обслуживающие программы (утилиты) или писать
драйверы для отдельных устройств. Во многих случаях это невозможно реализовать на
языках высокого уровня и только язык ассемблера позволит это сделать.

Что такое язык ассемблера


Язык ассемблера — это специфический язык программирования со взаимно одно&
значным соответствием между его операторами и командами процессора. Язык ассемб&
лера существует для каждого типа процессоров или целого семейства процессоров. Это
связано с тем, что команды на языке ассемблера должны иметь взаимно однозначное со&
ответствие с системой машинных команд и должны быть согласованы с архитектурой
компьютера. Язык ассемблера для процессоров Intel имеет систему команд, работать
с которыми могут различные процессоры производства Intel, такие как 8086, 8088, 80186,
80286, 80386, 80486, Celeron и Pentium.
Ассемблер — это программа, преобразовывающая исходные коды языка ассемблера в ма&
шинные команды. Ассемблер может создавать листинг программы с номерами строк, адре&
сами памяти, исходными операторами и таблицей перекрестных ссылок символов и пере&
менных, используемых в программе. Совместно используемая с ассемблером программа,
называемая компоновщик, собирает отдельные файлы, созданные ассемблером, в единую
исполняемую программу. В блок базовых программ входит также отладчик, позволяющий
программисту проверять и трассировать исполняемую программу и контролировать содер&
жимое памяти. Программы на языке ассемблера можно запускать либо в операционной

Стр. 18
отсутствует. 19

системе MS&DOS (или в режиме эмуляции MS&DOS в системах Windows), либо непосредст&
венно в Windows. Наиболее популярны для семейства процессоров Intel ассемблеры MASM
(Microsoft Assembler) и TASM (Borland Turbo Assembler). Язык ассемблера называется язы&
ком низкого уровня потому, что он очень близок к машинному языку по своей структуре
и выполняемым функциям. Каждая команда языка ассемблера имеет взаимно однозначное
соответствие с машинными командами, что отличает его от языков высокого уровня, для
которых характерно то, что один оператор транслируется во множество машинных команд.

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


Может сложиться впечатление, что программы, написанные на языке ассемблера, не
требуют особых усилий при их разработке &&&& необходимо только освоить небольшое ко&
личество исходных команд и можно реализовывать логику программы. Однако даже при
реализации простых программ следует вручную контролировать довольно много элемен&
тов, которые в языке высокого уровня контролируются автоматически. Язык ассемблера
требует особого внимания к деталям и поэтому написание и отладка программы занима&
ют очень много времени, но при этом получаются небольшие выполняемые модули, за&
нимающие мало места в памяти и позволяющие достичь приемлемой скорости работы
даже на медленных компьютерах. Значительные трудозатраты при написании программ
на языке ассемблера не позволяют его использовать для создания больших и сложных
программ, для этих целей существуют языки программирования высокого уровня, с по&
мощью которых можно создавать структурированные программы, облегчающие их напи&
сание и отладку. На современном этапе развития такие объектно&ориентированные язы&
ки, как C++, Java, C# или Delphi, стали основным инструментом написания огромных
программ, включающих миллионы строк кода.
Язык ассемблера в основном используется при написании отдельных сегментов при&
кладных программ (для повышения скорости работы и прямого доступа к оборудова&
нию), а также встроенных системных программ, которые хранятся в программируемой
памяти отдельных устройств.
Некоторые языки высокого уровня, например С++ или Delphi, имеют уникальную
способность совмещения языковых структур высокого уровня с низкоуровневыми дета&
лями. При этом возможен прямой доступ к аппаратуре, хотя в результате может полу&
читься непереносимая программа (программа, не работающая на других типах компьюте&
ров, отличных от того, для которого она разрабатывалась). Компиляторы этих языков
имеют возможность создавать ассемблерный код, с которым может работать програм&
мист перед тем, как получить исполняемый код.
Совмещение языков высокого уровня и языка ассемблера обычно используется при
разработке приложений для встроенных систем, таких как картриджи для компьютерных
игр, микроконтроллеры, телекоммуникационное оборудование, системы контроля ок&
ружающей среды, промышленные роботы, системы безопасности и др.
Можно написать подпрограммы на языке ассемблера и вызывать их из программ, на&
писанных на языках высокого уровня. В отдельных случаях такие подпрограммы назы&
вают интерфейсными программами, поскольку они обеспечивают взаимодействие с обо&
рудованием компьютера. В компьютерных играх, например, интерфейсные программы
могут обеспечивать прямой доступ к аппаратным портам видеоадаптера и звуковой плате
для достижения высочайшего уровня производительности.
Подпрограммы на языке ассемблера, вызываемые операционной системой для рабо&
ты с отдельными устройствами, называют драйверами устройств. Как правило, драйверы
обслуживают все прикладные программы. Например, когда программе необходимо считать

Глава 1. Предисловие 19

Стр. 19
блок данных из файла, операционная система вызывает подпрограмму драйвера для уст&
ройства считывания.
Программируя на языке ассемблера, необходимо абсолютно точно представлять рас&
пределение данных, иначе ошибки неизбежны. Языки высокого уровня скрывают от про&
граммиста специфические детали для удобства использования и получения переносимого
кода. С другой стороны, используя язык ассемблера, можно учитывать специфику отдель&
ных устройств и почти не иметь ограничений при реализации интерфейса или драйвера.
Главный недостаток языка ассемблера состоит в том, что написанная для одного типа
компьютеров программа не может быть перекомпилирована и использована на других
типах компьютеров. Для каждого семейства процессоров используется свой язык ассемб&
лера со своим синтаксисом &&&& это следствие того, что язык ассемблера очень близок ма&
шинным командам и архитектуре процессора. Для того чтобы создаваемая программа
могла работать на различных типах компьютеров, необходимо использовать языки высо&
кого уровня, такие как С++, C#, Delphi или Java.

Машинный язык
Компьютер не воспринимает непосредственно команды на языке ассемблера, он по&
нимает только машинные команды. Машинные команды состоят из чисел, которые ин&
терпретируются процессором. Процессор обычно имеет встроенный интерпретатор,
представляющий микропрограмму и преобразовывающий машинные команды непосред&
ственно в управляющие сигналы.
Машинный язык включает примитивные операторы, которые могут исполнить про&
стые арифметические действия или переместить данные. Система команд процессора со&
стоит из машинных команд, которые он может исполнить. Машинный язык для семей&
ства процессоров Intel в своей основе имеет систему команд процессора 8086, которые
могут выполняться процессорами всех последующих модификаций. Такая концепция
называется совместимостью сверху вниз.
По мере совершенствования процессоров появляются новые команды, которые уже
не могут использоваться при написании программ для старых моделей. Такие команды
необходимо использовать для повышения скорости работы процессора и разработки
программ, учитывающих специфику современных аппаратных устройств.
Конечно, можно написать целую программу на машинном языке, вводя в память дво&
ичные числа машинных команд и заставляя процессор исполнять их. Но человеческий
мозг не приспособлен для запоминания большого количества чисел и поэтому вместо
ввода чисел используют мнемонически понятный набор команд — язык ассемблера, ко&
торый преобразует мнемонические коды в машинные команды.

20 Глава 1. Предисловие

Стр. 20
Глава 2

Архитектура компьютера
В этой главе...

Современные процессоры
Устройство компьютера
Операционные системы и память
Ввод&вывод
Резюме
Контрольные вопросы

Первые процессоры Intel


История процессоров Intel начинается с 8&разрядного процессора Intel 8080, который
мог работать только с памятью размером не более 64 Кбайт.
Процессор Intel 8086, созданный в 1978 году, уже можно назвать родоначальником
семейства процессоров архитектуры Intel. Основными нововведениями в процессоре
8086 были 16&разрядные регистры, 16&разрядная шина данных и сегментная модель па&
мяти, что позволяло программам использовать до 1 Мбайт ячеек &&&& с таким объемом уже
можно было создавать сложные коммерческие приложения. В 1980 году компания IBM
представила первый персональный компьютер, в котором использовался процессор
Intel 8088. Новый процессор почти ничем не отличался от 8086, разве что был значитель&
но дешевле за счет использования 8&разрядной шины данных.
Математический сопроцессор Intel 8087 был разработан для совместной работы с про&
цессорами 8086/8088. Использование сопроцессора позволило значительно повысить
скорость выполнения операций с плавающей запятой, которые ранее выполнялись с по&
мощью сложных подпрограмм.
Успеху процессоров Intel способствовало то, что каждый новый процессор семейства
Intel был совместим с более ранними поколениями по используемой системе команд. Это
позволяло применять все ранее разработанное программное обеспечение на новейших
компьютерах без какой&либо его модификации. Во вновь разрабатываемых программах
можно было использовать дополнительные возможности, предоставляемые новейшими
процессорами, хотя они уже не могли выполняться на процессорах старших поколений.
Следующий процессор 80286 был установлен в компьютерах серии IBM PC/AT и в то
время значительно поднял планку уровня производительности. Процессор мог адресо&
вать 16 Мбайт памяти, используя 24&разрядную шину адреса, и работал с тактовой часто&
той от 12 до 26 МГц. Важной особенностью было то, что он мог работать как в реальном
режиме (подобно 8086/8088), так и в защищенном. Защищенный режим давал возмож&
ность операционной системе запускать программы в отдельных сегментах памяти, что

Стр. 21
исключало их взаимное влияние. Операционная система DOS поддерживала работу
только в реальном режиме, но надстройки, подобные Microsoft Windows, уже могли ис&
пользовать защищенный режим.
В 1985 году Intel представила процессор 80386 с 32&разрядными регистрами, который мог
работать с 32&разрядной внешней шиной данных. Шина данных также стала 32&разряд&
ной, что позволило адресовать 4 Гбайта памяти. Появилась торговая марка Intel386. Бо&
лее дешевый процессор 80386&SX имел 16&разрядную шину данных.
Процессор 80386 мог работать в трех режимах: реальном, защищенном и виртуальном.
Это позволяло на одном процессоре запускать несколько программ реального режима
как отдельные ‘‘виртуальные машины’’. Другими словами, стандартные приложения
DOS могли работать одновременно, причем считалось, что каждое из них имеет доступ
ко всей памяти объемом в 1 Мбайт.
Благодаря схеме адресации памяти, названной виртуальной, вся используемая процес&
сором память могла превышать физический размер памяти компьютера. Процессор авто&
матически перемещал содержимое временно не используемых участков памяти на жесткий
диск и предоставлял эту память другим программам. Именно этот процессор помог опера&
ционной системе Microsoft Windows 3.0 достичь грандиозной популярности. Операционная
система Windows предоставляла пользователям возможность работать как в защищенном,
так и в виртуальном режимах. В виртуальном режиме в отличие от операционной системы
DOS можно было запускать несколько больших приложений одновременно.
Семейство процессоров Intel486 включало в себя процессоры 486DX, 486DX2 и 486SX.
В архитектуре процессора были использованы элементы высокопроизводительных про&
цессоров RISC. Это связано с тем, что изначально применяемая с процессорами Intel сис&
тема команд была довольно неудобной для обработки в процессоре, так как имела слож&
ную структуру, а отдельные команды имели разную длину (от одного до шестнадцати байт).
Но отказаться от этой системы команд Intel уже не могла, и поэтому был использован
блок преобразования в более удобные команды RISC. Последовательность выполнения
команд была изменена, режимы декодирования и исполнения могли работать одновре&
менно, что позволяло выполнять многие команды всего за один такт. Это была первая
серия процессоров, у которых блок вычислений с плавающей запятой (математический
сопроцессор) находился с ядром процессора на одном кристалле, что и обеспечило значи&
тельный прирост производительности. Процессор Intel486 имел внутреннюю кэш&память
первого уровня объемом 8 Кбайт, в которой хранились последние используемые коман&
ды. К ним обеспечивался очень быстрый доступ. В более дешевом Intel486SX был отклю&
чен блок вычислений с плавающей запятой.
Дальнейшим шагом Intel был процессор Pentium. Согласно проведенным тестам, про&
изводительность процессора Pentium с тактовой частотой 90 МГц почти вдвое превышала
производительность процессоров Intel486. Pentium мог выполнять больше одной команды
за такт, потому что в нем использовалась суперскалярная архитектура с двумя конвейерами
для команд. Другими словами, одновременно декодироваться и выполняться могли сразу
две команды. Конвейеры были надежным и испытанным способом повышения произво&
дительности, так как они уже давно использовались в процессорах архитектуры RISC для
рабочих и графических станций. Способствовали успеху Pentium и такие особенности:
встроенная кэш&память для команд и данных объемом по 8 Кбайт (486 имел толь&
ко одну кэш&память);
улучшенный блок вычислений с плавающей запятой;
предсказание ветвлений позволяло процессору Pentium анализировать последователь&
ность команд. Когда встречались команды условия (например, цикл или команда IF),

22 Глава 2. Архитектура компьютера

Стр. 22
отсутствует. 23

процессор рассчитывал наиболее вероятный адрес очередной последовательности


команд и загружал их для выполнения;
первый процессор Pentium имел 3,1 млн транзисторов, что значительно больше,
чем у Intel486 (всего 1,3 млн);
Pentium имел 32&разрядную шину адреса и 64&разрядную шину данных для внеш&
них устройств (у Intel486 только 32 разряда).
Безусловно, Pentium стал большим шагом вперед. Тактовая частота первых процессо&
ров, составлявшая 66 МГц, быстро достигла отметки в 400 МГц и в настоящее время со&
ставляет 3,5 ГГц.

Ядро процессора
Не будем вдаваться в тонкости построения процессоров, хотя эта тема и интересна сама
по себе. Для программиста важно знать только общий принцип работы процессора и те его
элементы, к которым можно обратиться в процессе написания программы. На рис. 2.1 по&
казан блок управления, который распределяет задания и выбирает данные из памяти, ариф&
метико&логический блок, где непосредственно и происходят вычисления, и регистры, в кото&
рых хранятся все необходимые данные для оперативного выполнения очередной команды.

Рис. 2.1. Архитектура простейшего компьютера

На рисунке помимо процессора изображены системная шина и память, которые яв&


ляются неотъемлемой частью любого компьютера. Хотя они в состав процессора не вхо&
дят, без них процессор не сможет выполнить ни одной команды. Процессор последова&
тельно выполняет три основных шага: выборку, декодирование и исполнение. Работа
каждого шага происходит в несколько этапов.
Выборка очередной команды:
команда помещается во внутреннюю память процессора, называемую очередь;
обновляется программный счетчик.
Декодирование команд:

Глава 2. Архитектура компьютера 23

Стр. 23
рассчитывается адрес;
выбираются операнды из памяти.
Исполнение команд:
выполняются необходимые расчеты;
сохраняются результаты в памяти или регистрах;
устанавливаются необходимые состояния флагов.
Подобно системной шине, в процессоре существует внутренняя шина, которая пред&
ставляет собой множество параллельных проводников, связывающих отдельные блоки
процессора для обмена данных между ними. Если данные должны быть считаны из
внешней памяти, блок управления рассчитывает их адрес и устанавливает его на внеш&
ней шине адреса, являющейся частью системной шины. Контроллер памяти считывает
адрес с шины адреса и размещает затребованные данные на шине данных, также входящей
в состав системной шины. Затем он устанавливает сигнал готовности на одной из линий
контрольных сигналов. Процессор проверяет наличие сигнала готовности на этой линии
и после его появления считывает данные во внутреннее устройство хранения. Обращение
к внешней памяти &&&& самый длительный процесс относительно остальных этапов обра&
ботки данных в процессоре. Системная шина и память уже давно стали узким местом при
повышении общей производительности компьютера. В первых процессорах даже вводи&
ли специальные холостые циклы процессора при ожидании данных из внешней памяти.
В настоящее время эти проблемы решают за счет усложнения конструкции компьютера
и процессора, устанавливая более быстродействующую и, соответственно, более дорогую
внешнюю память поближе к ядру процессора. Емкость такой памяти не может быть
очень большой, так как это грозит значительным повышением стоимости компьютера,
поэтому такая память является буферной между основной памятью и процессором, со&
храняя только небольшое количество команд, необходимых процессору для оперативной
работы. Это позволяет значительно снизить общее время обращения к памяти и поднять
производительность компьютера. Такую память называют кэш%памятью. Разработчики
могут установить несколько уровней кэш&памяти, оптимизируя стоимость и производи&
тельность компьютера, но самыми быстродействующими участками памяти являются
регистры, которые расположены непосредственно на ядре процессора.
Регистры &&&& это участки высокоскоростной памяти для хранения данных в процессо&
ре. Они непосредственно подключены к блоку управления и арифметико&логическому
устройству, поэтому доступ к регистрам из этих блоков происходит значительно быстрее,
чем доступ к внешней памяти. Команды, использующие только обращение к регистрам,
выполняются очень быстро, в отличие от команд, требующих получения операндов из
внешней памяти, что необходимо учитывать при программировании, если программа
должна работать максимально быстро.
Каждая отдельная операция, которая выполняется внутри процессора, должна быть
синхронизирована с работой остальных устройств. Отдельное устройство синхронизации
вырабатывает все тактовые импульсы, необходимые для работы процессора. Тактовая
частота у современных процессоров может достигать нескольких гигагерц.

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

24 Глава 2. Архитектура компьютера

Стр. 24
отсутствует. 25

всей программы. Регистры подразделяются на регистры общего назначения, а также регистры


состояния и управления. К регистрам общего назначения обычно относят регистры дан&
ных, регистры сегментов, а также регистры указателей и индексов. Эти регистры доступны
при написании программы и программист должен их использовать при работе с данны&
ми. Другая группа регистров &&&& это регистры флагов и регистры состояния и управления,
которые изменяются автоматически при выполнении программы, за редким исключени&
ем программист не может непосредственно изменять их. Регистр флагов состоит из от&
дельных разрядов, которые или управляются непосредственно процессором при опера&
циях, или отражают состояние процессора после выполнения операции. Отдельные
флаги сообщают о переполнении, направлении перемещения, разрешении прерываний,
режиме трассировки, знаке результата, нулевом результате, вспомогательном переносе,
четности или переносе (рис. 2.2).
Регистры данных используются при арифметических операциях и при перемещении
данных. Каждый регистр содержит 32&разрядное значение, но существует также возмож&
ность обратиться к каждому байту отдельно. Например, регистр EAX будет 32&разрядным
регистром, но для обращения к его младшей половинке можно использовать аббревиату&
ру AX. Для обращения к старшему байту из младшей половинки можно использовать
обозначение AH, а младший байт будет называться AL.
Такая запутанная система обозначений образовалась потому, что компания Intel по&
следовательно развивала совместимую систему команд, в которой сначала использова&
лись 8&разрядные процессоры, затем 16&разрядные и, наконец, наиболее распространен&
ные в настоящее время 32&разрядные процессоры. В настоящее время происходит
переход к 64&разрядным процессорам, но благодаря совместимости сверху вниз, все ра&
нее разработанные программы будут работать и на этих процессорах.
В командах можно адресоваться к 32&разрядным данным с использованием регистров
EAX, EBX, ECX и EDX, к 16&разрядным данным с использованием регистров AX, BX, CX и DX
или к 8&разрядным данным с использованием регистров AH, AL, BH, BL, CH, CL, DH и DL.
Когда 32&разрядное значение изменяется, изменяются и соответствующие ему 16&разряд&
ные значения. Предположим, что регистр EAX включает все нули. Соответствующие AX,
AH и AL также равны нулю. Если в AX поместить значение 0001001001101111, то AL
немедленно станет равным 01101111.
Каждый регистр данных имеет приоритетное назначение, которое неявно использует&
ся в ряде команд:
EAX (аккумулятор). Используется при арифметических операциях. Другие опера&
ции также будут выполняться несколько быстрее при использовании регистра AX.
EBX (базовый регистр). Регистр EBX содержит адреса процедур или переменных.
Для этих же целей используют регистры SI, DI и BP. Регистр BX также можно ис&
пользовать при выполнении арифметических операций и перемещении данных.
ECX (счетчик). Регистр ECX выполняет функции счетчика для операций повторе&
ния и циклических команд. При выполнении этих команд увеличение значения
происходит автоматически.
EDX (регистр данных). Регистр данных EDX выполняет особые функции при опе&
рациях умножения и деления. Например, при умножении в него помещается ре&
зультат операции.

Глава 2. Архитектура компьютера 25

Стр. 25
31 15 0 Номера битов для 32разрядных регистров
Регистры данных
EAX AH AL Аккумулятор
EBX BH BL Базовый регистр
EDX CH CL Счетчик
ECX DH DL Регистр данных

Регистры указателей и индексов


ESP SP Указатель стека
EBP BP Указатель базы
ESI SI Индекс источника
EDI DI Индекс приемника

Регистры сегментов
CS Регистр сегмента команд
DS Регистр сегмента данных
SS Регистр сегмента стека
ES Регистр дополнительного сегмента
FS Регистр дополнительного сегмента
GS Регистр дополнительного сегмента

Регистры состояния и управления


EFLAGS FLAGS Указатель команд

EIP IP Регистр флагов

79 Регистры сопроцессора 0
ST(0)
ST(1)
ST(2)
ST(3)
ST(4)
ST(5)
ST(6)
ST(7)

63 Регистры целочисленного MMXрасширения 0


MMX0
MMX1
MMX2
MMX3
MMX4
MMX5
MMX6
MMX7

127 Регистры MMXрасширения для чисел с плавающей запятой 0


XMM0
XMM1
XMM2
XMM3
XMM4
XMM5
XMM6
XMM7

Рис. 2.2. Основные регистры 32 разрядных процессоров

26 Глава 2. Архитектура компьютера

Стр. 26
отсутствует. 27

Также в процессоре имеется четыре 16&разрядных регистра сегментов, используемых


при указании адреса размещения кода, данных и стека. Эти регистры необходимы в ре&
жиме Real Mode и используются при обращениях к памяти для расчета адреса.
CS (сегмент кода). В 16&разрядном режиме содержит сегментную часть адреса рас&
положения команд программы. При 32&разрядном программировании указывает
на соответствующий элемент таблицы дескрипторов.
DS (сегмент данных). В 16&разрядном режиме содержит сегментную часть адреса
расположения переменных. При 32&разрядном программировании указывает на
соответствующий элемент таблицы дескрипторов.
SS (сегмент стека). В 16&разрядном режиме сдержит сегментную часть адреса сте&
ка. При 32&разрядном программировании указывает на соответствующий элемент
таблицы дескрипторов.
ES (дополнительный сегмент). Используется дополнительно для хранения сег&
ментной части адреса переменных.
32&разрядные регистры указателей и индексов содержат величину смещения, используе&
мую при расчете адресов данных и команд. Термин ‘‘смещение’’ указывает расстояние пере&
менной, метки или команды от начала базового сегмента. Использование регистров индексов
повышает скорость работы со строками, массивами и другими структурами со множеством
элементов. Регистры указателей и индексов обозначаются как EBP, ESP, ESI и EDI.
EBP (указатель базы). Регистр EBP включает смещение от начала сегмента стека.
Этот регистр часто используют в подпрограммах для определения адресов пере&
менных, которые помещаются в стек вызывающими программами.
ESP (указатель стека). Этот регистр содержит смещение вершины стека. Исполь&
зуя регистры SP и SS, можно рассчитать полный адрес вершины стека.
ESI (индекс отправителя). Используется при исполнении команд перемещения.
Он указывает смещение начала данных, которые должны быть перемещены.
EDI (индекс получателя). Указывает смещение адреса, куда перемещаются данные.

Регистры состояния и управления


В регистре&указателе команд EIP находится адрес следующей команды, которая
должна быть выполнена.
Регистр флагов &&&& это специальный регистр, в котором позиция каждого бита ото&
бражает состояние процессора или результаты арифметических операций, причем все за&
действованные биты имеют соответствующие имена. На рис. 2.3 показаны все исполь&
зуемые флаги 32&разрядных процессоров.
Нет необходимости запоминать положение соответствующего бита для каждого флага &&&&
можно воспользоваться специальными командами для управления флагами. Флаг считается
установленным, если соответствующий бит равен 1, и флаг считается сброшенным, если бит
равен 0. Существует два основных типа флагов: флаги контроля и флаги состояния.
Программист может непосредственно изменять состояние некоторых флагов для управ&
ления процессором. Речь идет о флагах направления, разрешения прерываний и трасси&
ровки, которые обозначаются как DF, IF и TF.
Флаг направления DF влияет на передачу блока данных при выполнении таких ко&
манд, как MOVS, CMPS и SCAS. Если флаг установлен на 0, осуществляется счет вперед,
если на 1, то назад. Эти флаги также могут изменяться с помощью команд STD и CLD.

Глава 2. Архитектура компьютера 27

Стр. 27
Флаг прерывания IF устанавливается, если возникает системное прерывание. Сис&
темные прерывания могут быть произведены различными устройствами, например кла&
виатурой, драйверами дисков или системным таймером. В некоторых программах иногда
необходимо запретить выполнение прерываний на период выполнения критичных по вре&
мени процедур. Флаг считается установленным, если соответствующий бит равен 1, и сбро&
шенным, если бит равен 0. Состояние флага изменяется командами CLI и STI.
Флаг трассировки TF заставляет процессор останавливаться после выполнения каждой
команды, что обычно используется при пошаговой отладке программы. Когда флаг уста&
новлен, программа отладки позволяет программисту последовательно просматривать выпол&
нение команд (трассировка). Флаг установлен, если бит равен 1, и сброшен при нулевом бите.

Цикл выполнения команды


Основные этапы цикла выполнения команды во всех процессорах одинаковы, разни&
ца заключается в количестве одновременно выполняемых циклов за один такт и в спосо&
бе упорядочения данных. Предположим, необходимо добавить единицу к операнду, на&
ходящемуся в памяти. Для этого нужно рассчитать адрес операнда, поместить адрес на
шину данных, дождаться ответа памяти, получить операнд, поместить его в арифметико&
логическое устройство, произвести операцию сложения и отправить операнд обратно
в память. При этом процессор всегда будет циклически осуществлять три основных опе&
рации: выборку, декодирование и выполнение. Каждый шаг в этом цикле занимает, по
крайней мере, один такт системного таймера и называется тактом операции.
Выборка. Блок управления выбирает команды, копирует их из памяти в очередь
команд и увеличивает на единицу программный счетчик.
Декодирование. Блок управления определяет тип команды, которая должна вы&
полняться. Если требуется получить операнды из памяти, блок управления орга&
низует процесс считывания необходимых операндов. Он загружает эти операнды
в арифметико&логическое устройство и сообщает ему, какой тип операции должен
быть выполнен.
Выполнение. Арифметико&логическое устройство выполняет команду и возвра&
щает результат, затем обновляет флаги состояния.
Важной особенностью современных процессоров является возможность параллель&
ной работы блока контроля и арифметико&логического устройства. Не дожидаясь окон&
чания выполнения операции арифметико&логическим устройством, блок контроля вы&
бирает следующую команду из памяти и помещает ее в очередь, что повышает скорость
работы процессора.

Современные процессоры
Несмотря на то что система команд первых процессоров Intel в своей основе не изме&
нялась до настоящего времени и идеология обработки команд также не претерпела суще&
ственных перестроек, тем не менее производительность современных процессоров зна&
чительно возросла и обычный настольный компьютер сейчас имеет такую же
производительность, какую десяток лет назад имели только отдельные суперкомпьюте&
ры. Росту производительности работы процессора способствовало то, что разработчики
предпринимали усилия по повышению скорости обработки данных сразу по нескольким
направлениям. Во&первых, улучшение технологического процесса производства микросхем

28 Глава 2. Архитектура компьютера

Стр. 28
отсутствует. 29

способствовало повышению тактовой частоты работы процессора, во&вторых, уменьше&


ние размеров элементов позволяло размещать на одном кристалле значительно большее
количество транзисторов, число которых в современных процессорах достигает несколь&
ких десятков и даже сотен миллионов. Это позволило создать несколько конвейеров для
параллельной обработки данных и повысить число разрядов, передаваемых по шинам за
один такт. Более того, синхронизация обработки данных теперь уже выполнялась не
только по одному такту, но и по переднему и заднему фронтам такта.
Все это вместе взятое, а также улучшенная логика обработки команды и введение
элементов предсказания переходов программы на отдельные ветви позволило радикаль&
но поднять производительность процессора.

Улучшенный цикл выполнения команды


Процессор 80386 стал первым процессором в семействе процессоров Intel, в котором
была реализована возможность параллельного выполнения команд. Шесть основных
устройств работают совместно:
шинный интерфейс &&&& доступ к памяти и ввод&вывод;
блок предварительной выборки &&&& принимает машинные команды из шинного
интерфейса и размещает их в предварительной очереди;
блок декодирования &&&& декодирует машинные команды из предварительной оче&
реди и транслирует их в микрокод;
исполнительный блок &&&& выполняет команды в микрокодах из блока декодирования;
блок сегментов &&&& преобразовывает логические адреса в линейные адреса и кон&
тролирует защиту;
блок разбиения на страницы &&&& преобразовывает линейные адреса в физические,
контролирует защиту страниц и ведет список адресов тех страниц, которые ис&
пользовались недавно.
Именно такая архитектура используется в современных процессорах, различие за&
ключается только в количестве конвейеров, количестве функций, выполняемых отдель&
ным конвейером, и тактовой частоте.
С точки зрения программиста, интерес представляют новые регистры для повышения
скорости обработки изображений, хотя их можно использовать не только для работы
с графикой, но и для обработки последовательных потоков данных.

32*разрядные процессоры
Все процессоры Intel, начиная с 80386, имеют 32&разрядные регистры (см. рис. 2.2). Это
означает, что все программы, рассчитанные на эти процессоры, могут оперировать 32&
разрядными данными и использовать регистры индексов для адресации 4 Гбайт памяти.
Для работы с 32&разрядным форматом были добавлены новые команды.
Регистры сегментов остались 16&разрядными, но были добавлены два новых регистра &&&&
FS и GS. Не получили условного обозначения и верхние половины 32&разрядных регистров.
Для того чтобы разместить значение в верхней половине регистра, необходимо сначала
разместить это значение в нижней половине (например AX) и применить команду сдвига.
Немного подробнее рассмотрим регистры состояния и управления, так как в послед&
нее время в основном используются 32&разрядные процессоры, содержащие, соответст&
венно, 32&разрядные регистры флагов. При программировании нужно будет обращаться
именно к этим флагам.

Глава 2. Архитектура компьютера 29

Стр. 29
Регистры состояния и управления 32разрядного процессора
В 32разрядный процессор включены несколько регистров, которые постоянно со
держат информацию о состоянии как самого процессора, так и программы, команды ко
торой в данный момент загружены на конвейер.
К этим регистрам относятся:
регистр флагов EFLAGS;
регистр указателя команды EIP.
Используя эти регистры, можно получать информацию о результатах выполнения ко
манд и влиять на состояние самого процессора. Рассмотрим подробнее назначение и со
держимое этих регистров.
Регистр флагов EFLAGS
Регистр флагов EFLAGS содержит 32 разряда. Отдельные биты данного регистра име
ют определенное функциональное назначение и называются флагами. Младшая часть
этого регистра полностью аналогична регистру FLAGS для процессоров 8086. На рис. 2.3
показано содержимое регистра EFLAGS.
Флаг переноса
Флаг четности
Флаг вспомогательного переноса
Флаг нуля
Флаг знака
Флаг направления
Флаг переполнения
Флаг привелигерованности вводавывода
Флаг вложенности задач
31 ... 21 20 11 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
0 ... ID VIP VIF AC VM RF 0 NT IOPL OF DF IF TF SF ZF 0 AF 0 PF 1 CF

Флаг трассировки
Флаг прерывания
Флаг возобновления
Флаг виртуального 8086
Флаг контроля выравнивания
Флаг виртуального прерывания
Флаг отложенного прерывания
Флаг идентификации процессора

Рис. 2.3. Регистр флагов 32разрядного процессора

Исходя из особенностей использования, флаги можно разделить на три группы.


Восемь флагов состояния. Эти флаги могут изменяться после выполнения машин
ных команд. Флаги состояния регистра EFLAGS отражают особенности результата
исполнения арифметических или логических операций. Это дает возможность
анализировать состояние вычислительного процесса и реагировать на него с по
мощью команд условных переходов и вызовов подпрограмм. В табл. 2.1 приведены
флаги состояния и указано их назначение.
Один флаг управления. Обозначается как DF. Он находится в десятом бите регист
ра EFLAGS и используется командами обработки последовательностей байт. Зна
чение флага DF определяет направление поэлементной обработки в этих операци
ях: от начала строки к концу (DF=0) либо, наоборот, от конца строки к ее началу
(DF=1). Для работы с флагом DF существуют специальные команды: CLD (сбросить
флаг DF) и STD (установить флаг DF). Применение этих команд позволяет устано
вить нужное состояние флага DF и обеспечить автоматическое увеличение или
уменьшение счетчиков при выполнении операций со строками.

30 Глава 2. Архитектура компьютера

Стр. 30
отсутствует. 31

Пять системных флагов, управляющих вводом&выводом, маскируемыми прерыва&


ниями, отладкой, переключением между задачами и режимом виртуального 8086.
В прикладных программах не рекомендуется изменять без серьезной необходимо&
сти эти флаги, так как в большинстве случаев это приведет к прерыванию работы
программы. В табл. 2.1 перечислены системные флаги и их назначение.

Таблица 2.1. Описание флагов 32*разрядного процессора


Флаг Описание Бит Состояние и назначение
CF Флаг переноса 0 1 &&&& арифметическая операция произвела перенос из старшего бита
результата. Старшим является 7, 15 или 31 бит в зависимости
от размерности операнда;
0 &&&& переноса не было
PF Флаг паритета 2 1 &&&& 8 младших разрядов (флаг только для 8 младших разрядов)
результата содержат четное число единиц;
0 &&&& 8 младших разрядов результата содержат нечетное число единиц
AF Вспомогательный 4 Только для команд, работающих с BCD&числами. Фиксирует факт
флаг переноса займа из младшей тетрады результата:
1 &&&& в результате операции сложения был произведен перенос
из разряда 3 в старший разряд или при вычитании был заем
в третий разряд младшей тетрады из значения в старшей тетраде;
0 &&&& переносов в третий разряд и займов из третьего разряда
младшей тетрады не было
ZF Флаг нуля 6 1 &&&& результат нулевой;
0 &&&& результат ненулевой
SF Флаг знака 7 Отражает состояние старшего бита результата (биты 7, 15 или 31
для 8&, 16& или 32&разрядных операндов, соответственно):
1 &&&& старший бит результата равен 1;
0 &&&& старший бит результата равен 0
OF Флаг 11 Флаг используется для фиксации факта потери значащего бита
переполнения при арифметических операциях:
1 &&&& в результате операции происходит перенос или заем
из старшего, знакового бита результата (биты 7, 15 или 31);
0 &&&& в результате операции не происходит переноса или заема
из старшего, знакового бита результата
IOPL Уровень 12, 13 Используется в защищенном режиме работы процессора
привилегий для контроля доступа к командам ввода&вывода в зависимости
ввода&вывода от привилегированности задачи
NT Флаг вложенности 14 Используется в защищенном режиме работы процессора
задачи для фиксации того, что одна задача вложена в другую
TF Флаг трассировки 8 Предназначен для организации пошаговой работы процессора:
1 &&&& процессор генерирует прерывание с номером 1 после выполнения
каждой машинной команды. Используется при отладке программ;
0 &&&& обычная работа
IF Флаг прерывания 9 Предназначен для разрешения или запрещения (маскирования)
аппаратных прерываний (прерываний по входу INTR):
1 &&&& аппаратные прерывания разрешены;
0 &&&& аппаратные прерывания запрещены
RF Флаг возобновления 16 Используется при обработке прерываний от регистров отладки

Глава 2. Архитектура компьютера 31

Стр. 31
Окончание табл. 2.1
Флаг Описание Бит Состояние и назначение
VM Флаг режима 17 Признак работы процессора в режиме виртуального 8086:
виртуального 8086 1 &&&& процессор работает в режиме виртуального 8086;
0 &&&& процессор работает в реальном или защищенном режиме
АС Флаг контроля 18 Предназначен для разрешения контроля выравнивания при
выравнивания обращениях к памяти. Используется совместно с битом AM
в системном регистре CR0. К примеру, Pentium разрешает размещать
команды и данные с любого адреса. Если требуется контролировать
выравнивание данных и команд по адресам, кратным 2 или 4,
то установка этих битов приведет к тому, что все обращения по
некратным адресам будут генерировать исключительную ситуацию
VIF Флаг виртуального 19 При определенных условиях (одно из которых &&&& работа процессора
прерывания в V&режиме) является аналогом флага IF. Флаг VIF используется
совместно с флагом VIP. Флаг появился в процессоре Pentium
VIP Флаг отложенного 20 Устанавливается в 1 для индикации отложенного прерывания.
виртуального Используется при работе в V&режиме совместно с флагом VIF.
прерывания Флаг появился в процессоре Pentium
ID Флаг 21 Используется для того, чтобы показать факт поддержки процессором
идентификации команды CPUID. Если программа может установить или сбросить
этот флаг, то это означает, что данная модель процессора
поддерживает инструкцию CPUID

Регистр указателя команды EIP


Регистр указателя команды EIP имеет 32 разряда и содержит адрес следующей, под&
лежащей выполнению команды. Этот регистр непосредственно программисту недосту&
пен, но загрузка и изменение его значения производятся различными командами управ&
ления, к которым относятся команды условных и безусловных переходов, вызова
процедур и возврата из процедур. Возникновение прерываний также приводит к моди&
фикации регистра EIP.

64*разрядные процессоры
Новое семейство процессоров Intel &&&& Itanium &&&& рассчитано на использование в вы&
сокопроизводительных серверах. Для того чтобы обеспечить резкое повышение произво&
дительности, необходимой в серверах, процессоры Itanium были построены по новому
архитектурному принципу, обозначенному как явно параллельные выполнения команд
(EPIC &&&& Explicitly Parallel Instruction Computing). Система команд дополнена новыми
командами для 64&разрядных процессоров, причем совместимость с системой команд
для 32&разрядных процессоров сохранена. Применение 32&разрядных команд также по&
зволяет использовать преимущества новой архитектуры процессора и получать более бы&
стродействующие программы.

Процессоры AMD
Фирма AMD выпускает несколько семейств процессоров, которые программно
совместимы с семейством х86 и имеют логотип, указывающий на совместимость с Windows.
Однако их необходимо устанавливать только в те системные платы, в описании которых
есть явное указание на их поддержку. В противном случае возможны сбои в работе.

32 Глава 2. Архитектура компьютера

Стр. 32
отсутствует. 33

Процессоры AMD отличаются наличием развитых средств системного управления и


управления энергопотреблением.
Процессоры AMD пятого поколения совместимы с процессорами Pentium, но по
сравнению с процессорами Intel этим процессорам присущи некоторые черты шестого
поколения: более сложный конвейер, предсказание ветвлений, изменение порядка вы&
полнения команд, переименование регистров и некоторые другие.
Процессоры поддерживают двухпроцессорную архитектуру с избыточным контролем
функциональности, однако интерфейс симметричных мультипроцессорных систем в них
отсутствует.
По производительности эти процессоры приближаются к производительности Pentium.
Вопрос применимости этого процессора в широко распространенных системных платах
упирается в основном в поддержку конкретной версии BIOS, замена которой при ис&
пользовании флэш&памяти не представляет большой технической проблемы. Процессор
имеет первичный кэш объемом 64 Кбайт (32 Кбайт для данных и столько же для команд).
Кэш данных &&&& двухпортовый, поддерживает обратную запись. Кэш команд имеет до&
полнительную область для предварительно декодированных команд. Предсказание ветв&
лений выполняется по двухступенчатой схеме, обеспечивая достоверность предсказаний
на уровне 95%. Не вдаваясь в подробности архитектурных решений, можно сказать, что
в этом процессоре отражены практически все достижения, имеющиеся в процессорах
Pentium, включая режимы управления энергопотреблением и синхронизацией. Однако
в отличие от процессоров Intel, процессоры AMD не имеют встроенных средств под&
держки многопроцессорных систем.
В процессорах AMD технология ММХ расширена набором команд 3DNow!, которые
позволяют значительно повысить производительность вычислений с плавающей точкой,
необходимых для трехмерной графики и обработки аудиосигналов.
Очередное поколение процессоров AMD вышло с вторичной кэш&памятью емкостью
128 Кбайт, работающей на частоте ядра (500 МГц). Расширение 3DNow! включает ко&
манды для цифровой записи звука.
Отличительной особенностью последних процессоров серии является трехуровневая
система кэширования памяти. Как и у предыдущих процессоров, здесь имеется первичный
кэш размером 64 Кбайт (по 32 Кбайт для данных и команд) &&&& это в два раза больше, чем
у Pentium. Однако теперь к нему добавлен вторичный кэш размером 256 Кбайт, располо&
женный на одном кристалле с процессором и работающий на полной частоте ядра. Процес&
сор устанавливается в плату, на которой может быть до 2 Мбайт кэш&памяти второго уров&
ня, но с учетом вторичной кэш&памяти процессора это будет уже кэш&память 3&го уровня.

Устройство компьютера
Для прикладного программиста необязательно глубоко знать архитектуру компьюте&
ра, но знание базовых принципов работы компьютера и взаимодействие его основных
частей необходимо. Без таких знаний невозможно написать даже простейшую програм&
му, для чего потребуется организовать ввод и вывод информации, т.е. необходимо обра&
щаться к периферийным устройствам, таким как клавиатура и монитор. А поскольку без
аппаратных устройств не обойтись, надо иметь представление о том, как они работают,
и что нужно для того, чтобы данные обрабатывались именно так, как они должны обра&
батываться. Устройство компьютера описано в следующих разделах.

Глава 2. Архитектура компьютера 33

Стр. 33
Внутренние компоненты
Системная (материнская) плата &&&& основной компонент любого компьютера. Имен&
но на этой плате располагаются процессор, системный набор микросхем для обслужива&
ния процессора, системная память, соединители ввода&вывода, разъемы для подключения
питания и разъемы расширения. Все компоненты соединяются друг с другом при помо&
щи системной шины, которые упрощенно можно представить как ряд параллельных
проводников, вытравленных в проводящем слое платы и соединяющих отдельные эле&
менты платы. Однако это очень упрощенное объяснение, так как современные шины
представляют собой помимо проводников еще и управляющие микросхемы. И если в пер&
вых компьютерах можно было рассматривать системную шину как ряд параллельных на&
прямую подключенных к процессору проводников, где всеми сигналами, проходящими
по шине, управляет процессор, то к современным системным платам это не имеет ника&
кого отношения. Современную системную шину необходимо рассматривать как отдель&
ное устройство, которое может работать независимо от процессора, хотя в любом случае
общее управление всей системой осуществляет процессор, и только процессор решает,
в каком режиме будет работать отдельное устройство.
Системные платы, независимо от компании&производителя, имеют стандартный набор
функций, отличаясь лишь производительностью и возможностями модификации. Совре&
менные платы обязательно имеют базовый набор соединителей, который включает в себя:
панельку для установки процессора (она имеет различные параметры в зависимо&
сти от типа процессора);
разъемы для установки системной памяти (модули памяти SDRAM, DDRAM или
DDR);
разъем для микросхемы BIOS (часто возникает необходимость в модернизации BIOS,
которая раньше производилась путем смены микросхемы ROM; в настоящее время
все микросхемы BIOS можно перепрограммировать электрически и обычно эти
микросхемы впаяны в плату);
соединители для подключения гибких и жестких дисков, а также CD&ROM приводов;
стандартные соединители для подключения клавиатуры, мышки, джойстика, ви&
деоустройств, принтера и устройств связи.

Шинная архитектура
Как уже отмечалось, обмен информацией между отдельными устройствами на сис&
темной плате происходит через шину. В современном компьютере шина представляет со&
бой отдельный распределительный узел, к которому подключаются все (как внутренние,
так и внешние) устройства. Во многом именно от грамотного построения шины зависит
производительность компьютера. Шинная архитектура реализуется с помощью специ&
ального набора микросхем, называемого системным набором.
В первых компьютерах IBM PC использовалась 8&разрядная шина для передачи дан&
ных между процессором и другими компонентами компьютера. Скорость передачи состав&
ляла около одного миллиона битов в секунду, что позволяло процессору 8088 работать
без задержек. Однако с увеличением производительности процессоров такой скорости
для работы шины оказалось недостаточно.
Шина PC AT, представленная IBM для работы с процессором Intel 80286, имела
16 разрядов для передачи данных и 24 разряда для передачи адреса. С увеличением такто&
вой частоты процессора увеличилась и частота работы шины до 8 МГц.

34 Глава 2. Архитектура компьютера

Стр. 34
отсутствует. 35

В настоящее время широко распространена шина PCI, которая была разработана в 1992 году
компанией Intel для совместной работы с процессорами Pentium, требующих высокой ско&
рости передачи данных. Эта шина до сих пор остается доминирующей во всех системах, ис&
пользующих процессоры Intel. Спецификация шины учитывает особенности как 32&раз&
рядных, так и на 64&разрядных системных плат. Диапазон тактовой частоты работы шины
может быть в пределах 25&&1000 МГц, обеспечивая скорость передачи до 500 Мбит в секун&
ду. Во всех системных платах используется мост для перехода от внутренней 64&разрядной
шины процессора к внешней 64&разрядной шине. На смену шине PCI приходит шина PCI
Express, характеристики которой значительно превышают возможности шины PCI и кото&
рая будет основной шиной компьютеров в течение нескольких ближайших лет.

Видеоадаптер и монитор
Видеоадаптер рассчитан на совместную работу с дисплеями по спецификации IBM
и состоит из двух основных частей: видеоконтроллера и видеопамяти. Видеоадаптер мо&
жет быть в виде отдельной платы, вставляемой в разъем расширения, или может быть
расположен непосредственно на системной плате. Здесь необходимо уточнить некоторые
терминологические тонкости. Видеоадаптером можно считать простую графическую
плату, которая рассчитана на воспроизведение как двухмерных, так и трехмерных изо&
бражений. Однако при создании трехмерного изображения графическая плата не может
обойтись без процессора, загружая его расчетами каркаса изображения и занимая значи&
тельную часть процессорного времени. Более совершенные графические платы называют
графическими ускорителями. Такие платы почти не обращаются к процессору при рас&
чете трехмерной сцены, где они учитывают все источники света, определенные в сцене,
и создают очень реалистичные световые и цветовые переходы.
Все символы и графические изображения перед отображением на дисплее должны
быть записаны в видеопамять, откуда они и посылаются на дисплей видеоконтроллером.
Видеоконтроллер, или видеопроцессор, &&&& это процессор, который берет на себя всю ра&
боту с видеоустройствами, освобождая от этих обязанностей центральный процессор.
Обычно видеоадаптер содержит не менее 4 Мбайт видеопамяти и оптимизирован для ра&
боты с двухмерными или трехмерными графическими изображениями. Современные ви&
деоадаптеры рассчитаны на работу не менее чем с 16 млн цветов и обеспечивают разре&
шение экрана не менее 1024×768. Эти характеристики постоянно улучшаются.
Что же касается мониторов, то для получения изображения в них используется техно&
логия, называемая растровым сканированием. Электронный луч создает на экране све&
тящиеся точки &&&& пиксели. Электронная пушка управляет лучом, постоянно передвигая
его слева направо и сверху вниз, проходя через все точки экрана. Достигнув конца самой
нижней строки, электронный луч перескакивает обратно в верхний левый угол, завершая
один цикл, называемый кадром. Для уменьшения мерцания экрана используют метод
чересстрочной развертки, когда луч пробегает за одну половину кадра только четные
строки, а за вторую &&&& нечетные, хотя в современных компьютерах этот метод почти не
используется и развертка всегда является прогрессивной, или последовательной.
Современные плоские мониторы используют другой принцип отображения инфор&
мации на экране. Мельчайшая сетка экрана состоит из жидких кристаллов, которые под
воздействием электрических сигналов могут пропускать или задерживать свет. Используя
это свойство жидких кристаллов, можно применить решетку из красных, синих и желтых
ячеек для создания цветного изображения.
Есть и другие принципы создания изображения на плоском экране, но в данном слу&
чае это не принципиально. Поговорим о характеристиках монитора.

Глава 2. Архитектура компьютера 35

Стр. 35
Качество монитора определяют несколько факторов. Это величина шага между распо&
ложенными рядом точками. В современных мониторах эта величина не превышает 0,26 мил&
лиметра и чем она меньше, тем лучше. Также важны скорость вертикальной и горизонтальной
разверток. Горизонтальная развертка определяется временем пробега луча по одной линии
(от левого края до правого), а скорость вертикальной развертки определяет полное время
пробега всех линий. Если скорость вертикальной развертки будет меньше, чем 70 кадров в се&
кунду, то изображение на экране будет мерцать при смене кадра, что вредно для глаз.
Скорость вертикальной развертки и разрешение экрана связаны обратно&пропорцио&
нальной зависимостью. Если увеличить разрешение, то может случиться так, что монитор
или видеоконтроллер не смогут поддерживать высокую скорость вертикальной развертки.
Разрешение экрана можно изменять программно, но оно ограничено возможностями
видеоконтроллера и объемом видеопамяти. Разрешение определяется количеством пик&
селей, размещаемых по горизонтали и вертикали. Стандартным значением для режима
VGA будет 640 пикселей по горизонтали и 480 по вертикали (640×480), для режима super
VGA &&&& 800×600, для extended VGA &&&& 1024×768, 1152×864, 1280×1024 и 1600×1400.
Количество одновременно отображаемых цветов может быть от 256 до 16 млн.

Память
В компьютерах используются три основных типа памяти: динамическая RAM, стати%
ческая RAM и видео RAM. Динамическая память должна обновляться не меньше, чем раз
в миллисекунду, иначе она потеряет свое содержимое, так как электроны постоянно
‘‘просачиваются’’ из созданного для них кармана. Статическая память не требует обнов&
ления. Видеопамять используется только для хранения данных, предназначенных для
отображения на дисплее. CMOS RAM предназначена для хранения информации о сис&
темных установках и для сохранения в ней информации при выключенном компьютере
используется специальная батарейка.
Основная используемая память является динамической памятью, так как она самая деше&
вая. Ее часто называют системной, или оперативной памятью. В некоторых системах исполь&
зуют память с проверкой ошибок. Такая память способна определять ошибки в нескольких
битах и корректировать ошибки в одном бите каждого байта, но это приводит к некоторому
удорожанию системы и в настоящее время в обычных компьютерах динамическая память
не используется. Это связано также и с тем, что сбои при передаче достаточно редки.
Обычно процессор работает значительно быстрее, чем оперативная память, поэтому
разработчикам приходится усложнять конструкцию для того, чтобы согласовать работу
процессора и памяти без потери производительности. Частично эту проблему решает вы&
сокоскоростная кэш&память первого уровня, включенная в состав процессоров Intel на&
чиная с модели 80386. Для более лучшего согласования используют кэш&память второго
уровня, которая подключается непосредственно к шине процессора. Ее объем обычно
составляет не менее 512 Кбайт. В этой памяти сохраняются недавно используемые ко&
манды и данные. Например, если встречаются циклические операции, то процессору со&
всем не придется обращаться к медленной основной оперативной памяти.

Устройства хранения данных


Даже самый простой компьютер обязательно включает несколько устройств хранения
данных:
один или несколько жестких дисков, имеющих объем хранения 100 Гбайт и выше;
привод CD или DVD объемом не менее 600 Мбайт;

36 Глава 2. Архитектура компьютера

Стр. 36
отсутствует. 37

съемные устройства емкостью от 100 Мбайт до 4 Гбайт;


ленточные накопители для хранения данных на магнитных лентах. Один картридж
с магнитной лентой может содержать более 40 Гбайт данных. Ленточные накопи&
тели не обеспечивают быстрого доступа, поэтому используются только для архи&
вирования данных.

Платы расширения
Платы расширения вставляются в разъемы расширения, размещенные на системной
или специальной разработанной плате, конструкция которой позволяет платам занимать
меньше места. На платах расширения обычно размещают:
графический ускоритель;
звуковую плату с портом для игровых устройств;
синтезатор звука;
контроллер SCSI;
контроллер ленточного накопителя;
устройство видеозахвата;
дополнительные параллельные и последовательные порты.
В полноразмерных конструкциях типа ‘‘башня’’ может быть от пяти до восьми разъе&
мов расширения и несколько отсеков для приводов, что позволяет подключать дополни&
тельные устройства. Массовые настольные компьютеры имеют меньше разъемов расши&
рения, так как часть функций реализована непосредственно на системной плате или
встроена в системный набор микросхем. Такое решение позволяет разрабатывать не&
большие и недорогие компьютеры.

Процессоры поддержки
Почти все системные платы имеют в своем составе системный набор микросхем,
включающий в себя две или три микросхемы с процессорами, которые выполняют от&
дельные функции (некоторые из них перечислены ниже).
Контроллер прямого доступа к памяти соединяет внешние устройства непосредст&
венно с памятью без использования центрального процессора.
Контроллер прерываний передает запросы на обслуживание от аппаратуры в цен&
тральный процессор.
Счетчик времени управляет системными часами, которые обновляются с частотой
18,2 раза в секунду.
Контроллер моста для перехода от локальной шины к шине PCI или PCIE.
Контроллер системной памяти и кэш&памяти.
Контроллер для работы с клавиатурой и мышкой.

BIOS
ROM BIOS &&&& это память только для чтения, которая содержит программы и данные,
не подлежащие стиранию или изменению. ROM BIOS можно рассматривать как часть
операционной системы. Когда компьютер включается, то процессор обращается непо&
средственно к BIOS, так как только в ней есть рабочие программы, все остальное еще

Глава 2. Архитектура компьютера 37

Стр. 37
предстоит загрузить в оперативную память с жесткого диска. Это программы контроля
аппаратной части и программы загрузки части операционной системы из загрузочного
сектора диска. Большинство систем копируют BIOS в более быстродействующую сис&
темную память на момент загрузки.
Наиболее важная особенность ROM BIOS &&&& возможность проверять наличие аппа&
ратных устройств и готовить операционную систему для работы с устройствами в соот&
ветствии с информацией, полученной от них. Никакой дополнительной настройки уст&
ройств при этом не требуется, поэтому такой режим назвали ‘‘подключи и работай’’.
Современные операционные системы не всегда доверяют созданным BIOS установкам
и сами производят конфигурацию отдельных или всех устройств.

Порты
Параллельные порты. Большинство принтеров подключаются к компьютеру через па&
раллельный порт. Слово ‘‘параллельный’’ означает, что 8 битов могут одновременно пе&
редаваться от компьютера к принтеру по восьми параллельным проводникам, но на не&
большие расстояния (обычно не более 10 метров). При этом данные могут передаваться
очень быстро. В компьютере имеется три параллельных порта: LPT1, LPT2 и LPT3, кото&
рые могут быть двунаправленными, позволяя компьютеру принимать информацию от
принтера о его состоянии.
Последовательные порты. Последовательные порты также называются интерфейсами
RS&232. Последовательные порты используются для подключения мышки, модема или
некоторых других устройств. Микросхема, которая управляет последовательными порта&
ми, обозначается как 16550 UART и может находиться на системной плате или плате
расширения.
В таких портах каждый бит посылается последовательно один за другим, что не спо&
собствует высокой скорости передачи, но зато становится возможной передача на большие
расстояния. Операционные системы поддерживают два последовательных порта COM1
и COM2, у которых разные типы соединителей: в одном 9 контактов, а в другом &&&& 25.
В связи с развитием технологической базы стало возможным обеспечивать большие
скорости передачи по последовательным портам. Поэтому в современных компьютерах
происходит замена параллельных портов на более удобные, позволяющие получить вы&
сокое быстродействие последовательные порты, такие как SATA или USB. Эти порты
можно использовать для подключения периферийных устройств любого типа.
Порты USB. Наиболее современные порты для ввода и вывода. Они обеспечивают
высокую скорость передачи данных и позволяют унифицировать ввод&вывод периферийных
устройств. В настоящее время наблюдается все более активный переход на порты USB,
которые в конечном итоге должны заменить все остальные порты. Порт USB помимо вы&
сокой скорости передачи предоставляет и ряд других преимуществ, среди которых удоб&
ство подключения и автоматическое распознавание вновь подключенного устройства.

Работа компьютера
Теперь, когда вы имеете общее представление о различных устройствах компьютера,
схематично рассмотрим совместную работу этих устройств. Более подробно работу от&
дельных устройств будем рассматривать в соответствующих разделах.
Итак, начнем с самого начала. В момент включения компьютера процессор обращается
к участку памяти, которую занимает BIOS, так для работы процессора нужна программа,
которую в начальный момент можно получить только из энергонезависимой памяти
BIOS. Оперативная память еще ничем не заполнена и в начальный момент неизвестно,

38 Глава 2. Архитектура компьютера

Стр. 38
отсутствует. 39

какая операционная система установлена на компьютере и как компьютер будет


управляться в дальнейшем. Поэтому происходит обращение к BIOS, в которой посто&
янно зашита программа инициализации компьютера. При инициализации компьютера
происходит контроль работоспособности системных устройств и проверка всех доступных
периферийных устройств. Если в момент инициализации какое&либо из жизненно важных
устройств не работает, то компьютер будет об этом сигнализировать короткими сигналами.
Подсчитав количество сигналов, можно определить неисправное устройство.
В случае если инициализация прошла успешно, то происходит считывание загрузочного
сектора основного жесткого диска, который создается операционной системой и в котором
определен порядок загрузки операционной системы в оперативную память. После того как
загрузочный сектор будет считан в оперативную память, управление передается программе,
которая находилась в загрузочном секторе и уже перенесена в оперативную память, т.е. к ней
может обращаться процессор. Считывая программу, процессор загружает операционную сис&
тему в оперативную память и только после этого компьютер становится пригодным для рабо&
ты. Операционная система принимает все управление на себя и ожидает ваших команд.
При работе с операционной системой DOS команды следует вводить с помощью кла&
виатуры. Посмотрим, как это происходит. При нажатии клавиши клавиатуры контроллер
клавиатуры посылает код нажатой клавиши в системную плату, к которой он электрически
подключен. Расположенный на системной плате контроллер принимает этот код и гене&
рирует аппаратное прерывание, которое заставляет процессор прервать свою работу и об&
ратить внимание на возникшее прерывание. После анализа прерывания операционная
система обрабатывает это прерывание. Обычно это выражается в отображении нажатого
символа на экране и запоминании его в буфере команд. Если была нажата клавиша но&
мер 13 (<Enter>), то операционная система анализирует все символы в буфере команд.
Если эти символы соответствуют имени одной из имеющихся в операционной системе
команд, то происходит выполнение этой команды. В противном случае на экране ото&
бражается надпись, говорящая о том, что команда не распознана.
А как же происходит обращение к устройствам, подключенным к системной шине? Ка&
ждое из устройств имеет порт, которому присвоен определенный номер. Получив из про&
граммы команду обращения к порту, процессор выставляет номер этого порта на шине ад&
реса, входящей в состав системной шины, а на специальной управляющей линии
выставляет единичное значение. При получении этого единичного значения все подключен&
ные к шине устройства считывают адрес с шины адреса. Если этот адрес не является их собст&
венным адресом, то они ничего не делают и оставляют свой порт закрытым. Открывается
только тот порт, адрес которого был выставлен на шине. Устройство с нужным номером порта
должно сообщить процессору, что оно готово к приему данных, и процессор начинает пере&
дачу, последовательно выставляя на шине данных передаваемые значения. Если же после
некоторого времени процессор не получит ответного сигнала готовности приема, то операци&
онная система должна будет принять решение о том, что делать дальше. Обычно это считается
аварийной ситуацией и операционная система обращается за помощью к пользователю.
Вот так, выполняя последовательность команд операционной системы, процессор
обрабатывает информацию, выводя данные на печать или отображая их на мониторе, со&
храняя данные на диске, обращаясь к другим компьютерам через сеть и т.д. Более под&
робно эти вопросы будут рассмотрены в соответствующих разделах.

Операционные системы и память


Операционная система &&&& это набор компьютерных программ, которые контролируют
работу прикладных программ и системных приложений и исполняют роль интерфейса
между приложениями и аппаратным обеспечением компьютера. Именно операционная

Глава 2. Архитектура компьютера 39

Стр. 39
система делает работу с компьютером такой простой и удобной, при этом она эффектив
но использует ресурсы компьютерной системы. Как и любая другая программа, операци
онная система состоит из команд, которые считывает процессор. Уникальностью этой
программы является ее назначение  операционная система позволяет процессору ис
пользовать все доступные системные ресурсы и распределять время при исполнении
других программ. Однако для того, чтобы компьютер мог выполнять полезную работу,
операционная система должна периодически переключать процессор на выполнение при
кладных программ. Таким образом, операционная система передает управление другой
программе, чтобы процессор смог выполнить заданную пользователем работу, а затем во
зобновляет контроль над системой для подготовки процессора к следующей работе.
На рис. 2.4 показаны основные ресурсы, которыми управляет операционная система.

Память
Контроллер Устройство Принтер
Операционная ввода вывода ввода вывода Клавиатура
система Модем
Монитор
Контроллер Устройство USB
ввода вывода ввода вывода ...

Внешняя
память
Программы
пользователей Операционная
и данные система
Программы

Контроллер Устройство
ввода вывода ввода вывода Данные

Процессор

Рис. 2.4. Ресурсы, управляемые операционной системой

Часть операционной системы обычно располагается в основной оперативной памяти.


В эту часть входит ядро, содержащее основную часть наиболее часто используемых
функций; там же находятся и некоторые другие компоненты операционной системы, ис
пользуемые в данный момент времени. Остальная используемая часть оперативной па
мяти содержит прикладные программы и данные пользователя. Размещение этих данных
в оперативной памяти управляется совместно операционной системой и аппаратной ча
стью процессора, предназначенной для управления памятью. Операционная система
принимает решение, когда исполняемая программа может использовать нужные ей уст
ройства вводавывода, и управляет доступом к файлам и их использованием. Операци
онная система решает, сколько времени процессор должен уделить исполнению той или
иной пользовательской программы. В многопроцессорной системе решение должно
приниматься по отношению ко всем процессорам.
Для конечного пользователя или программиста операционная система предоставляет
значительное число сервисных программ, часто называемых утилитами. К утилитам можно
обращаться из разрабатываемой программы или вызывать их непосредственно. Програм
мисты могут самостоятельно разрабатывать сервисные программы и дополнять ими опера
ционную систему. Программируя на языке ассемблера, можно не только дополнить систе
му утилитами, но также изменить поведение самой операционной системы. Например,

40 Глава 2. Архитектура компьютера

Стр. 40
отсутствует. 41

когда в Советский Союз начали поступать первые персональные компьютеры, они не мог&
ли отображать русские буквы, так как операционная система не имела соответствующих
шрифтов. Но очень скоро наши программисты ‘‘научили’’ компьютеры понимать русский
язык. Специальные программы&русификаторы встраивались в систему при инициализации
компьютера и позволяли отображать и вводить русские символы.
Здесь нет ничего удивительного, поскольку операционную систему можно изменять как
и любую другую программу. Но лучше этого не делать, так как, вероятнее всего, вы не смо&
жете улучшить то, что создавалось большими коллективами высококлассных программи&
стов. Тем более что исходные коды операционных систем закрыты и разработчики обыч&
но их никому не предоставляют. Исключение составляет операционная система Linux,
в поставку которой входят исходные коды.

Операционная система как диспетчер ресурсов


В любой компьютер входит минимальный набор средств, необходимых для выполне&
ния задач хранения, перемещения и обработки данных, а также средства контроля за ра&
ботой этих и многих других устройств. Управляет этими ресурсами операционная систе&
ма, хотя необходимо отметить, что операционная система часто передает управление
пользовательским программам и должна ожидать, когда ей отдадут управление вновь. Но
даже при передаче управления другим программам современная операционная система
оставляет за собой часть контролирующих функций и не позволит, например, пользова&
тельским программам использовать участки оперативной памяти за пределами выделен&
ной для них области памяти. Операционная система также контролирует время, зани&
маемое отдельной программой, и при необходимости может закрыть программу, которая
нарушает установленные правила.
Операционные системы постоянно развиваются. И происходит это потому, что мо&
дифицируются и возникают новые типы аппаратного обеспечения и возрастают требова&
ния к самой операционной системе. Например, ранние версии операционных систем
UNIX и OS/2 не использовали механизмы страничной организации памяти, потому что
они работали на машинах, не поддерживающих соответствующие аппаратные средства.
Более поздние версии операционных систем были доработаны таким образом, чтобы они
могли использовать новые аппаратные возможности. Точно так же на архитектуру опера&
ционных систем повлияло введение графических терминалов и терминалов, работающих
в страничном режиме, вместо алфавитно&цифровых терминалов с построчной разверт&
кой. Такой терминал предоставляет пользователю возможность работать одновременно
с несколькими приложениями в различных окнах экрана, что требует более широкой
поддержки со стороны операционной системы.
Со временем появляется необходимость в новых услугах со стороны операционной
системы. Например, для поддержки высокой производительности компьютера в операци&
онную систему следует добавлять инструменты для контроля и анализа производительности.
А введение режима с использованием нескольких окон на экране дисплея потребовало
существенного изменения всей архитектуры операционной системы. Необходимость
изменения операционной системы возникает и потому, что в каждой операционной сис&
теме обязательно есть ошибки и блоки, которые недостаточно защищены от несанкцио&
нированного проникновения в систему. Время от времени они обнаруживаются и ис&
правляются. Часто исправление приносит с собой новые ошибки.
Необходимость регулярных изменений операционных систем накладывает опреде&
ленные требования на их архитектуру. Системы должны иметь модульную структуру
с четко определенным взаимодействием модулей.

Глава 2. Архитектура компьютера 41

Стр. 41
Эволюция операционных систем
В первых компьютерах в конце 40&х и до середины 50&х годов прошлого века про&
граммы непосредственно взаимодействовали с аппаратным обеспечением машины &&&&
операционных систем тогда еще не было. Эти компьютеры управлялись с пульта управ&
ления, состоящего из сигнальных ламп, тумблеров, устройства для ввода данных и прин&
тера. Программы в машинных кодах и данные загружались через устройство ввода дан&
ных (например, устройство ввода с перфокарт). Если из&за ошибки выполнение
программы прекращалось, о возникновении сбойной ситуации сообщали аварийные
сигнальные лампы. Чтобы определить причину ошибки, программист вручную должен
был проверить состояние регистров процессора и основной памяти. Если программа ус&
пешно завершала свою работу, ее выходные данные распечатывались на принтере. Такой
режим работы можно назвать последовательной обработкой данных. Это название отра&
жает тот факт, что пользовательские программы исполнялись на компьютере последователь&
но. Все это сопровождалось массой неудобств, а выполнение пользовательской программы
занимало слишком много времени. Для устранения этих недостатков были разработаны
различные системные инструментальные программы. К ним относятся библиотеки функ&
ций, редакторы связей, загрузчики, отладчики и драйверы ввода&вывода, существующие
в виде программного обеспечения, общедоступного для всех пользователей.
В дальнейшем для повышения эффективности работы была предложена концепция
пакетной операционной системы. Первые пакетные операционные системы для машин
типа IBM 701 были разработаны в середине 50&х годов компанией General Motors. Впо&
следствии эта концепция была усовершенствована и внедрена на машинах серии
IBM 704. В начале 60&х годов некоторые поставщики разработали пакетные операцион&
ные системы для своих компьютеров. Одна из примечательных систем того времени &&&&
IBSYS фирмы IBM, разработанная для компьютеров 7090/7094 и оказавшая значитель&
ное влияние на другие системы.
Главная идея, лежащая в основе простых пакетных схем обработки, состоит в исполь&
зовании программы, известной под названием монитор. Используя операционную сис&
тему такого типа, пользователь не имеет непосредственного доступа к машине. Вместо
этого он передает свое задание на перфокартах или магнитной ленте оператору компью&
тера, который собирает разные задания в пакеты и помещает их в устройство ввода дан&
ных. Так информация передается монитору. Каждая программа составлена таким образом,
что по завершении ее работы управление переходит к монитору, который автоматически
загружает следующую программу. Задания в пакетах выстраиваются в очередь и выпол&
няются без простоев максимально быстро. Кроме того, монитор помогает в подготовке
программы к исполнению. В каждое задание включаются простые команды языка управ&
ления заданиями. Это специальный тип языка программирования, используемый для
того, чтобы отдавать команды монитору. При этом монитор обеспечивал защиту памяти
и во время работы программа пользователя не могла внести изменения в область памяти,
в которой находился сам монитор. Монитор использовал таймер для снятия программ,
превысивших выделенное для них время работы; только он мог выполнять некоторые
привилегированные команды машинного уровня. Например, команды ввода&вывода
контролировались монитором, и если программе пользователя нужно было произвести
ввод&вывод, ей приходилось запрашивать для выполнения этих операций монитор. Но
в первых операционных системах отсутствовали прерывания, которые придают системе
большую гибкость при управлении ресурсами процессора.
Занимая часть ресурсов компьютера, монитор существенно повышал эффективность
его использования, что с учетом дорогостоящих компьютеров и дорогого процессорного

42 Глава 2. Архитектура компьютера

Стр. 42
отсутствует. 43

времени было оценено по достоинству. Но и под управлением простой пакетной опера&


ционной системы при автоматическом выполнении заданий процессору часто приходи&
лось простаивать. Поэтому разработчикам пришлось устранять и этот недостаток. Аппа&
ратная часть совершенствовалась и появлялись новые возможности для повышения
производительности процессора. Значительно возросшие объемы оперативной памяти
позволяли размещать в ней несколько пользовательских программ &&&& так появилась воз&
можность исполнять их параллельно, переключаясь с одной программы на другую. Те&
перь, когда одно из заданий ждет завершения операций ввода&вывода, процессор может
переключиться на другое задание, для которого в данный момент ввод&вывод, скорее
всего, не требуется. Поэтому изменилась и операционная система &&&& появился многоза&
дачный режим. Такой режим является основным в современных операционных системах.
Работа многозадачной пакетной системы, как и работа простой пакетной системы,
базируется на некоторых аппаратных особенностях компьютера. Именно для решения
проблем многозадачности потребовалось разработать аппаратное обеспечение, поддержи&
вающее прерывания ввода&вывода, и прямой доступ к памяти. Используя эти возможности,
процессор генерирует команду ввода&вывода для одного задания и переходит к другому
на то время, пока контроллер устройства выполняет ввод&вывод. После завершения опе&
рации ввода&вывода процессор получает прерывание, и управление передается програм&
ме обработки прерываний из состава операционной системы. Затем операционная сис&
тема передает управление другому заданию.
Многозадачные операционные системы сложнее систем последовательной обработки
заданий. Для того чтобы можно было обрабатывать несколько заданий одновременно,
они должны находиться в основной памяти, при этом не обойтись без системы управле&
ния памятью. Кроме того, если к работе готовы несколько заданий, процессор должен
решить, какое из них следует запустить, для чего необходимо было разработать алгоритм
планирования заданий.
Использование многозадачности в пакетной обработке приводило к существенному по&
вышению эффективности использования компьютера. Однако пользователи, почувство&
вавшие вкус к работе с компьютером, стали настаивать на таком режиме, в котором они
могли бы непосредственно взаимодействовать с компьютером. Это стало возможным при
возросшем уровне аппаратного обеспечения, когда многозадачность позволяет процессору
не только одновременно обрабатывать несколько заданий в пакетном режиме, но и приме&
няется для обработки нескольких интерактивных заданий. Такой режим компьютера назы&
вают работой с разделением времени, потому что процессорное время распределяется между
отдельными пользователями. В системе разделения времени несколько пользователей одно&
временно получают доступ к системе с помощью терминалов, а операционная система череду&
ет исполнение программ каждого пользователя через малые промежутки времени. Принимая
во внимание относительно медленную реакцию человека, время отклика компьютера с хо&
рошо настроенной операционной системой будет почти незаметно для пользователя.
С появлением разделения времени и многозадачности перед создателями операцион&
ных систем появилось много проблем. Если в памяти находится несколько заданий, их
нужно защищать друг от друга, иначе одно задание может легко изменить данные другого
задания. Если в интерактивном режиме работают несколько пользователей, то файловая
система должна быть защищена, чтобы к каждому конкретному файлу доступ был только
у определенных пользователей. Нужно разрешать конфликтные ситуации, возникающие
при работе с различными ресурсами, например, при обращении к принтеру или устрой&
ству хранения данных нескольких пользователей одновременно, и еще много разных
проблем. Все это потребовало разработки новых операционных систем.

Глава 2. Архитектура компьютера 43

Стр. 43
Современные операционные системы
Операционные системы относят к числу самых сложных программных комплексов.
Это связано со стремлением разработчиков сделать системы максимально удовлетво&
ряющие требованиям удобства работы и эффективности, но при этом ОС не должны ут&
ратить способности к дальнейшему совершенствованию. В процессе развития операци&
онных систем были проведены исследования в нескольких основных направлениях.
Одной из главных концепций, лежащих в основе операционных систем, является
концепция процессов. Этот термин впервые был применен в 60&х годах и с тех пор широ&
ко используется. В разных операционных системах под процессом понимают несколько
отличающиеся действия операционных систем. Фактически процесс можно разделить на
три компонента:
выполняемая программа;
данные, нужные для ее работы (переменные, рабочее пространство, буферы и т.д.);
состояние программы.
Очень важен компонент состояния программы, который включает в себя всю инфор&
мацию, нужную операционной системе для управления процессом, и процессору &&&& для
его выполнения. Данные, характеризующие это состояние, включают в себя содержимое
различных регистров процессора, таких как программный счетчик и регистры данных.
Сюда же входит информация, используемая операционной системой (приоритет процес&
са и сведения о том, находится ли данный процесс в состоянии ожидания какого&то со&
бытия, связанного с вводом&выводом).
Таким образом, процесс реализуется в виде структуры данных. Он может выполнять&
ся или находиться в состоянии ожидания. Состояние процесса в каждый момент времени
заносится в специально отведенную область данных. Использование структуры позволя&
ет развивать мощные методы координации и взаимодействия процессов.
Четкое обоснование понятия процесса позволило решить проблемы, возникающие на
этапе развития операционных систем и связанных с распределением времени и синхро&
низацией.
Были разработаны системы групповой обработки нескольких программ, при этом
процессы могли запускаться в режиме разделения времени и транзакций одновременно.
В ответ на сигналы, сообщающие о завершении транзакций ввода&вывода, процессор пе&
реключается с одной программы на другую.
Следующим направлением развития являются системы разделения времени. Основ&
ная цель их разработки &&&& удовлетворение потребностей каждого пользователя при усло&
вии их одновременной работы на компьютере. В этих системах учитывается тот факт, что
пользователь реагирует на события намного медленнее, чем компьютер.
Еще одним важным направлением развития являются системы обработки транзакций
в реальном времени. При этом необходимо учитывать то, что большое число пользовате&
лей могут одновременно отправлять запросы в базу данных или вносить в нее изменения,
и при этом не должно быть большой задержки с ответом.
Прерывание стало важным инструментом, которое могли использовать системные
программисты уже на ранних стадиях развития многозадачных и многопользовательских
интерактивных систем. Выполнение любого задания может быть прервано при наступле&
нии определенного события, например завершения ввода&вывода. При этом процессор
сохраняет информацию о текущем состоянии программы и переключается на выполне&
ние программы обработки прерываний, которая выясняет причину прерывания, обраба&
тывает его, а затем возобновляет исполнение прерванной программы.

44 Глава 2. Архитектура компьютера

Стр. 44
отсутствует. 45

Устройство системного программного обеспечения, координирующего подобные


процессы, оказалось очень сложным. При одновременной обработке многих заданий,
каждое из которых включает в себя трудно предсказуемую последовательность действий,
не представлялось возможным проанализировать все комбинации. При малейших наруше&
ниях в системе синхронизации система выходила из строя. Возникали трудности и с со&
вместно используемым ресурсом, когда к нему одновременно пытались обратиться не&
сколько пользователей или несколько программ. Для корректной работы потребовалось
разработать механизм взаимного исключения, позволяющий в каждый момент времени
выполнять транзакцию только одной программе. Однако реализация такого взаимного
исключения при всех возможных последовательностях событий вызывала сомнения.
Также необходимо было учитывать непредсказуемое поведение программы. В условиях
совместного использования памяти и процессора программы могут влиять на работу друг
друга, переписывая общие области памяти непредсказуемым образом. При этом результат
работы программ может зависеть от порядка, в котором они выполняются, или от ситуа&
ции, когда несколько программ ‘‘зависают’’, ожидая действий друг друга. Например, двум
программам может понадобиться, чтобы устройства ввода&вывода выполнили копирование
с диска на магнитную ленту. Одна из этих программ управляет одним из устройств, а дру&
гая &&&& другим. Каждая из них ждет, пока другая программа освободит нужный ресурс.

Управление памятью
Лучше всего потребности пользователя удовлетворяются вычислительной средой, под&
держивающей модульное программирование и гибкое использование данных. Эффек&
тивный контроль над размещением данных в оперативной памяти со стороны операци&
онной системы позволяет избавиться от многих проблем. При этом операционная
система должна следить за тем, чтобы ни один из независимых процессов не смог изме&
нить содержимое памяти, отведенное другому процессу. Программы должны динамически
размещаться в памяти в соответствии с определенными требованиями. Распределение па&
мяти должно происходить автоматически, независимо от программиста, хотя программист
и может управлять распределением. Таким образом, программист будет избавлен от необ&
ходимости следить за ограничениями памяти, а операционная система повышает эффек&
тивность работы вычислительной системы, выделяя заданиям только тот объем памяти,
который им необходим. Однако программист должен иметь возможность определять мо&
дули программы, а также динамически их создавать, уничтожать и изменять их размер.
При совместном использовании памяти на каждом ее иерархическом уровне одна
программа может обратиться к пространству памяти другой программы. Хотя такая воз&
можность необходима, она представляет угрозу целостности программ и самой операци&
онной системы. Операционная система должна обязательно следить за тем, каким обра&
зом отдельные пользователи осуществляют доступ к различным областям памяти.
Обычно операционные системы выполняют эти требования с помощью средств вир&
туальной памяти и файловой системы. Файловая система обеспечивает долгосрочное
хранение информации, помещаемой в именованные объекты, которые называются фай&
лами. Файл — это удобная для широкого использования структура данных, доступ к ко&
торой и ее защита осуществляются операционной системой.
Виртуальная память — это абстрактное понятие, позволяющее программистам рассмат&
ривать память с логической точки зрения, не заботясь о наличии физической памяти доста&
точного объема. Принципы работы с виртуальной памятью позволяли добиться того, чтобы
задания нескольких пользователей выполнялись параллельно и могли одновременно присут&
ствовать в основной памяти. При такой организации процессов задержка между их выполне&
нием отсутствует: как только один из процессов заносится на вспомогательное запоминающее

Глава 2. Архитектура компьютера 45

Стр. 45
устройство, считывается следующий процесс. Из&за различий в количестве памяти, которое
требуется для разных процессов, при переключении процессора с одного процесса на дру&
гой возникают трудности с компактным их размещением в основной памяти. Поэтому бы&
ли разработаны системы со страничной организацией памяти, при которой процесс разби&
вается на блоки фиксированного размера, или страницы. Обращение программы к слову
памяти происходит по виртуальному адресу, который состоит из номера страницы и смеще&
ния относительно ее начала. Страницы одного и того же процесса могут быть разбросаны по
всей основной памяти. Система разбивки на страницы обеспечивает динамическое соответст&
вие между виртуальным адресом, который использует программа, и реальным адресом основ&
ной памяти. При этом было исключено требование, чтобы все страницы процесса одновре&
менно находились в основной памяти; достаточно, чтобы все они хранились на диске. Во
время выполнения процесса только некоторые его страницы находятся в основной памяти.
Если программа обращается к странице, которая там отсутствует, то система управления
памятью обнаружит это и организует загрузку недостающих страниц.

Характеристики современных операционных систем


Год за годом совершенствуются структура и возможности операционных систем. В со&
став новых операционных систем и новых версий уже существующих ОС вошли структур&
ные элементы, которые внесли большие изменения в природу этих систем. Современные
операционные системы отвечают требованиям постоянно развивающегося аппаратного
и программного обеспечения. Они способны управлять работой многопроцессорных систем,
высокоскоростных сетевых устройств и новейших запоминающих устройств, разнообразие
типов которых постоянно увеличивается. Из приложений, оказавших значительное влия&
ние на архитектуру операционных систем, следует выделить мультимедийные приложения,
средства доступа к Интернету, а также модель распределенных вычислений клиент/сервер.
Неуклонный рост требований к операционным системам приводит не только к улуч&
шению их архитектуры, но и к возникновению новых способов их организации. В экспе&
риментальных и коммерческих операционных системах были опробованы самые разнооб&
разные подходы и структурные компоненты, большинство из которых можно объединить
в следующие категории:
архитектура микроядра;
многопоточность;
симметричная многопроцессорность;
распределенные операционные системы;
объектно&ориентированное построение.
Отличительной особенностью большинства операционных систем на сегодняш&
ний день является большое монолитное ядро. Ядро операционной системы обеспечи&
вает большинство ее возможностей, включая планирование заданий, работу с файло&
вой системой, сетевые функции, работу драйверов различных устройств, управление
памятью и т.д. Обычно монолитное ядро реализуется как единый процесс, все элементы
которого используют одно и то же адресное пространство. В архитектуре микроядра ядру
отводится лишь несколько самых важных функций, в число которых входят работа с ад&
ресными пространствами, обеспечение взаимодействия между процессами и основное
планирование. Работу других сервисов операционной системы обеспечивают процессы,
которые иногда называют серверами. Эти процессы запускаются в пользовательском ре&
жиме и микроядро работает с ними так же, как и с другими приложениями. Такой подход
позволяет разделить задачу разработки операционной системы на разработку ядра и раз&
работку серверов. Серверы можно настраивать для требований конкретных приложений

46 Глава 2. Архитектура компьютера

Стр. 46
отсутствует. 47

или среды. Выделение в структуре системы микроядра упрощает реализацию системы,


обеспечивает ее гибкость, а также хорошо вписывается в распределенную среду. Факти&
чески микроядро взаимодействует с локальным и удаленным сервером по одной и той же
схеме, что упрощает построение распределенных систем.
Многопоточность — это технология, при которой процесс, выполняющий приложение,
разделяется на несколько одновременно выполняемых потоков. Разбивая приложение на
несколько потоков, программист получает все преимущества модульности приложения
и возможность управления связанными с приложением временными событиями. Много&
поточность оказывается весьма полезной для приложений, выполняющих несколько не&
зависимых заданий, которые не требуют последовательного исполнения. В качестве
примера такого приложения можно привести сервер базы данных, который одновремен&
но принимает и обрабатывает несколько запросов клиентов. Если в пределах одного и того
же процесса обрабатываются несколько потоков, то при переключении между различ&
ными потоками непроизводительный расход ресурсов процессора меньше, чем при пере&
ключении между разными процессами.
До недавнего времени все персональные компьютеры, рассчитанные на одного поль&
зователя, и рабочие станции содержали один процессор общего назначения. В результате
постоянного повышения требований к производительности и снижения стоимости мик&
ропроцессоров производители перешли к выпуску компьютеров с несколькими процес&
сорами. Для повышения эффективности работы таких систем используется технология
симметричной многопроцессорной обработки данных. При этом процессоры, соединенные
между собой коммуникационной шиной или какой&нибудь другой схемой, совместно
используют одну и ту же основную память и одни и те же устройства ввода&вывода.
Операционная система должна поддерживать симметричную многопроцессорную об&
работку данных, распределяя процессы или потоки между всеми процессорами. При этом
повышается надежность работы, так как отказ одного из процессоров не приведет к ос&
тановке компьютера, потому что все процессоры могут выполнять одни и те же функции.
После такого отказа система продолжит свою работу, хотя производительность ее не&
сколько снизится. Производительность системы также легко повысить путем добавления
дополнительных процессоров.
Для того чтобы надлежащим образом реализовать потенциал, заключенный в много&
процессорных вычислительных системах, операционная система должна предоставлять
адекватный набор инструментов и возможностей.
Одним из последних новшеств в устройстве операционных систем стало использова&
ние объектно&ориентированных технологий. Объектно&ориентированная структура по&
могает навести порядок в процессе добавления к основному небольшому ядру дополни&
тельных модулей. На уровне операционной системы объектно&ориентированная
структура позволяет программистам настраивать операционную систему, не нарушая ее
целостности. Кроме того, этот подход облегчает разработку распределенных инструмен&
тов и полноценных распределенных операционных систем.

Архитектура памяти
Физическая память, к которой микропроцессор имеет доступ по шине адреса (см. рис. 2.1),
называется оперативной памятью. Конструктивно память компьютера можно рассмат&
ривать как массив битов. Один бит может хранить значение 0 или 1 и для работы с ними
идеально подходят логические схемы. Однако работать с памятью на уровне битов край&
не неудобно, поэтому реально ОЗУ организовано как последовательность из восьми ячеек,
которую называют байтом. Один байт состоит из 8 бит. Каждому байту соответствует
свой уникальный адрес, называемый физическим адресом. Диапазон значений физиче&
ских адресов зависит от разрядности шины адреса микропроцессора. Для Intel486 и Pentium

Глава 2. Архитектура компьютера 47

Стр. 47
32
он находится в пределах от 0 до 2 &&1 (4 Гбайт). Для микропроцессоров семейства Р6 этот
36
диапазон шире &&&& от 0 до 2 &&1 (64 Гбайт).
Механизм управления памятью полностью аппаратный. Это означает, что в програм&
ме нельзя сформировать физический адрес памяти на адресной шине, необходимо учи&
тывать конструктивные особенности процессора. Такой механизм позволяет обеспечить:
компактность хранения адреса в машинной команде;
гибкость механизма адресации;
защиту адресных пространств задач в многозадачной системе;
поддержку виртуальной памяти.
Микропроцессор аппаратно поддерживает две модели использования оперативной
памяти.
Сегментированную модель. В этой модели программе выделяются непрерывные
области памяти (сегменты), а сама программа может обращаться только к данным,
которые находятся в этих сегментах.
Страничную модель. Ее можно рассматривать как надстройку над сегментирован&
ной моделью. В случае использования этой модели оперативная память рассмат&
ривается как совокупность блоков фиксированного размера (4 Кбайт). Основное
применение этой модели связано с организацией виртуальной памяти, что позво&
ляет операционной системе использовать для работы программ пространство па&
мяти большее, чем объем физической памяти. Для микропроцессоров Intel486
и Pentium размер возможной виртуальной памяти может достигать 4 Тбайт.
Особенности использования и реализации этих моделей зависят от режима работы
микропроцессора. В реальном режиме работал процессор Intel 8086, защищенный режим
позволяет максимально реализовать все архитектурные идеи, заложенные в модели мик&
ропроцессоров Intel, начиная с i80286. Программы, разработанные для Intel 8086, не мо&
гут функционировать в защищенном режиме. Одна из причин этого связана именно
с особенностями формирования физического адреса в защищенном режиме. Для того
чтобы использовать такие программы, предусмотрен режим виртуального 8086. Переход
в этот режим возможен, если микропроцессор уже находится в защищенном режиме. От&
личительной особенностью этого режима является возможность одновременной работы
нескольких программ, разработанных для Intel 8086. Несмотря на то что микропроцессор
находился в защищенном режиме, в режиме виртуального i8086 возможна работа про&
грамм реального режима. Дело в том, что процесс формирования физического адреса для
этих программ производится по правилам реального режима.
И последний новый режим работы &&&& режим системного управления. Этот режим впер&
вые появился в микропроцессоре Pentium. Он предоставляет операционной системе ме&
ханизм для выполнения машинно&зависимых функций, таких как перевод компьютера
в режим пониженного энергопотребления или выполнения действий по защите системы.
Для перехода в этот режим микропроцессор должен получить специальный сигнал от
усовершенствованного программируемого контроллера прерываний (APIC), при этом
сохраняется состояние вычислительной среды микропроцессора. Функционирование
микропроцессора в этом режиме подобно его работе в режиме реальных адресов.

Сегментированная модель памяти


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

48 Глава 2. Архитектура компьютера

Стр. 48
отсутствует. 49

сегмента, который представляет собой независимый, поддерживаемый на аппаратном


уровне блок памяти.
Каждая программа в общем случае может состоять из любого количества сегментов
(но непосредственный доступ она имеет только к трем основным сегментам: кода, данных
и стека) и включать от одного до трех дополнительных сегментов данных. Программа не
в состоянии определить, по каким физическим адресам будут размещены ее сегменты !!!!
этим занимается операционная система. Система размещает сегменты программы в опера!
тивной памяти по определенным физическим адресам, после чего помещает значения этих
адресов в определенные места (куда именно !!!! зависит от режима работы микропроцессо!
ра). Так, в реальном режиме эти адреса помещаются непосредственно в соответствующие
сегментные регистры, а в защищенном режиме они размещаются в элементах специальной
системной таблицы дескрипторов. Внутри сегмента программа обращается к адресам отно!
сительно начала сегмента линейно, т.е. начиная с 0 и заканчивая адресом, равным размеру
сегмента. Этот относительный адрес, или смещение, который микропроцессор использует
для доступа к данным внутри сегмента, называется эффективным адресом.
Отличия моделей сегментированной организации памяти в различных режимах пока!
заны на рис. 2.5. Здесь представлены три основные модели сегментированной организа!

Эффективный адрес Физический адрес

Смещение
Сегментированная
+ Сегмент модель
памяти
Адрес реального
(Сегментный *4 режима
регистр)
Сегмент

Эффективный адрес Физический адрес

Смещение
Сегментированная
+ Сегмент модель
памяти
Селектор защищенного
(Сегментный режима
регистр)
Таблица
дескрипторов Сегмент

Эффективный адрес Физический адрес

Смещение
Плоская
+ модель
памяти
Селектор защищенного
(Сегментный режима
регистр)
Таблица
дескрипторов
Селектор
(Сегментный
регистр)

Рис. 2.5. Организация памяти процессоров Intel

Глава 2. Архитектура компьютера 49

Стр. 49
ции памяти: сегментированная модель памяти реального режима, сегментированная мо&
дель памяти защищенного режима и сплошная модель памяти защищенного режима.
Для примера рассмотрим порядок формирования физического адреса в реальном ре&
жиме. Под физическим адресом понимается адрес памяти, выдаваемый на шину адреса
микропроцессора.
Формирование физического адреса в реальном режиме
В реальном режиме механизм адресации физической памяти имеет следующие харак&
теристики:
диапазон изменения физического адреса от 0 до 1 Мбайт. Эта величина определя&
ется тем, что шина адреса Intel 8086 имела 20 линий;
максимальный размер сегмента 64 Кбайт. Это объясняется 16&разрядной архитектурой
Intel 8086. Поскольку максимальное значение, которое могут содержать 16&разрядные
16
регистры, составляет 2 &&1, то это значение и определяет величину 64 Кбайт.
Для обращения к конкретному физическому адресу оперативной памяти необходимо
определить адрес начала сегмента и смещение внутри сегмента. Однако поскольку адрес
начала сегмента представляет собой всего лишь 16&битовое значение, помещенное в один
из сегментных регистров, то максимальное значение, которое при этом получается, соот&
16
ветствует 2 &&1. Если исходить из этого, то получается, что адрес начала сегмента может
быть только в диапазоне 0&&64 Кбайт от начала оперативной памяти. Но тогда как адресо&
вать остальную часть оперативной памяти вплоть до 1 Мбайт с учетом того, что размер
самого сегмента не превышает 64 Кбайт? Дело в том, что в сегментном регистре содержатся
только старшие 16 бит физического адреса начала сегмента, которые сдвигаются на 4 разря&
да влево для получения 20&битового адреса. Эта операция сдвига выполняется аппаратно
и для программного обеспечения абсолютно прозрачна. Получившееся 20&битовое значе&
ние и является настоящим физическим адресом, соответствующим началу сегмента. Что
касается второго компонента, участвующего в образовании физического адреса некоторого
объекта в памяти &&&& смещения, то оно представляет собой 16&битовое значение. Это значе&
ние может содержаться явно в команде либо косвенно в одном из регистров общего назна&
чения. В микропроцессоре эти две составляющие складываются на аппаратном уровне,
в результате чего получается физический адрес памяти размером 20 бит. Данный механизм
образования физического адреса позволяет сделать программное обеспечение перемещае&
мым, т.е. не зависящим от конкретных адресов загрузки его в оперативной памяти.
Разумеется, в такой организации памяти можно найти много недостатков, но на ран&
них этапах разработки процессоров подобная архитектура имела определенные преиму&
щества. Никто не предполагал, что для обычного компьютера может понадобиться опе&
ративная память в несколько гигабайт. Билл Гейтс при разработке операционной
системы DOS выделил для системы 640 Кбайт и при этом заявил, что он не представляет,
для чего может понадобиться такое огромное количество оперативной памяти. Появле&
ние защищенного режима вызвано желанием ввести в архитектуру процессора средства,
позволяющие избавиться от недостатков реального режима.

Ввод*вывод
Без получения и отображения информации в удобном виде вся работа компьютера не име&
ет смысла. Поэтому система ввода&вывода (BIOS) является одной из важнейших систем,
которая реализована как на самом низшем (аппаратном) уровне, так и на более высоких
уровнях. Например, вы можете написать команду для отображения данных на языке высокого

50 Глава 2. Архитектура компьютера

Стр. 50
отсутствует. 51

уровня, таком как С или Java. При выполнении такой программы произойдет обращение
к операционной системе в тот момент, когда возникнет необходимость отображения дан&
ных. Операционная система в свою очередь обратится к соответствующей функции базовой
системы ввода&вывода для вывода необходимых данных, так как только BIOS имеет воз&
можность произвести запись данных непосредственно в память графического адаптера. Та&
ким образом, взаимодействие с аппаратной частью производится только через BIOS.
Такой подход позволяет создавать переносимые программы, т.е. такие программы,
которые могут выполняться на различных типах компьютеров без переделки самих про&
грамм, поскольку взаимодействие с различным оборудованием производится через BIOS,
который настраивается именно на то оборудование, для которого он предназначен.
Необходимо добавить, что стандартный BIOS, который входит в состав любого ком&
пьютера, обычно ‘‘умеет’’ обращаться только к тем устройствам, которые входят в базо&
вый состав компьютера, и ничего ‘‘не знает’’ о новых устройствах, которые могут быть
дополнительно установлены в компьютер. Для работы с такими устройствами необходи&
мо расширить BIOS, т.е. дополнить его такими программами (драйверами), которые
смогут работать с новым оборудованием. Установка дополнительных драйверов произво&
дится в момент загрузки компьютера и современные операционные системы могут делать
это автоматически. Однако, к примеру, в DOS необходимо записать специальную коман&
ду в файл инициализации, такую как
DEVICE = CDROM.SYS
где файл CDROM.SYS является драйвером устройства считывания компакт&дисков.
В отличии от программ, написанных на языке высокого уровня, программы, напи&
санные на ассемблере, могут быть непереносимыми, &&&& возможно, они будут обращаться
непосредственно к аппаратной части (рис. 2.7).
Преимущество таких программ &&&& высокая скорость работы, так как программист
может не использовать стандартные подходы, которые из&за своей универсальности яв&
ляются довольно медленными, а реализовать уникальный и быстрый метод доступа.

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

Контрольные вопросы
1. Какова роль процессора в компьютерной системе?
2. Назовите основные компоненты компьютера, без которых невозможна работа
компьютера.
3. Как взаимосвязаны операционная система и аппаратная часть компьютера?
4. Как вы считаете, операционная система разрабатывается под определенную аппа&
ратную платформу или аппаратная часть разрабатывается для определенной опе&
рационной системы?
5. Почему операционные системы постоянно модифицируются?

Глава 2. Архитектура компьютера 51

Стр. 51
6. Зачем нужна оперативная память?
7. Что такое кэш&память и почему она необходима в компьютере?
8. Зачем нужны регистры в процессоре?
9. Назовите четыре регистра общего назначения.
10. Как обозначается старшая половина регистра CX? Сколько битов она может хранить?
11. Какое приоритетное назначение регистра ECX?
12. Почему доступ к оперативной памяти происходит значительно дольше по време&
ни, чем к регистрам?
13. В чем преимущество защищенного режима в процессорах? Для каких прикладных
программ необходим такой режим?
14. Можно ли в операционной системе DOS использовать защищенный режим?
15. Почему с изменением регистра AH одновременно изменяется регистр EAX?
16. В каком 16&разрядном регистре будет находиться верхняя часть результата пере&
множения чисел?
17. Какой регистр является указателем базы для всех выполняемых команд?
18. Какой регистр является указателем базы для стека?
19. Помимо указателя стека, с помощью каких других регистров можно вычислить пе&
ременную в стеке?
20. Назовите два регистра индексов.
21. Какие три флага называются флагами контроля?
22. Флаги состояния реагируют на дополнительный перенос, четность, перенос, пере&
полнение. Какие еще есть флаги состояния?
23. Какой флаг устанавливается, когда результат арифметической операции с числами
без знака становится слишком большим для размещения результата в регистре?
24. Какой флаг устанавливается, когда в результате арифметической или логической
операции возникает отрицательный результат?
25. Какой флаг отражает состояние одного бита результата операции?

52 Глава 2. Архитектура компьютера

Стр. 52
Глава 3

Введение
в язык ассемблера
В этой главе...

Представление данных
Основы языка ассемблера
Разработка программы на языке ассемблера
Работа в DOS под Windows NT
Инструментальные средства
Пример простой программы
Ассемблер Microsoft
Отладчик
Резюме
Контрольные вопросы

Язык ассемблера помогает раскрыть все секреты аппаратного и программного обес&


печения. С его помощью можно получить представление о том, как аппаратная часть
взаимодействует с операционной системой и как прикладные программы обращаются
к операционной системе. Большинство программистов работают с языками высокого
уровня, где отдельное утверждение преобразовывается во множество процессорных ко&
манд. Ассемблер &&&& язык машинного уровня; каждая команда непосредственно интер&
претируется в машинный код, что дает основание считать его языком низкого уровня.
Наиболее часто язык ассемблера используется для написания дополнений к операци&
онной системе или для написания программ прямого доступа к аппаратуре. Он необхо&
дим также при оптимизации критических блоков в прикладных программах с целью по&
вышения их быстродействия.

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

Стр. 53
Двоичные числа
Компьютер сохраняет команды и данные в оперативной памяти как последователь
ность заряженных или разряженных ячеек. Образно можно представить состояние каж
дой ячейки как переключатель с двумя состояниями: ‘‘включено и выключено’’ или ‘‘ис
тина и ложь’’. Такие ячейки идеально подходят для хранения двоичных чисел, которые
используют базовое число 2, так как отдельные биты могут принимать только два состоя
ния  0 или 1. Ячейки памяти, соответствующие единице, имеют повышенный заряд,
а соответствующие нулю  почти разряжены. На рис. 3.1 условно показано соответствие
переключателей и двоичных чисел.
Включено Выключено Включено Включено Выключено Выключено Включено Выключено
1 0 1 1 0 0 1 0

Рис. 3.1. Соответствие переключателей и двоичных чисел

Первые компьютеры имели наборы механических переключателей, управляемых


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

Биты, байты, слова, двойные и учетверенные слова


Каждый разряд в двоичном числе называется битом. Восемь битов составляют байт 
отдельно адресуемый элемент памяти в большинстве компьютеров. Байт может содер
жать простую машинную команду, символ или число. Следующим по размеру элемен
тарным понятием является слово. В процессорах Intel слово составляет 16 бит (2 байта).
Размер слова не является жестко определенным. Компьютеры, в которых применя
ются процессоры Intel, используют 16, 32 или 64разрядные операнды, поэтому длина
слова определяется в 16, 32 или 64 бит, т.е. слово может состоять из двух, четырех или
восьми байт, как показано на рис. 3.2.
слово

байт

10010101 00011100 11110000 10101010 10010101 00011100 11110000 10101010

двойное
слово

учетверенное
слово

Рис. 3.2. Размеры слов

В табл. 3.1 представлены диапазоны значений в зависимости от количества разрядов,


составляющих байты, слова, двойные слова и учетверенные слова. Наибольшее значение
в диапазоне определяется как 2b-1, где b  число битов.

54 Глава 3. Введение в язык ассемблера

Стр. 54
отсутствует. 55

Таблица 3.1. Размеры в битах и диапазон целых чисел


Тип слова Биты Диапазон

байт без знака 8 0&&255


слово без знака 16 0&&65 535
двойное слово без знака 32 0&&4 294 967 295
учетверенное слово без знака 64 0&&18 446 744 073 709 551 615

Команды и данные
В языках высокого уровня команды и данные имеют существенное логическое различие,
однако в машине они все представлены одинаково, как наборы нулей и единиц. Например,
следующая последовательность двоичных разрядов может включать первые три символа
алфавита, сохраненные в строковой переменной, или может быть машинной командой.
010000010100001001000011
Именно поэтому программисты, использующие язык ассемблера, должны разделять
данные и команды, чтобы процессор не ‘‘выполнял’’ переменные и не воспринимал ко&
манды как переменные.

Числовые системы
Каждая числовая система имеет основание системы счисления, или базовое число &&&&
максимальное значение, которое может быть присвоено отдельной цифре. В табл. 3.2 при&
ведены разрешенные значения для различных систем счисления. Во всех последующих
главах при отображении записей в памяти, значений регистров и адресов будут использо&
ваться шестнадцатеричные числа, для которых основанием системы счисления является
число 16. Для компактного отображения значений больше 9 используются шестнадца%
теричные символы от A до F, соответствующие десятичным значениям от 10 до 15.
Когда записывают двоичное, восьмеричное или шестнадцатеричное число, к нему до&
бавляют определенный символ, представленный строчной буквой. Например, шестна&
дцатеричное число 45 должно быть записано как 45h, восьмеричное 76 &&&& как 76o, а дво&
ичное 11010011 необходимо записать как 11010011b. Таким образом ассемблер
распознает числовые константы в исходной программе.

Таблица 3.2. Цифры в различных числовых системах


Система Базовое число Разрешенные значения

Двоичная 2 01
Восьмеричная 8 01234567
Десятичная 10 0123456789
Шестнадцатеричная 16 0123456789ABCDEF

Правила представления числовых данных


Очень важно знать и понимать правила представления данных в памяти и отображе&
ния их на экране. Для примера воспользуемся десятичным числом 65. Сохраненное в памя&
ти как один байт, оно будет представлено в двоичном виде как 01000001. Отладочная про&
грамма, вероятнее всего, будет его отображать как 41, т.е. как шестнадцатеричное значение.

Глава 3. Введение в язык ассемблера 55

Стр. 55
Но если это число послать в память видеоадаптера, то он воспримет его как символ, по&
этому на экране увидим букву А. Это происходит потому, что в соответствии с кодиров&
кой ASCII для символа А выбрано значение 01000001. Таким образом, интерпретация
данного значения зависит от определенных условий, которые и придают ему смысл.
Двоичное число &&&& сохраняется в памяти как последовательность битов, готовых к ис&
пользованию в расчетах. Целые двоичные числа сохраняются по 8, 16 или 32 разряда.
Символы стандартного набора ASCII &&&& могут быть представлены в памяти подоб&
но числовому значению, например как 123 или 65. Для отображения символов
может быть использован любой числовой формат, как показано в табл. 3.3.

Таблица 3.3. Представление буквы ‘‘A’’ в различных форматах


Формат Значение

Двоичный символ ASCII 01000001


Восьмеричный символ ASCII 101
Десятичный символ ASCII 65
Шестнадцатеричный символ ASCII 41

Преобразование двоичных чисел в десятичные


Довольно часто приходится переводить двоичные числа в соответствующие десятич&
ные. В табл. 3.4 показано соответствие двоичных и десятичных чисел от 20 до 215. Каждая
ячейка представляет степень числа 2.
Чтобы перевести двоичное число в десятичное, просуммируйте десятичные эквива&
ленты всех позиций двоичного числа, в которых находится единица. Пример преобразо&
вания числа 00001001 показан на рис 3.3.

Таблица 3.4. Значения разрядов двоичного числа


n Десятичное значение n Десятичное значение
2 2

0 1 8 256
2 2
1 2 9 512
2 2
2 4 10 1024
2 2
3 8 11 2048
2 2
4 16 12 4096
2 2
5 32 13 8192
2 2
6 64 14 16384
2 2
7 128 15 32768
2 2

56 Глава 3. Введение в язык ассемблера

Стр. 56
отсутствует. 57

8 + 1 + 9

0 0 0 0 1 0 0 1

Рис. 3.3. Преобразование двоичного числа


в десятичное

Шестнадцатеричные числа
Большие двоичные числа почти невозможно прочитать, поэтому используют шестна
дцатеричные числа, которые удобно преобразовывать в двоичные числа и которые до
вольно хорошо воспринимаются при просмотре листингов. Их используют и в языке ас
семблера, и в отладчиках для отображения двоичных данных и машинных команд.
Каждое шестнадцатеричное число заменяет четыре двоичных бита, а два шестнадцате
ричных числа представляют байт.
На рис. 3.4 показано представление двоичного числа 000101100000011110010100
в шестнадцатеричном виде 160794h.
1 6 0 7 9 4
0001 0110 0000 0111 1001 0100 = 160794h

Рис. 3.4. Соответствие двоичного и шестнадцате


ричного чисел

Одно шестнадцатеричное число может принимать значения от 0 до 15, поэтому на


равне с числами 0–9 для отображения значений от 10 до 15 используют символы от A до
F: A=10, B=11, C=12, D=13, E=14, F=15. В табл. 3.5 показано, как последовательность
четырех битов переводится в десятичное и шестнадцатеричное значение.

Таблица 3.5. Двоичные, десятичные и шестнадцатеричные эквиваленты


Шестнадцате- Шестнадцате-
Двоичное Десятичное Двоичное Десятичное
ричное ричное

0000 0 0 1000 8 8
0001 1 1 1001 9 9
0010 2 2 1010 10 A
0011 3 3 1011 11 B
0100 4 4 1100 12 C
0101 5 5 1101 13 D
0110 6 6 1110 14 E
0111 7 7 1111 15 F

Каждая позиция шестнадцатеричного числа представляет степень числа 16, что ис
пользуется при вычислении десятичного значения числа, как показано в табл. 3.6.
Для преобразования шестнадцатеричного значения в десятичное необходимо умно
жить значение каждого разряда на соответствующий десятичный эквивалент, а потом их
просуммировать. На рис. 3.5 приведен пример преобразования числа 3BA4h. Берется
наибольшее значение 3 и умножается на десятичный эквивалент позиции  4096. Сле
дующее число B умножается на 256, A умножается на 16 и последнее число 4 умножается
на 1. Все просуммировав, получим соответствующее десятичное число 15268.

Глава 3. Введение в язык ассемблера 57

Стр. 57
Таблица 3.6. Степени числа 16
16n Десятичное 16n Десятичное
0 4
16 1 16 65 536
161 16 165 1 048 576
2 6
16 256 16 16 777 216
163 4096 167 268 435 456

3*4096 + 11*256 + 10*16 + 4*1 = 15268


3 B A 4

Рис. 3.5. Преобразование шестнадца


теричного числа 3BA4 в десятичное

Числа со знаком
Двоичные числа могут быть как со знаком, так и без знака. Числа без знака использу
ют все восемь битов для получения значения (например, 11111111 = 255). Просумми
ровав значения всех битов для преобразования в десятичное число, получим максималь
но возможное значение, которое может хранить байт без знака (255). Для слова без знака
это значение будет составлять 65535. Байт со знаком использует только семь битов для
получения значения, а старший восьмой бит зарезервирован для знака, при этом 0 соот
ветствует положительному значению, а 1  отрицательному. На представленном ниже
рис. 3.6 показано отображение положительного и отрицательного числа 10.
Знаковый бит

1 1 1 1 0 1 1 0 = 10

0 0 0 0 1 0 1 0 = +10

Рис. 3.6. Отображение положительного и отрицательного числа 10

Дополнение до двух
Чтобы не усложнять процессор, отдельный блок для реализации операции вычитания
не делают; эту операцию выполняет блок суммирования. Перед суммированием отрица
тельные числа преобразовываются в дополнительные числа. Это такое число, которое
в сумме с исходным числом дает 0. Например, десятичное –6 будет дополнением к 6, так
как 6+(-6)=0. Таким образом, вместо операции вычитания A–B процессор суммирует
с положительным числом A дополнительное к B: A+(-B). Вместо того чтобы вычесть 4 из 6,
процессор просто складывает –4 и 6.
При работе с двоичными числами для дополнительного числа используется термин
дополнение до двух (встречается также определение двоичное дополнение). Например, для
двоичного значения 0001 двоичным дополнением до двух будет 1111. Такое число полу
чается из исходного числа после изменения всех единиц на нули, а нулей на единицы
(инверсия) и прибавления к полученному числу единицы, как показано ниже. Инверсия

58 Глава 3. Введение в язык ассемблера

Стр. 58
отсутствует. 59

битов в двоичном числе обозначается NOT(n). Поэтому двоичное дополнение может быть
представлено выражением NOT(n)+1.

число 0001
инверсированное число 1110
добавить 1 1111

Если сложить N и дополнение до двух к N, получим 0: 0001+1111=0000. Операция


получения дополнения до двух полностью обратима. Например, для отрицательного чис
ла -10 дополнением до двух будет 10.
11110110 = -10
00001001 инверсия бит
+00000001 добавить 1
00001010 = +10

Еще несколько примеров преобразования чисел приведено в табл. 3.7 (для дополне
ния до двух используем аббревиатуру NEG(n)).

Таблица 3.7. Преобразование чисел


Десятичное Двоичное NEG(n) Десятичное
+2 00000010 11111110 –2
+16 00010000 11110000 –16
+127 01111111 10000001 –127

Максимальные и минимальные значения


Число со знаком из n разрядов может использовать только n-1 бит для получения значе
ния. Например, знаковый байт использует только семь битов (от 0 до 127). В табл. 3.8 пока
заны максимальные и минимальные значения для байт, слов, двойных и учетверенных слов со
знаком. Наименьшие значения (-128, -32768, -2147483648) являются недопустимыми.
Нетрудно убедиться, что двоичное дополнение до -128 (10000000) будет также 10000000.

Таблица 3.8. Целые числа со знаком и без знака


Тип хранения Биты Диапазон

байт со знаком 7 от 128 до +127


слово со знаком 15 от 32 768 до +32 767
двойное слово со знаком 31 от 2 147 483 648
до +2 147 483 647
учетверенное слово со знаком 63 от 9 223 372 036 854 775 808
до +9 223 372 036 854 775 807

Хотя процессор выполняет вычисления без учета знака числа, в программе знак операн
да необходимо обязательно указывать. Сложение операндов со значениями +16 и -23 будет
выглядеть в командах ассемблера следующим образом.
MOV AX,+16
ADD AX,-23

Глава 3. Введение в язык ассемблера 59

Стр. 59
В двоичном выражении число 16 будет выглядеть как 00010000, а -23 &&&& как
11101001. Когда процессор складывает эти числа, он получает 11111001. Это двоичное
число соответствует десятичному -7, как показано в примере.

00010000 16
+11101001 -23
=11111001 -7

Таким образом, сложение чисел со знаком получается корректным. Но в данном слу&


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

Хранение символов
Компьютеры могут хранить только двоичные значения, но нам необходимо работать не
только с численными значениями, но и с символами, такими как ‘‘A’’ или ‘‘$’’. Для этого
компьютер использует схему кодирования символов, которая позволяет преобразовывать
символы в числа и наоборот. Наиболее известная система кодирования для компьютеров
обозначается аббревиатурой ASCII (American Standard Code for Information Interchange).
В ASCII каждому символу присваивается уникальный код, включая контрольные символы,
используемые при печати и передаче данных между компьютерами. Стандартный ASCII&
код использует только 7 разрядов в диапазоне 0-127. Значения от 0 до 31 заняты слу&
жебными кодами, используемыми при печати, передаче информации и выводе на экран.
В обычном режиме они не отображаются на экране. Остальные значения, допустимые в бай&
те, &&&& дополнительные, их применяют для расширения символьного ряда. В операционной
системе MS DOS значения 128-255 используются для получения графических символов
и греческих букв. В операционной системе Windows существует множество наборов симво&
лов, и в каждом из них дополнительным значениям соответствуют различные символы.
Строка символов представляет в памяти последовательность байт. Например, число&
вым кодам строки ‘‘ABC123’’ будет соответствовать последовательность значений 41h,
42h, 43h, 31n, 32h и 33h.
Таблица кодов ASCII приведена в справочном разделе. Чтобы найти шестнадцате&
ричное значение нужного символа, используйте соответствующие значения второй стро&
ки и второй колонки, на пересечении которых находится символ. Старший разряд числа
находится в строке, младший &&&& в колонке. Например, чтобы найти шестнадцатеричное
значение символа ‘‘a’’, посмотрим на соответствующее значение в строке. Это будет 6,
соответствующее значение в колонке будет 1. Таким образом, получаем шестнадцате&
ричное значение 61h.

Хранение чисел
Как наиболее эффективно сохранять числа в памяти? Это зависит от того, как эти
числа будут использоваться. Если числа используются для вычислений, должно быть
применено двоичное представление числа, и наоборот, лучше хранить коды ASCII, если
данные значения будут использоваться для отображения символов на экране. Например,
число 123 можно сохранить в памяти двумя способами: как последовательность кодов
ASCII для чисел 1, 2 и 3 или как один байт со значением 123, как показано на рис. 3.7.
Двоичное содержимое памяти всегда можно просмотреть, но по одному лишь значе&
нию нельзя определить, что оно представляет. Предположим, два байта памяти имеют

60 Глава 3. Введение в язык ассемблера

Стр. 60
отсутствует. 61

значения 01000001 и 01000010. Но что это такое? “1” “2” “3”

Данные, код или текст? Это невозможно узнать, 00110001 00110010 00110011 = “123”

пока не идентифицирован определенный участок


01111011 = 123
памяти. Программа должна отслеживать состояние
данных и тип представления, чтобы избежать кон# Рис. 3.7. Хранение строки ‘‘123’’ и
фликтов. Языки высокого уровня не позволят ис# числа 123 в памяти
пользовать переменные вместо команд, чего нельзя
сказать о языке ассемблера. Ограничения языка помо#
гают избежать серьезных ошибок, но в языке ассемблера почти нет ограничений, и про#
граммист должен учитывать все, даже незначительные детали.

Основы языка ассемблера


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

Команды языка ассемблера


Команды языка ассемблера представляют взаимно однозначное соответствие с ма#
шинными кодами. В простейшем варианте они состоят из мнемокода команды с после#
дующими операндами. Все это непосредственно преобразуется в машинные коды. Ко#
манды могут либо иметь, либо не иметь операндов, как показано на примере ниже.
CLC ; мнемокод
INC EAX ; мнемокод с одним операндом
MOV EAX,EBX ; мнемокод с двумя операндами
Любую команду можно сопроводить комментарием, отделяя его от команды точкой
с запятой ‘‘;’’.
Ассемблер является языком низкого уровня, потому что его команды, по сути, машин#
ные, т.е. команды языка ассемблера имеют взаимно однозначное соответствие с машинны#
ми кодами. И наоборот, одно утверждение в языке высокого уровня, таком как Delphi
или C#, обычно транслируется в несколько машинных кодов.
Операнд может быть регистром, переменной, ячейкой памяти или непосредственным
значением, как показано в табл. 3.9.

Таблица 3.9. Представление операндов


Операнд Описание
10 (непосредственное значение)
count (переменная)
EAX (регистр)
[0200] (ячейка памяти)

Глава 3. Введение в язык ассемблера 61

Стр. 61
Константы и выражения
Цифровой литерал является комбинацией цифр и дополнительных символов &&&& зна&
ков, десятичных точек и экспонент:
5;
5,5;
26,E+05.
Целочисленные константы могут оканчиваться дополнительным буквенным символом,
который является указателем базы системы счисления: h — шестнадцатеричная, q (или o) &&&&
восьмеричная, d &&&& десятичная, b &&&& двоичная. Если дополнительного буквенного сим&
вола нет, то по умолчанию принимается десятичная система счисления. Буквенный символ
может быть строчным или заглавным. В табл. 3.10 приведено несколько примеров цело&
численных констант.
Если число не сопровождается дополнительным буквенным символом, то по умолчанию
принимается, что для данного числа используется десятичная система счисления.

Таблица 3.10. Представление целочисленных констант


Константа Система счисления
1Ah шестнадцатеричная
26 десятичная
1101b двоичная
36q восьмеричная
2BH шестнадцатеричная
42Q восьмеричная
36D десятичная
47d десятичная
0F6h шестнадцатеричная

Хотя дополнительный символ может быть и заглавной буквой, рекомендуется исполь&


зовать строчные буквы для унификации записи. Если шестнадцатеричная константа на&
чинается с буквы, то перед ней должна ставиться цифра 0.
Константное выражение состоит из комбинации цифровых литералов, операторов
и определенных символьных констант. Значение константного выражения определяется
во время трансляции программы и не может меняться во время выполнения программы.
Ниже приведено несколько примеров константных выражений, включающих только
цифровые литералы:
5;
26,5;
4 * 20;
-3 * 4/6;
-2,301E+04.

62 Глава 3. Введение в язык ассемблера

Стр. 62
отсутствует. 63

Символические константы являются именами константных выражений.


rows = 5
columns = 10
tablePos = rows * columns
Обратите внимание на то, что хотя это утверждение и выглядит подобно выражению
времени выполнения в языках высокого уровня, но определяется оно во время трансляции.
Символы или символьные константы могут быть представлены отдельными символами
или строками символов, заключенными в двойные или одинарные кавычки. Внутренние
и внешние кавычки должны быть разного типа, как показано в следующих примерах:
'ABC';
'X';
"This is a test";
'4096';
"This isn't a test";
'Say "hello" to Bill.'.
При этом необходимо хорошо представлять, что символьная константа ‘‘4096’’ занима&
ет в памяти четыре байта, так как каждая цифра определяется в соответствии с кодом ASCII
и занимает один байт, о чем уже говорилось ранее. Также не забывайте, что кодировки сим&
волов русского алфавита для DOS и Windows в диапазоне 127–255 не совпадают. Поэтому
когда вы пишете программу в текстовом редакторе для Windows, а потом выполняете ее
с выводом на экран консоли, которая все символы отображает в кодировке для DOS, то
буквы русского алфавита будут отображаться неправильно. Поэтому либо используйте
английский алфавит, либо делайте перекодировку с помощью специальной процедуры.

Утверждения
В языке ассемблера утверждение состоит из имени, мнемокода, операндов и коммен&
тариев. Утверждения бывают двух типов: команды и директивы. Команды &&&& это утвер&
ждения, выполняемые в программе, а директивы &&&& утверждения, информирующие ас&
семблер о том, как создавать выполняемый код. Общая форма утверждения выглядит так:
[имя] [мнемокод] [операнды] [; комментарии]
Утверждение имеет свободную форму записи. Это означает, что его можно записы&
вать с любой колонки и с произвольным количеством пробелов между операндами. Ут&
верждение должно быть записано на одной строке и не заходить за 128&ю колонку. Мож&
но продолжить запись со следующей строки, но при этом первая строка должна
заканчиваться символом ‘‘\’’ (обратная косая черта), как показано в примере ниже.
longArrayDefinition DW l000h, 1020h, 1030h \
1040h, 1050h, 1060h, 1070h, 1080h
Команда &&&& это утверждение, которое выполняется процессором во время работы
программы. Команды могут быть нескольких типов: передачи управления, передачи дан&
ных, арифметические, логические и ввода&вывода. Команды транслируются ассемблером
непосредственно в машинные коды. Ниже приведен фрагмент листинга со всеми исполь&
зуемыми категориями команд.
CALL MySub ; Передача управления.
MOV EAX,5 ; Передача данных.
ADD EAX,20 ; Арифметическая.
JZ next1 ; Логическая (переход, если установлен флаг нуля).
IN AL,20 ; Ввод-вывод (чтение из аппаратного порта).

Глава 3. Введение в язык ассемблера 63

Стр. 63
Директива  это утверждение, которое выполняется ассемблером во время трансля
ции исходной программы в машинные коды. Например, директива DB заставляет ас
семблер выделить память для однобайтовой переменной, названной count, и поместить
туда значение 50.
count DB 50
Следующая директива .STACK заставляет ассемблер зарезервировать пространство
памяти для стека.
.STACK 4096

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

Таблица 3.11. Допустимые для имен символы


Символы Описание
A ... Z, a ... z Буквы
0 ... 9 Цифры
? Знак вопроса
_ Подчеркивание
@ Знак @
$ Знак доллара

Для имен есть следующие ограничения.


Максимальное количество символов  247 (в MASM).
Заглавные и строчные буквы не различаются.
Первым символом могут быть @, _ или $. Последующими могут быть эти же сим
волы, буквы или цифры. Избегайте использования в начале имени символа ‘‘@’’,
так как многие предопределенные имена начинаются именно с него.
Выбранные программистом имена не должны совпадать со словами, зарезервиро
ванными в языке ассемблера.
Переменные  это данные какойлибо программы, которым присвоены имена.
count1 DB 50 ; Переменная (место в памяти размером 1 байт).
Метка является именем, которое размещается в пространстве кодов. Метки отмечают
те строки программы, на которые необходимо делать переход из других мест. Метка мо
жет стоять в начале пустой строки или за ней могут находиться команды. В приведенном
ниже фрагменте листинга метки указывают на определенные строки в программе.
Label1: MOV EAX,0
MOV EBX,0
Label2:
JMP Label1 ; Переход на Label1.
Ключевые слова всегда имеют предопределенный смысл в языке ассемблера. Это могут
быть команды или директивы, например MOV, PROC, TITLE, ADD, AX или END. Ключевые

64 Глава 3. Введение в язык ассемблера

Стр. 64
отсутствует. 65

слова не могут использоваться программистом для каких&либо других целей, например как
имена. Если использовать ключевое слово ADD как метку, это будет синтаксической ошибкой.
ADD: MOV EAX,10 ; Синтаксическая ошибка!

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


Хотя внешне программы, написанные на языке ассемблера, сильно отличаются от
программ, созданных на языке высокого уровня, тем не менее технология их разработки
одинакова. Однако следует учесть, что разработка программ на языке ассемблера требует
большего внимания и аккуратности. При этом необходимо последовательно выполнить
следующие этапы.
Поставить задачу и составить проект программы. На этом этапе нередко состав&
ляются блок&схемы &&&& эскиз выполняемых программой действий.
Ввести команды программы в компьютер с помощью редактора. При сложной
логике программы удобно предварительно написать комментарии на обычном
языке с описанием предполагаемых действий, а затем вставлять между ними соот&
ветствующие команды языка ассемблера. В качестве редактора можно использо&
вать любой текстовый редактор, который создает файлы с расширением .txt.
Подойдет даже простейший Notepad.
Откомпилировать программу с помощью ассемблера. Если ассемблер обнаружит
ошибки, исправить их в редакторе и откомпилировать программу заново.
Преобразовать результат работы ассемблера в исполняемый модуль с помощью
компоновщика.
Выполнить программу.
Проверить результаты. Если они не соответствуют ожидаемым, найти ошибки
с помощью отладчика. Данный этап называется отладкой и обычно занимает
большую часть времени, затрачиваемого на разработку программы.
Если программа простая и короткая, то ее разработка не займет много времени. Од&
нако сложные программы требуют значительного времени на каждом этапе, поэтому не&
обходимо тщательно планировать процесс разработки уже на этапе проектирования,
иначе этап отладки может никогда не закончиться.
Программа, написанная в командах ассемблера, называется исходной программой, а ее
преобразованный в коды процессора вид именуется объектной программой. Таким обра&
зом, функцией ассемблера является преобразование исходной программы, доступной
восприятию человеком, в объектную программу, понятную процессору.
Операционная система при выполнении может разместить программу в любом под&
ходящем месте памяти и освобождает разработчика от необходимости думать, куда ее
поместить. Но чтобы этим воспользоваться, надо преобразовать оттранслированную
программу в вид, позволяющий ее перемещение. Такие программы называются переме%
щаемыми. Они создаются с помощью компоновщика &&&& программы LINK, которая обяза&
тельно входит в комплект поставки ассемблера.
Обычно объектным модулем называется файл, содержащий результат трансляции
программы ассемблером. А файл, содержащий перемещаемую версию оттранслирован&
ной программы, называется исполняемым модулем. Таким образом, функцией компонов&
щика LINK является создание исполняемого модуля из объектного модуля.
Компоновщик необходим также при написании большой программы. Невозможно на&
писать сложную программу как единое целое, поэтому такие программы пишут по частям,

Глава 3. Введение в язык ассемблера 65

Стр. 65
которые потом можно собрать вместе с помощью компоновщика. При этом можно исполь
зовать модули, написанные другими программистами, или ранее написанные и отлажен
ные модули. Если есть набор подходящих модулей, то разработка сложной программы мо
жет занять не так уж и много времени. Надо только объединить уже существующие и вновь
написанные модули и получить один исполняемый модуль  что и сделает компоновщик.
Компоновщик должен вызываться для любой написанной программы, даже если она
состоит только из одного объектного модуля. Одномодульные программы компоновщик
сразу преобразует в перемещаемый модуль. Если программа состоит из двух или боль
шего количества модулей, то компоновщик сначала объединяет их, а затем преобразовы
вает результат в перемещаемый модуль.
Завершенную программу можно вызвать для выполнения двумя способами:
набрать ее имя в качестве команды или щелкнуть на имени программы мышкой;
выполнить ее под управлением программы DEBUG.
Обычно программу следует выполнять только в том случае, если есть уверенность в ее
безошибочной работе. Пока она не будет полностью отлажена, необходимо вызывать
программу только под управлением отладчика DEBUG. Это связано с тем, что непрове
ренная программа может нанести системе непоправимые повреждения. При программи
ровании под Win32 это маловероятно, но если вы программируете под DOS, то это пра
вило необходимо соблюдать.
С помощью отладчика DEBUG можно управлять процессом выполнения программы.
Наряду с другими функциями, DEBUG позволяет отображать и изменять значения пере
менных, останавливать выполнение программы в заданной точке или выполнять про
грамму по шагам. Таким образом, DEBUG является основным инструментом для поиска
и исправления ошибок в программе.
На рис. 3.8 показаны этапы разработки программ с помощью ассемблера. В скобках
для каждого модуля указаны расширения файлов, в которых модули сохраняются на диске.
Библиотека
загрузочных
модулей
Текстовый редактор
Исходная программа Ассемблер Объектный модуль Загрузчик Исполняемый модуль
Отладчик
(.asm) (.obj) (.exe)

Файл листинга Файл перекрестных


(.lst) ссылок (.crt)

Рис. 3.8. Этапы разработки программ на ассемблере

Разработка программы методом ‘‘сверху вниз’’


При создании программы естественным желанием является начать последовательно
вводить команды, реализуя логику программы по мере их ввода. Такой метод может срабо
тать в случае простой или короткой программы, но обычно это приводит к ошибкам или
созданию программ, которые трудно понять, а впоследствии еще труднее модифицировать.
Благодаря удобным возможностям редактирования текстов, предоставляемым программа
ми обработки текстов и редакторами, можно воспользоваться более легким, удобным и эф
фективным методом разработки программ. Этот метод разработки называется ‘‘сверху вниз’’.
Разработка методом ‘‘сверху вниз’’ означает, что вначале создается набросок про
граммы в виде текста на обычном языке, а затем этот набросок постепенно дополняется

66 Глава 3. Введение в язык ассемблера

Стр. 66
отсутствует. 67

деталями. Набросок должен представлять собой ряд строк, в которых описаны действия
программы. Например, при разработке программы, которая выполняет одну из несколь&
ких функций по выбору пользователя, набросок может выглядеть следующим образом.
; Изобразить меню возможных функций
; Запросить у пользователя выбор из меню
; Прочитать ответ пользователя
; Проверить допустимость ответа
; Если ответ допустим, выполнить требуемую функцию
Точки с запятой означают, что эти строки представляют собой не команды, а коммен&
тарии, которые необходимы только разработчику. Ассемблер пропускает все до конца
строки после точки с запятой.
Затем, с учетом этого текста, производится вставка необходимых команд между стро&
ками. Так как каждая строка описывает относительно небольшую задачу, проще всего
выполнить каждую задачу в отдельности, проверяя решение перед тем, как двинуться
дальше. Иначе говоря, начните со вставки первой группы команд (в представленном
примере с команд для изображения меню), затем запишите полученную программу на
диск и завершите все последующие этапы (трансляцию, компоновку и выполнение).
Реализация такой частичной программы покажет, правильно ли она работает. Если она
работает неправильно, отладьте ее и попробуйте еще раз. После того как первая часть от&
лажена, перейдите ко второй части, затем к третьей и т.д.
Может показаться, что это очень медленный метод разработки программы, но только
так можно разрабатывать программы, которые содержат мало ошибок и которые впо&
следствии можно модифицировать. При этом достигаются следующие цели:
четко просматривается логика программы;
производится документирование программы с помощью комментариев, впослед&
ствии программу будет легко модифицировать;
обеспечивается правильность работы каждой части до того, как произойдет пере&
ход к разработке следующей части программы.

Работа в DOS под Windows NT


Хотя термины ‘‘Windows 3x’’ и ‘‘Windows 9х’’ связывают с операционными системами, эти
графические интерфейсы пользователя являются только оболочкой, сооруженной над DOS.
Довольно часто эти системы останавливаются, и связано это с тем, что если одна программа
для Windows (или программа DOS, работающая под Windows) дает сбой, то зависает и вся
система. Приходится перезагружать компьютер, и, вероятнее всего, часть готовой работы
будет потеряна. И все же, благодаря внутренним конструктивным доработкам, в Windows 98
отказов стало меньше, хотя пользователи, работающие с этими системами, ограничены ар&
хитектурой DOS, которая не позволяет создать оболочку, устойчивую к неисправностям.
Однако все еще работают сотни миллионов копий DOS и десятки тысяч приложений,
созданных для этих систем. Довольно часто программа состоит только из версии DOS,
что относится как к большим приложениям, так и к незаметным утилитам. Поэтому тех&
нология DOS сохранена и в новейших операционных системах, в которых она использу&
ется совсем по&другому. Операционные системы Windows NT (Windows 2000 и Windows XP)
совсем не используют DOS. Программа DOS является только отдельной исполняемой
программой, как и многие другие. При инициализации Windows система DOS не загру&
жается вообще. Технология NT имеет операционную систему с ядром, которое отвечает
за большую часть того, что ‘‘умеет’’ DOS (например, ввод&вывод с помощью клавиатуры

Глава 3. Введение в язык ассемблера 67

Стр. 67
и экрана, работа с загружаемыми драйверами устройств, обработка запросов на ввод&
вывод с диска). Windows NT может эмулировать определенные операционные системы
с использованием модулей, которые называются подсистемами среды, и DOS &&&& одна из
таких операционных систем, работающая как программа под управлением Windows NT.
Подсистема среды DOS предоставляет весь системный сервис, как это обычно делает
DOS, но эти функции интегрированы в Windows NT, а не расположены отдельно от нее.
Windows NT имеет специальное окно для DOS, которое называется командная кон%
соль, и работа в этом окне очень похожа на сеанс MS DOS в Windows 3x или Windows 9x.
Интерпретатор команд содержит богатый набор команд, размеры окна можно изменять,
окно имеет полосу прокрутки и не ограничено 24 строками, как в DOS.
Эмуляцию DOS обеспечивает 32&разрядное приложение с именем cmd.exe, которое яв&
ляется расширенной версией MS DOS и не только обеспечивает совместимость с MS DOS,
но и позволяет запускать приложения Windows, OS/2 и POSIX в режиме командной строки.
Сеанс DOS, который создается при запуске приложения DOS из командного окна,
можно сконфигурировать с помощью загружаемых драйверов устройств, TSR и т.д.
При запуске программы DOS из Windows NT создается виртуальная машина DOS,
благодаря которой программа DOS работает как бы на отдельном компьютере. Windows NT
создает отдельную виртуальную машину для каждого запускаемого приложения DOS.
Каждая такая машина имеет весь сервис, необходимый для работы как с 16&разрядными,
так и с 32&разрядными вызовами DOS в соответствии с требованиями DOS 6. Этой вир&
туальной машине выделяется 16 Мбайт памяти. При необходимости могут поддержи&
ваться диспетчеры памяти. Есть и ограничения. В целях безопасности и защиты ядра
приложения DOS должны быть изолированы. Для этого подсистема среды DOS перехва&
тывает все процессы ввода&вывода, проверяет их, а затем направляет данные по назначе&
нию. Этот процесс обслуживается перехватчиками ввода&вывода, которые, в свою оче&
редь, передают данные программе NT Executive для доставки по назначению.
Любые традиционно написанные программы DOS, которые выполняют ввод&вывод
с помощью стандартных системных вызовов DOS, будут работать под Windows NT без
проблем. А программы, выполняющие запись непосредственно на устройство, для кото&
рого драйверы не разрешают прямого доступа (например, драйверы жестких дисков), бу&
дут прерваны программой контроля безопасности, что приведет к сообщению об ошибке
и завершению опасной программы.
Если программа пытается записывать и читать из портов СОМ и LPT, с экрана или
клавиатуры, то Windows NT выполнит это безупречно, однако другие виды прямого
управления памятью, диском или системным устройством разрешены не будут.
Большинство программ DOS, которые используют драйвер мыши или клавиатуры, бу&
дут работать, поскольку строки ввода драйвера мыши и клавиатуры эмулируются (их ис&
пользуют очень многие программы DOS).
Операционная система DOS использует командную строку для ввода отдельных ко&
манд. Перечень всех доступных команд можно получить, если ввести команду help.
Список всех команд приведен в табл. 3.13.

Инструментальные средства
Термины ‘‘трансляция’’, ‘‘компоновка’’, ‘‘отладка’’ и другие, связанные с этапами ра&
боты ассемблера, уже упоминались в этой книге, но о самом ассемблере пока ничего не
говорилось. В этом разделе будут даны начальные сведения о наиболее популярных ас&
семблерах TASM и MASM.

68 Глава 3. Введение в язык ассемблера

Стр. 68
отсутствует. 69

Ассемблер фирмы Borland (TASM)


Ассемблер фирмы Borland, как и любой другой, поставляется с полным набором про&
грамм, необходимых для компиляции исходного файла, получения выполняемого файла,
компоновки и редактирования. Последняя версия ассемблера Borland Turbo Assembler 5.0
реализует следующие функциональные особенности:
объектно&ориентированную технику программирования;
поддержку 32&разрядной модели и кадра стека;
полную поддержку процессоров Intel 386, Intel 486 и Pentium;
использование директив упрощенной сегментации;
поддержку таблиц;
гибкую систему макросов.
К преимуществам данного компилятора следует также отнести высокую скорость
компиляции. Используя данный ассемблер, можно программировать для Windows. Од&
нако поскольку фирма Borland закрыла свое направление для языков C/C++, она отка&
залась и от TASM как от отдельного продукта. И теперь новые версии TASM приходят
только в составе таких продуктов, как Delphi и C++ Builder.
Фирма Borland разработала отличный ассемблер, располагающий возможностями,
недоступными в других компиляторах, например объектно&ориентированная техника
программирования. Так как TASM удобно использовать при написании программ для
операционной системы Windows, которая состоит из сообщений, исключений и классов,
то эта возможность оказалась как нельзя кстати.

Ассемблер фирмы Microsoft (MASM)


Последняя версия ассемблера Microsoft Macro Assembler 6.15 реализует следующие
возможности:
поддержку 32&разрядной модели памяти и кадра стека;
полную поддержку всех процессоров вплоть до Pentium II;
директивы упрощенной сегментации;
поддержку таблиц;
директивы языков верхнего уровня.
В отличие от фирмы Borland, Microsoft поддерживала свой продукт и выпускала его
как отдельный пакет, так и в составе таких программных пакетов, как Microsoft Quick C,
Microsoft Visual Studio и др.
Использование этого ассемблера при написании программ для Windows будет более
привычным для тех, кто начал использовать MASM еще с операционной системой DOS
и продолжает его применять с Windows. В этом компиляторе предусмотрено все: от раз&
работки простых оконных приложений для Windows до создания виртуальных драйверов
устройств VxD (все подробно описано в документации и файлах помощи). В MASM
нельзя применять объектно&ориентированный стиль программирования, но можно ис&
пользовать дополнительные макроопределения, такие как .IF, .WHILE, .REPEAT,
.CONTINUE, .BREAK.
Еще одно преимущество данного ассемблера &&&& новый пакет MASM32, в котором со&
брано все, что нужно программисту при написании программ для Win32.

Глава 3. Введение в язык ассемблера 69

Стр. 69
Пример простой программы
В представленном листинге приведена простая программа для Win32, которая отобра&
жает на экране традиционное приветствие ‘‘Hello, world!’’. В этой программе показаны
основные особенности приложений на языке ассемблера. В первой строке использована
директива Title, остальные символы строки трактуются как комментарий, подобно всем
символам во второй строке. Исходный код этой программы написан на языке ассемблера
и должен быть оттранслирован в машинные коды перед запуском программы.
Сегменты являются строительными блоками программы. Сегмент кодов определяет
место, где хранятся коды программы, сегмент данных включает все переменные, а сегмент
стека включает исполнительный стек. Стек &&&& это специальное пространство в памяти,
которое обычно используется программой при вызове и возврате подпрограмм.

Программа ‘‘Hello World’’ для Win32


TITLE (.asm)
.386
.model flat, stdcall

ExitProcess PROTO,
x:dword
WaitMsg PROTO
WriteString PROTO
Crlf PROTO

.data
strHello BYTE "Hello Word!",0

.code
main PROC
mov EDX, OFFSET strHello ; Аргументы для WriteString.
invoke WriteString ; Вывод строки на консоль.
invoke Crlf ; Перевод каретки.
invoke WaitMsg ; Запрос для нажатия клавиши.
invoke ExitProcess,0 ; Корректное окончание программы.
main ENDP

END main

Обратите внимание на структуру программы. Она начинается с директивы Title, ко&


торая не является обязательной, но всегда лучше ее поставить и кратко описать назначе&
ние программы (дополнительная информация в данном случае не помешает). Затем идет
директива для задания минимального типа процессора, команды которого можно ис&
пользовать, и директива модели памяти. При создании программ для Win32 всегда долж&
на выбираться директива
.model flat, stdcall
Далее следуют объявления используемых в программе процедур. В данном случае это
процедуры окончания программы (ExitProcess), ожидания нажатия клавиши (WaitMsg),
вывода строки на консоль (WriteString) и перевода каретки (Crlf).
Далее в сегменте данных (.data) объявляется переменная strHello, которая пред&
ставляет собой строку, выводимую на консоль. Наконец, в сегменте кодов (.code) опи&
сывается логика программы, которая в данном случае реализует вывод строки на консоль
и представляет собой последовательный вызов процедур. Эти процедуры могут быть как

70 Глава 3. Введение в язык ассемблера

Стр. 70
отсутствует. 71

процедурами операционной системы Windows (ExitProcess), так и процедурами, на&


писанными самим пользователем и помещенными в библиотеки. Разницы между ними
нет никакой (обо всем этом речь пойдет далее). Обратите внимание на обязательный по&
рядок расположения директив и команд. Обязательно должна присутствовать процедура
main, которая является точкой входа в программу, и заключительные директивы
main ENDP
END main
Иначе компилятор не сможет найти начало и конец программы и не будет ее обраба&
тывать. Для компиляции программы необходимо в окне консоли ввести команды, как
показано на рис. 3.9.

Рис. 3.9. Компиляция программы

Это не единственный способ оформления программы, вы можете встретить другие ди&


рективы, которые будут обрабатываться компилятором. Но в дальнейшем будем ис&
пользовать только такой формат &&&& он довольно наглядный и обладает расширенными воз&
можностями. Для сравнения ниже приведена программа для операционной системы DOS.

Программа ‘‘Hello World’’ для DOS


TITLE Hello World Program (hello.asm)
; Эта программа отображает слова "Hello, world!"
.MODEL small
.STACK 100h
.DATA
strHello DB "Hello, world!",0dh,0ah,'$'
.CODE
main PROC
MOV AX,@data
MOV DS,AX
MOV AH,9
MOV DX,offset message
INT 21h
MOV AX,4C00h
INT 21h
main ENDP
END main

Кратко рассмотрим основные строки программы.


Директива .MODEL small сообщает ассемблеру, что в данной программе необходи&
мо использовать не более 64 Кбайт памяти для кодов и не более 64 Кбайт для данных.

Глава 3. Введение в язык ассемблера 71

Стр. 71
Директива .STACK устанавливает размер пространства для стека емкостью 100h
(256) байт. Директива .DATA отмечает начало сегмента данных, где сохраняются
переменные. Здесь под именем strHello сохраняется строка ‘‘Hello, world!’’, за
которой следуют два служебных символа перехода на новую строку (0dh,0ah).
Символ ‘‘$’’ используется как символ конца строки для подпрограмм, которые бу&
дут считывать эту строку.
Директива .CODE отмечает начало сегмента кодов, где должны находиться выпол&
няемые команды. Директива PROC объявляет начало процедуры. В этой программе
объявлена процедура с именем main.
Первые две команды процедуры main копируют адрес сегмента данных (@data)
в регистр DS. Команда MOV всегда сопровождается двумя операндами: первый ука&
зывает, куда поместить данные, а второй &&&& откуда эти данные взять.
Затем в процедуре main на экран выводится строка символов. При этом вызыва&
ется функция DOS, которая непосредственно выводит на экран строку символов,
начиная с адреса, который указан в регистре DX. Номер этой функции предвари&
тельно должен быть помещен в регистр AH.
Последние две команды процедуры main (MOV AX,4C00h и INT 21h) заканчи&
вают программу и передают управление операционной системе.
Утверждение main ENDP использует директиву ENDP, которая отмечает конец
главной процедуры.
В самом конце находится директива END, заканчивающая программу, которая долж&
на быть оттранслирована. Следующая за ней метка main говорит об окончании
главной процедуры, или программы. Эта директива необходима для компилятора.
Как видите, отличие есть, и оно заключается в том, что вместо процедур Windows
здесь используются прерывания DOS и номера функций, которые выполняют аналогич&
ные действия, но менее удобны в использовании, так как приходится запоминать боль&
шое число номеров функций и прерываний и знать, как передавать и получать из них
рассчитанные значения.
В табл. 3.12 приведен список наиболее часто используемых директив ассемблера.

Таблица 3.12. Стандартные директивы ассемблера


Директива Описание

END Окончание трансляции программы


ENDP Конец процедуры
PAGE Устанавливает формат листинга
PROC Начало процедуры
TITLE Название листинга
.CODE Отмечает начало сегмента кодов
.DATA Отмечает начало сегмента данных
.MODEL Устанавливает режим памяти
.STACK Устанавливает размер стека

После того как программа ‘‘Hello World’’ написана в текстовом редакторе, ее необхо&
димо сохранить на диске под именем hello.asm. После этого ее можно компилировать.

72 Глава 3. Введение в язык ассемблера

Стр. 72
отсутствует. 73

Анализ программы Hello для Win32


На первый взгляд, программа довольно простая и понятная. Но все это потому, что
мы воспользовались библиотекой Study32, которая не входит в стандартную поставку ас&
семблера и которую нужно написать самому. В библиотеке, которую вы будете писать для
себя, необходимо использовать только стандартные процедуры, которые входят в Win&
dows и выглядят несколько сложнее, чем использованные процедуры в программе Hello
для вывода данных на экран или запроса для нажатия клавиши. Чтобы понять, почему
это так, кратко рассмотрим работу операционной системы Windows.

Операционная система Windows


Об этой операционной системе уже говорилось в предыдущих главах, однако следует
дополнить общие понятия необходимыми для начинающего программиста сведениями.
Система Windows одновременно выполняет множество задач, расчлененных на процессы
и потоки, для которых выделяется память и которые независимы друг от друга. Отдель&
ные функциональные программные фрагменты в процессах можно рассматривать как
объекты, к которым операционная система обращается с помощью специальных сооб&
щений. Некоторые объекты могут отображаться на экране (часто из называют элементами
управления), другие такой возможности не имеют, но и те и другие могут получать сооб&
щения Windows и сами отправлять сообщения. Поэтому для того, чтобы, например, вы&
вести в консоли текст, необходимо отправить сообщение нужному элементу управления.
Но как определяется каждый объект Windows? Делается это с помощью специальных но&
меров, или дескрипторов. При создании отдельного объекта операционная система при&
сваивает ему некоторый номер, по которому к этому объекту и можно обратиться.
Поэтому для того чтобы обратиться к консоли, необходимо знать ее дескриптор. Уз&
нать дескриптор консоли можно с помощью процедуры Windows GetStdHandle, прото&
тип которой выглядит так:
GetStdHandle PROTO, ; Получить стандартный дескриптор.
nStdHandle:DWORD ; Тип устройства (ввод, вывод, ошибки).
После того как дескриптор необходимого устройства будет известен, можно исполь&
зовать также процедуру Windows WriteConsole, прототип которой выглядит следую&
щим образом
WriteConsole PROTO, ; Вывести данные из буфера на консоль.
handle:DWORD, ; Дескриптор выходного устройства.
lpBuffer:PTR BYTE, ; Указатель на буфер.
nNumberOfCharsToWrite:DWORD, ; Размер буфера.
lpNumberOfCharsWritten:PTR DWORD, ; Число выведенных символов.
lpReserved:PTR DWORD ; 0 (зарезервировано)
Этот процесс несколько сложнее, чем процедура WriteString, которая была ис&
пользована ранее в программе Hello. При вызове процедуры WriteString был просто
указан адрес буфера (OFFSET strHello), т.е. адрес места в памяти, где находится нуж&
ная строка, и не указывалось ни длины строки, ни дескриптора выходного устройства;
число выведенных символов также не контролировалось.
Понятно, что процедура WriteString написана для того, чтобы получать более на&
глядные и удобные программы, в которых скрыта вся сложность процедур Windows. Можно
прийти к выводу, что процедуры Windows не могут быть более простыми, поскольку они
должны быть универсальными и рассчитанными для применения в различных ситуациях.
Обратите внимание на тот момент, что в процедуре WriteString не указывается
длина строки, которая должна быть выведена. Длина строки также рассчитывается в самой

Глава 3. Введение в язык ассемблера 73

Стр. 73
процедуре и признаком окончания строки служит нулевой символ. Именно поэтому при
объявлении строки в конце поставлено значение нуль.
strHello BYTE "Hello Word!",0
Следовательно, для использования процедуры WriteConsole предварительно необ&
ходимо рассчитать и длину строки.
Полностью программа Hello, которая использует только процедуры Windows и не
обращается к сторонним библиотекам, будет выглядеть так, как показано в листинге 3.1.

Листинг 3.1. Программа ‘‘Hello World’’ для Win32 с использованием только


процедур Windows
TITLE (.asm)
.386
.model flat, stdcall

STD_OUTPUT_HANDLE = -11 ; Предопределенная для Windows константа


STD_INPUT_HANDLE = -10 ; Предопределенная для Windows константа

Initialize PROTO ; Объявление процедуры.


WaitMsg PROTO ; Объявление процедуры.
WriteString PROTO ; Объявление процедуры.

ExitProcess PROTO, ; Окончание процесса (Windows).


x:dword

GetStdHandle PROTO, ; Получить стандартный дескриптор (Windows).


STD_OUTPUT_HANDLE:DWORD

WriteConsoleA PROTO, ; Вывести данные на консоль (Windows).


handle:DWORD, ; Дескриптор выходного устройства.
lpBuffer:PTR BYTE, ; Указатель на буфер с данными.
nNumberOfCharsToWrite:DWORD, ; Размер буфера.
lpNumberOfCharsWritten:PTR DWORD, ; Число выведенных символов.
lpReserved:PTR DWORD ; 0 (зарезервировано).

ReadConsoleA PROTO, ; Считать данные с консолм (Windows).


handle:DWORD, ; Дескриптор входного устройства.
lpBuffer:PTR BYTE, ; Указатель на буфер для записи.
nNumberOfCharsToRead:DWORD, ; Число символов для ввода.
lpNumberOfCharsRead:PTR DWORD, ; Число введенных символов.
lpReserved:PTR DWORD ; 0 (зарезервировано).

FlushConsoleInputBuffer PROTO, ; Очистить буфер консоли (Windows).


nConsoleHandle:DWORD ; Дескриптор устройства ввода.

.data
strHello BYTE "Hello Word!",13,10,0

.code
main PROC
invoke Initialize
mov EDX, OFFSET strHello ; Аргументы для WriteString.
invoke WriteString ; Вывод строки на консоль.
invoke WaitMsg ; Запрос для нажатия клавиши.
invoke ExitProcess,0 ; Корректное окончание программы.
main ENDP

74 Глава 3. Введение в язык ассемблера

Стр. 74
отсутствует. 75

;---------------------------------------------------------
Initialize PROC private
; Получить стандартные дескрипторы консоли для входа и выхода
;----------------------------------------------------
.data
consoleOutHandle DWORD ?
consoleInHandle DWORD ?
.code
pushad

INVOKE GetStdHandle, STD_INPUT_HANDLE


mov [consoleInHandle],eax

INVOKE GetStdHandle, STD_OUTPUT_HANDLE


mov [consoleOutHandle],eax

popad
ret
Initialize ENDP

;---------------------------------------------------------
Str_length PROC USES edi,
pString:PTR BYTE ; Указатель на строку
; Возвращает длину строки с нулевым окончанием.
; Принимает: pString - указатель на строку
; Возвращает: EAX = длина строки
;---------------------------------------------------------
mov edi,pString
mov eax,0 ; Счетчик символов.
L1:
cmp BYTE PTR [edi],0 ; Конец строки? (сравниваем с нулем)
je L2 ; Да: выход. (переход на метку L2)
inc edi ; Нет: выбираем следующий символ.
inc eax ; Добавляет в счетчик 1.
jmp L1 ; Переход на метку L1.
L2: ret ; Возврат из процедуры.
Str_length ENDP

;---------------------------------------------------------
WriteString PROC
; Записывает строку с нулевым окончанием в стандартный выход.
; Принимает: EDX указывает на строку.
;--------------------------------------------------------
pushad ; Сохраняем регистры.
INVOKE Str_length,edx ; Возвращает длину строки в EAX.
cld ; Необходимо выполнить до WriteConsole.
INVOKE WriteConsoleA,
consoleOutHandle, ; Дескриптор выхода.
edx, ; Указывает на строку.
eax, ; Длина строки.
OFFSET strHello, ; Число записанных байт.
0
popad ; Восстанавливаем регистры.
ret
WriteString ENDP

;------------------------------------------------------

Глава 3. Введение в язык ассемблера 75

Стр. 75
WaitMsg PROC
; Отображает запрос и ожидает нажатия клавиши <Enter>.
;------------------------------------------------------
.data
waitmsgstr DB "Press [Enter] to continue...",0
localBuf BYTE 5 DUP(?)
bytesRead DWORD ?
.code
pushad ; Сохраняем регистры.
mov edx,OFFSET waitmsgstr
call WriteString
w1: INVOKE FlushConsoleInputBuffer,consoleInHandle
INVOKE ReadConsoleA,
consoleInHandle, ; Дескриптор устройства ввода.
OFFSET localBuf, ; Указатель на буфер.
5, ; Размер буфера.
OFFSET bytesRead,
0
cmp bytesRead,2 ; Сравнение с 2.
jnz w1 ; Повторение, пока не считано 2 байта

popad ; Восстанавливаем регистры.


ret
WaitMsg ENDP

END main

Итак, основная часть программы, которая находится в сегменте данных и кодов, поч&
ти не изменилась. В сегменте данных несколько изменилось объявление строки
strHello, где в конце добавлены два байта со значениями 13 и 10. Эти байты будут вос&
приниматься устройством вывода на консоль как перевод каретки, что аналогично ис&
пользованию в первом случае процедуры Crlf. Это не принципиально и сделано для по&
каза различных возможностей ассемблера.
Также не принципиально и появление новой процедуры Initialize, которая полу&
чает и присваивает значения дескрипторов соответствующим переменным, используе&
мым при инициализации устройств ввода&вывода. Эта процедура использовалась и ра&
нее, только это было не так явно.
Теперь для компиляции этой программы необходимо использовать команды, как по&
казано на рис. 3.10

Рис. 3.10. Компиляция программы, в которой использованы только процедуры


Windows

76 Глава 3. Введение в язык ассемблера

Стр. 76
отсутствует. 77

Давайте последовательно пройдем шаги, которые необходимы для того, чтобы вернуть
эту программу в исходное состояние, т.е. представим ее в удобном для программиста и тех,
кто ее анализирует, виде. Для этого существуют такие возможности, как создание биб&
лиотек и заголовочных файлов, в которые и будет вынесено все, что не используется при
описании логики программы, но что необходимо для того, чтобы компилятор мог реали&
зовать эту логику.
Сначала создадим собственную библиотеку с именем MyLib и вынесем в нее все, что
не составляет суть программы. Это будут все объявления и процедуры, которые описаны
после конца процедуры main: Initialize, WriteString, WaitMsg и Str_length.
Создадим отдельный файл и перенесем в него все эти объявления и процедуры. Этот
файл может выглядеть так
TITLE Библиотека необходимых процедур (MyLib.asm)
.386
.model flat, stdcall

STD_OUTPUT_HANDLE = -11 ; Константа Windows для устройства вывода.


STD_INPUT_HANDLE = -10 ; Константа Windows для устройства ввода.

; Ниже идут объявления процедур Windows.

ExitProcess PROTO, ; Окончание процесса (Windows).


x:dword

GetStdHandle PROTO, ; Получить стандартный дескриптор


(Windows).
STD_OUTPUT_HANDLE:DWORD

WriteConsoleA PROTO, ; Вывести данные на консоль (Windows).


handle:DWORD, ; Дескриптор выходного устройства.
lpBuffer:PTR BYTE, ; Указатель на буфер с данными.
nNumberOfCharsToWrite:DWORD, ; Размер буфера.
lpNumberOfCharsWritten:PTR DWORD, ; Число выведенных символов.
lpReserved:PTR DWORD ; 0 (зарезервировано).

ReadConsoleA PROTO, ; Считать данные с консоли (Windows).


handle:DWORD, ; Дескриптор входного устройства.
lpBuffer:PTR BYTE, ; Указатель на буфер для записи.
nNumberOfCharsToRead:DWORD, ; Число символов для ввода.
lpNumberOfCharsRead:PTR DWORD, ; Число введенных символов.
lpReserved:PTR DWORD ; 0 (зарезервировано).

FlushConsoleInputBuffer PROTO, ; Очистить буфер консоли (Windows).


nConsoleHandle:DWORD ; Дескриптор устройства ввода.

; Конец объявлений процедур Windows.

.code
;---------------------------------------------------------
Initialize PROC
; Получить стандартные дескрипторы консоли для входа и выхода.
;----------------------------------------------------
.data
consoleOutHandle DWORD ? ; Дескриптор устройства вывода.
consoleInHandle DWORD ? ; Дескриптор устройства ввода.
.code

Глава 3. Введение в язык ассемблера 77

Стр. 77
pushad

INVOKE GetStdHandle, STD_INPUT_HANDLE


mov [consoleInHandle],eax

INVOKE GetStdHandle, STD_OUTPUT_HANDLE


mov [consoleOutHandle],eax

popad
ret
Initialize ENDP

;---------------------------------------------------------
Str_length PROC USES edi,
pString:PTR BYTE ; Указатель на строку
; Возвращает длину строки с нулевым окончанием.
; Принимает: pString - указатель на строку
; Возвращает: EAX = длина строки
;---------------------------------------------------------
mov edi,pString
mov eax,0 ; Счетчик символов.
L1:
cmp BYTE PTR [edi],0 ; Конец строки? (сравниваем с нулем)
je L2 ; Да: выход. (переход на метку L2)
inc edi ; Нет: выбираем следующий символ.
inc eax ; Добавляет в счетчик 1.
jmp L1 ; Переход на метку L1.
L2: ret ; Возврат из процедуры.
Str_length ENDP

;---------------------------------------------------------
WriteString PROC
; Записывает строку с нулевым окончанием в стандартный выход.
; Принимает: EDX указывает на строку.
;--------------------------------------------------------
.data
wsBuf DWORD ?
.code
pushad ; Сохраняем регистры.
INVOKE Str_length,edx ; Возвращает длину строки в EAX.
cld ; Выполнить до WriteConsole.
INVOKE WriteConsoleA,
consoleOutHandle, ; Дескриптор выхода.
edx, ; Указывает на строку.
eax, ; Длина строки.
OFFSET wsBuf, ; Число записанных байт.
0
popad ; Восстанавливаем регистры.
ret ; Возврат из процедуры.
WriteString ENDP

;------------------------------------------------------
WaitMsg PROC
; Отображает запрос и ожидает нажатия клавиши <Enter>.
;------------------------------------------------------
.data
waitmsgstr DB "Press <Enter> to continue...",0
localBuf BYTE 5 DUP(?) ; Буфер.

78 Глава 3. Введение в язык ассемблера

Стр. 78
отсутствует. 79

bytesRead DWORD ? ; Число считанных символов.


.code
pushad ; Сохраняем регистры.
mov edx,OFFSET waitmsgstr
call WriteString
w1: INVOKE FlushConsoleInputBuffer,consoleInHandle
INVOKE ReadConsoleA,
consoleInHandle, ; Дескриптор устройства ввода.
OFFSET localBuf, ; Указатель на буфер.
5, ; Размер буфера.
OFFSET bytesRead, ; Указатель на переменную.
0
cmp bytesRead,2 ; Сравниваем число введенных байт с 2.
jnz w1 ; Повторение, пока не считано 2 байта
popad ; Восстанавливаем регистры.
ret ; Возврат из процедуры.
WaitMsg ENDP
END
Откомпилируем его с помощью команд, как показано на рис. 3.11.

Рис. 3.11. Компиляция библиотеки

Как видно из рисунка, после строки


Assembling: MyLib.asm
появилось сообщение об ошибке
LINK : error LNK2001: unresolved external symbol _mainCRTStartup
MyLib.exe : fatal error LNK1120: 1 unresolved externals
Не обращайте на него внимания &&&& это сообщение говорит о том, что компоновщик
не может обработать объектный модуль по той причине, что не нашел начала программы,
т.е. процедуры main. Но ее и не должно быть в библиотеке, библиотека только компили&
руется, а не компонуется. Поэтому можно вызвать программу ML с опцией /c (компилиро&
вать без компоновки), тогда сообщение об ошибке не появится.
Итак, после успешной компиляции получен объектный модуль Mylib.obj. Это и бу&
дет необходимой библиотекой. Используйте ее при компиляции программы, состоящей
только из необходимых строк
TITLE (.asm)
.386
.model flat, stdcall

Initialize PROTO

Глава 3. Введение в язык ассемблера 79

Стр. 79
ExitProcess PROTO,
x:DWORD
WaitMsg PROTO
WriteString PROTO

.data
strHello BYTE "Hello Word!",13,10,0

.code
main PROC
invoke Initialize
mov EDX, OFFSET strHello ; Аргументы для WriteString.
invoke WriteString ; Вывод строки на консоль.
invoke WaitMsg ; Запрос для нажатия клавиши.
invoke ExitProcess,0 ; Корректное окончание программы.
main ENDP
END main
При компиляции будем использовать вновь созданную библиотеку MyLib.obj, т.е. бу&
дем использовать команды, как показано на рис. 3.12.

Рис. 3.12. Компиляция с использованием собственной библиотеки

Здесь также можно наблюдать некоторое отличие от первой программы Hello, что
наглядно демонстрирует гибкость ассемблера &&&& вы можете так настроить программу, что она
будет полностью отвечать вашим запросам.
Например, в библиотеке необходимо четко разделить процедуры и объявления. Все
объявления следует вынести в отдельный заголовочный файл, который постоянно будет
использоваться с различными библиотеками, а в самой библиотеке оставить только описа&
ния процедур, для чего она и предназначена. Обо всем этом и пойдет речь в дальнейшем.

Ассемблер Microsoft
Как уже не раз отмечалось, операционная система DOS сохранена для работы с множе&
ством все еще существующих программ, написанных под DOS. Ассемблер Microsoft &&&& од&
на из таких программ. Для этой программы нет соответствующего оконного интерфейса
под Windows, и приходится работать в менее удобном консольном окне. Поэтому для нача&
ла работы с ассемблером необходимо запустить интерпретатор команд DOS, для чего выби&
рается команда Start All Programs Accessories Command Prompt в системном меню. За&
тем необходимо установить пути для удобного запуска программ ассемблера. Для этого
существует команда PATH из набора команд DOS. Рассмотрим подробнее работу в окне DOS.

80 Глава 3. Введение в язык ассемблера

Стр. 80
отсутствует. 81

Работа с DOS
Для работы с DOS необходимо запустить интерпретатор команд DOS. Для операци&
онной системы Windows XP это делается так, как описано в предыдущем разделе. Полу&
чим окно, показанное на рис. 3.13.

Рис. 3.13. Окно интерпретатора команд DOS

Теперь необходимо перейти в каталог, где находится программа интерпретатора команд


DOS &&&& cmd.exe. Это каталог ..\WINDOWS\system32. Здесь не указан том, в котором нахо&
дится этот каталог, так как он жестко не регламентируется и может отличаться. Для того чтобы
сделать этот каталог активным, необходимо использовать команду: cd путь_к_каталогу.
После этого можно ввести команду help &&&& появится перечень всех доступных ко&
манд, который приведен в табл. 3.13.

Таблица 3.13. Перечень команд DOS для Windows XP


Команда Описание
ASSOC Отображает или модифицирует связанные с программами расширения
AT Вызывает таблицу команд и программ для запуска на компьютере
ATTRIB Отображает или изменяет атрибуты файла
BREAK Устанавливает или сбрасывает проверку комбинации клавиш
CACLS Отображает или модифицирует список контроля доступа файлов (ACL)
CALL Вызывает одну командную программу из другой
CD Отображает или изменяет текущий каталог
CHCP Отображает или устанавливает номер активной кодовой страницы
CHDIR Отображает или изменяет текущий каталог
CHKDSK Проверяет диск и отображает отчет
CHKNTFS Отображает или модифицирует проверку диска во время компоновки
CLS Очищает экран
CMD Запускает новый экземпляр интерпретатора команд Windows
COLOR Устанавливает цвета символов и фона
COMP Сравнивает содержимое двух файлов
COMPACT Отображает или изменяет сжатие файлов на разделах NTFS
CONVERT Конвертирует тома FAT в NTFS. Нельзя конвертировать текущий том
COPY Копирует один или несколько файлов
DATE Отображает или устанавливает дату
DEL Стирает один или несколько файлов

Глава 3. Введение в язык ассемблера 81

Стр. 81
Продолжение табл. 3.13
Команда Описание
DIR Отображает список файлов и подкаталогов
DISKCOMP Сравнивает содержимое двух флоппи&дисков
DISKCOPY Копирует содержимое флоппи&диска на другой диск
DOSKEY Редактирует командную строку, повторяет команды и создает макросы
ECHO Отображает сообщения или выключает и включает отображение вводимых
символов на экране (эхо)
ENDLOCAL Завершает изменения локализации Windows в командных файлах
ERASE Удаляет один или несколько файлов
EXIT Закрывает интерпретатор команд
FC Сравнивает содержимое нескольких файлов и отображает различие между ними
FIND Выполняет поиск текстовой строки в одном или нескольких файлах
FINDSTR Осуществляет поиск строк в файлах
FOR Запускает указанную команду для каждого файла из набора нескольких файлов
FORMAT Выполняет форматирование диска для работы с Windows
FTYPE Осуществляет отображение или модификацию типов файлов, используемых
в перечне соответствий типов и расширений
GOTO Выполняет переход к помеченной строке в командных файлах
GRAFTABL Разрешает системе Windows отображать расширенный набор символов
в графическом режиме
HELP Используется для получения справочной информации
IF Используется для создания условного утверждения в командных файлах
LABEL Создает, изменяет или удаляет метки томов на диске
MD Создает каталог
MKDIR Создает каталог
MODE Конфигурирует системные устройства
MORE Отображает часть выходных данных на экран за один раз (чтобы получить
продолжение, необходимо еще раз выполнить эту команду)
MOVE Перемещает один или несколько файлов из одного каталога в другой
PATH Отображает или устанавливает путь поиска файлов для операционной системы
PAUSE Приостанавливает обработку команд в командных файлах и отображает сообщение
POPD Восстанавливает предыдущее значение текущего каталога, сохраненного командой PUSHD
PRINT Распечатывает текстовый файл
PROMPT Изменяет вид запроса
PUSHD Сохраняет текущий каталог перед его изменением
RD Удаляет каталог
RECOVER Восстанавливает доступную информацию с запорченных дисков
REM Отмечает комментарии в командных файлах или файле CONFIG.SYS
REN Переименовывает один или несколько файлов
RENAME Переименовывает один или несколько файлов

82 Глава 3. Введение в язык ассемблера

Стр. 82
отсутствует. 83

Окончание табл. 3.13


Команда Описание
REPLACE Заменяет файлы
RMDIR Удаляет каталог
SET Отображает, устанавливает или удаляет переменные окружения Windows
SETLOCAL Начинает локализацию окружения Windows в командных файлах
SHIFT Сдвигает позицию заменяемых параметров в командных файлах
SORT Выполняет сортировку
START Запускает указанную команду или программу в отдельном окне
SUBST Создает виртуальный диск
TIME Отображает или устанавливает системное время
TITLE Устанавливает имя окна для интерпретатора команд
TREE Графически отображает структуру каталогов
TYPE Отображает содержимое текстового файла
VER Отображает версию Windows
VERIFY Устанавливает режим проверки файлов на корректность записи на диск
VOL Отображает метку тома и серийный номер
XCOPY Копирует файлы и каталоги

Как видите, команд операционной системы DOS не так уж и много и запомнить их не со&
ставляет особого труда. Но даже этого делать не надо (те несколько команд, которые необ&
ходимы для запуска и работы с ассемблером, будут рассмотрены подробно далее, а аналоги
всех команд есть в системе Windows, так как режим DOS просто эмулируется системой
Windows и в конечном итоге производится обращение к процедурам Windows). В Windows 98
и в более ранних версиях можно работать и непосредственно в DOS, так как эти системы
являются только надстройками над DOS, но начиная с версии Windows NT все обстоит
по&другому. Здесь системы DOS в чистом виде уже нет и вся работа производится только
через процедуры Windows.
Чтобы получить более подробное описание каждой команды, необходимо набрать в ко&
мандной строке имя команды и дополнить ее символами ‘‘/?’’. Например, если набрать
cd /?, то получим следующую справку.
Отображает или изменяет имя текущего каталога.
CHDIR [/D] [drive:][path]
CHDIR [..]
CD [/D] [drive:][path]
CD [..]
Две точки (..) указывают, что необходимо перейти к каталогу, включающему данный
каталог.
Если набрать CD без параметров, то отобразится обозначение текущего тома и каталог.
Если набрать CD с обозначением тома, то отобразится текущий каталог.
Параметр /D используется для переключения текущего тома на указанный том с не&
обходимым каталогом.
Команда CHDIR используется в тех случаях, когда имена файлов или каталогов со&
держат пробелы.

Глава 3. Введение в язык ассемблера 83

Стр. 83
А вот командные файлы, которые существовали еще в первых системах DOS, могут
принести пользу даже при работе в системе Windows. Рассмотрим подробнее команд&
ные файлы.

Командный файл
Как уже отмечалось, в среде DOS работать довольно неудобно, так как приходится вруч&
ную набирать довольно длинные команды и пути файлов, что часто приводит к ошибкам.
Командные файлы созданы для облегчения работы в среде DOS, но и в среде Windows
они тоже приносят довольно ощутимую пользу.
Командный файл &&&& это обычный текстовый файл с последовательностью команд, ко&
торые должна выполнить система. Командный файл можно написать в обычном тексто&
вом редакторе и сохранить с расширением .bat. Файлы с таким расширением операци&
онная система трактует как командные и вызывает для их выполнения интерпретатор
команд, а не текстовый редактор.
В командный файл можно включать все команды DOS, а также команды условного пе&
рехода for, goto и if, которые позволяют реализовывать различную последовательность
выполнения команд в зависимости от наличия определенных условий. Еще несколько
команд позволяют контролировать ввод&вывод и вызывать другие командные файлы.
Контролировать процесс выполнения можно по возвращаемым приложениями кодам
ошибок, которые могут быть равны 0 (ошибок нет) или 1 (большие значения при нали&
чии ошибок).
Командный файл может иметь параметры, которые дописываются в командный файл
при его запуске на выполнение. Для подстановки параметров в команды используются
переменные от %0 до %9. Если используется переменная %0, то вместо ее при запуске
подставляется имя командного файла, а переменные от %1 до %9 заменяются соответст&
вующими аргументами. Для доступа к аргументу за пределами %9 используется команда
shift. Переменная %* ссылается на все аргументы, за исключением %0.
Например, для копирования содержимого каталога Folder1 в каталог Folder2
можно создать командный файл Mybatch.bat, содержимое которого будет представлять
одну строку
xcopy %1\*.* %2
и при вызове этого файла как
Mybatch.bat C:\folder1 D:\folder2
вместо переменной %1 будет подставлен каталог Folder1, а вместо переменной %2 &&&&
каталог Folder2.
Тот же самый результат можно получить, если в среде DOS выполнить команду
xcopy C:\folder1\*.* D:\folder2
Дополнительно с параметрами командного файла можно применять модификаторы.
Модификаторы используют информацию о текущем диске и каталоге для расширения
параметров. При использовании модификаторов сначала поставьте символ процента (%),
затем тильду (~), а за ней &&&& требуемый модификатор.
Все возможные модификаторы перечислены в табл. 3.14.

84 Глава 3. Введение в язык ассемблера

Стр. 84
отсутствует. 85

Таблица 3.14. Модификаторы для командных файлов


Модификатор Описание
%~1 Удаляет все фрагменты, ограниченные кавычками ("")
%~f1 Дополняет аргумент полным путем
%~d1 Дополняет аргумент буквенным символом текущего диска
%~p1 Дополняет аргумент путем
%~n1 Дополняет аргумент именем файла
%~x1 Дополняет аргумент расширением файла
%~s1 Использует только короткие имена
%~a1 Дополняет аргумент атрибутами файла
%~t1 Дополняет аргумент датой и временем создания файла
%~z1 Дополняет аргумент размером файла
%~$PATH:1 Ищет каталоги, перечисленные в переменной окружения PATH
и дополняет аргумент полным путем первого найденного каталога

Подготовка к запуску ассемблера


Теперь, когда вы познакомились с командами DOS и командными файлами, сделаем
так, чтобы было удобно запускать файлы на компиляцию, компоновку и отладку.
Для начала нужно обязательно указать пути, по которым операционная система будет
находить каталог с установленными модулями ассемблера. Например, если ассемблер
находится в каталоге E:\MASM615\, то необходимо выполнить следующую команду:
path E:\MASM615;%path%
После выполнения этой команды операционная система уже будет знать, в каких ката&
логах искать исполняемый файл, если не указан полный путь, а введено только имя файла.
Здесь используется модификатор %path% для сохранения всех ранее введенных каталогов.
А для того чтобы сделать активным каталог с вашими программами, необходимо вы&
полнить команду cd. Например:
cd /d H:\Gennady\Work\Assembl\Book1\Programs
Чтобы не набирать текст каждый раз, создайте командный файл и включите в него эти
строки, после чего нужно будет только запустить командный файл, и все выполнится ав&
томатически.
Теперь можно откомпилировать разработанную ранее программу Hello, которая
должна находиться в активном каталоге. Для этого необходимо вызвать компилятор. Ко&
мандная строка для вызова компилятора имеет следующий синтаксис:
ML [ /options ] filelist [ /link linkoptions ]
Здесь options &&&& это опции, или дополнительные элементы настройки; filelist &&&&
имя файла; linkoptions &&&& опции компоновщика. Так как имена заключены в квад&
ратные скобки, то их указывать не обязательно. Если не установить соответствующие
опции, эти файлы все равно будут созданы, но с именем исходного файла. Все допусти&
мые опции перечислены в табл. 3.15.

Глава 3. Введение в язык ассемблера 85

Стр. 85
Таблица 3.15. Опции компилятора MASM
Опция Описание
/AT Разрешена тонкая модель памяти (.COM)
/Bl<linker> Использовать альтернативный компоновщик
/c Компилировать без компоновки
/Cp Сохранять регистры пользовательских идентификаторов
/Cu Все идентификаторы в верхнем регистре
/Cx Сохранять регистры для открытых и внешних идентификаторов
/coff Генерировать объектный файл в формате COFF
/D<name>[=text] Задать макроопределение
/EP Вывести листинг препроцессора
/F <hex> Задать размер стека в байтах
/Fe<file> Ввести имя исполняемого файла
/Fl[file] Генерировать листинг
/Fm[file] Генерировать карту распределений
/Fo<file> Ввести имя объектного файла
/Fpi Эмулировать код 80×87
/Fr[file] Генерировать ограниченную обзорную информацию
/FR[file] Генерировать полную обзорную информацию
/G<c|d|z> Использовать вызовы Pascal, C или Stdcall (по умолчанию Stdcall)
/H<number> Установить максимальную длину внешних имен
/I<name> Добавить пути для подключаемых файлов
/link Задать <Опции компоновщика и библиотеки>
/nologo Не выводить авторские права
/omf Генерировать объектный файл в формате OMF
/Sa Задать максимальный размер листинга
/Sc Генерировать временные метки
/Sf Генерировать листинг первого прохода
/Sl<width> Задать длину строки
/Sn Не выводить таблицу перекрестных ссылок
/Sp<length> Задать длину страницы
/Ss<string> Задать подзаголовок
/St<string> Задать заголовок
/Sx Выводить ошибочные условия
/Ta<file> Компилировать файл с не .ASM&расширением
/w То же самое, что и /W0 /WX
/WX Трактовать предупреждения как ошибки
/W<number> Задать уровень предупреждений
/X Игнорировать путь к INCLUDE
/Zd Добавить номера строк в отладочную информацию

86 Глава 3. Введение в язык ассемблера

Стр. 86
отсутствует. 87

Окончание табл. 3.15


Опция Описание
/Zf Сделать все идентификаторы открытыми
/Zi Добавить символическую отладочную информацию
/Zm Обеспечить совместимость с MASM 5.10
/Zp[n] Выравнивать структуры
/Zs Проверить синтаксис без создания выходного файла

Теперь можно давать команду на трансляцию рассмотренного выше файла Hello,


который находится в каталоге \Work\HelloWord, в следующем виде:
G:\Work\HelloWord>ML /nologo /B link32 /coff Hello.asm /link /nologo /
SUBSYSTEM:CONSOLE F:\MASM615\LIB\KERNEL32.LIB F:\MASM615\LIB\USER32.LIB F:\
MASM615\LIB\Study32.lib
А это значит, что вы указываете транслятору не выводить информацию об автор&
ских правах (/nologo), использовать 32&разрядный компоновщик (/Bl link32),
создать объектный модуль формата coff (/coff) и использовать консольное окно
(/SUBSYSTEM:CONSOLE). При компоновке необходимо использовать следующие библиоте&
ки: F:\MASM615\LIB\KERNEL32.LIB, F:\MASM615\LIB\USER32.LIB, F:\MASM615\
LIB\Study32.lib
При трансляции программ для Windows следует использовать стандартные библиоте&
ки Windows: KERNEL32.LIB и USER32.LIB, в которых находятся все необходимые про&
цедуры и которые входят в поставку ассемблера. В данном случае использована также
библиотека Study32.lib, в которой находятся процедуры, написанные пользователем.
(Об этом уже говорилось ранее и еще будет говориться в дальнейшем, когда вы будете
разрабатывать собственную библиотеку.)
При успешной трансляции на экране появится следующее сообщение:
Assembling: Hello.asm
G:\Work\HelloWord>
После трансляции в указанном каталоге появятся файлы с расширением .obj и .lst
(при заданной опции). Полностью файл листинга (.lst) приведен ниже.
Microsoft (R) Macro Assembler Version 6.15.8803 08/15/06 16:28:02
(.asm) Page 1 - 1

TITLE (.asm)
.386
.model flat, stdcall

ExitProcess PROTO,
x:dword
WaitMsg PROTO
WriteString PROTO
Crlf PROTO

00000000 .data
00000000 48 65 6C 6C 6F strHello BYTE "Hello Word!",0
20 57 6F 72 64
21 00

00000000 .code

Глава 3. Введение в язык ассемблера 87

Стр. 87
00000000 main PROC

00000000 BA 00000000 R
mov EDX, OFFSET strHello ; Аргументы для WriteString.
invoke WriteString ; Вывод строки на консоль.
invoke Crlf ; Перевод каретки.
invoke WaitMsg ; Запрос для нажатия клавиши.
invoke ExitProcess,0 ; Корректное окончание программы.
0000001B main ENDP

END main
Microsoft (R) Macro Assembler Version 6.15.8803 08/15/06 16:28:02
(.asm) Symbols 2 - 1
Segments and Groups:
N a m e Size Length Align Combine Class FLAT GROUP
_DATA . . 32 Bit 0000000C DWord Public 'DATA'
_TEXT . . 32 Bit 0000001B DWord Public 'CODE'

Procedures, parameters and locals:


N a m e Type Value Attr
Crlf . . . .P Near 00000000 FLAT Length= 00000000 External STDCALL
ExitProcess P Near 00000000 FLAT Length= 00000000 External STDCALL
WaitMsg . . P Near 00000000 FLAT Length= 00000000 External STDCALL
WriteString P Near 00000000 FLAT Length= 00000000 External STDCALL
main . . . P Near 00000000 _TEXT Length= 0000001B Public STDCALL

Symbols:

N a m e Type Value Attr

@CodeSize . . . . . ..Number 00000000h


@DataSize . . . . . . Number 00000000h
@Interface . . . . . . Number 00000003h
@Model . . . . . . . . Number 00000007h
@code . . . . . . . . Text _TEXT
@data . . . . . . . . Text FLAT
@fardata? . . . . . . Text FLAT
@fardata . . . . . . . Text FLAT
@stack . . . . . . . . Text FLAT
strHello . . . . . . . Byte 00000000 _DATA

0 Warnings
0 Errors
В этом листинге приведена вся информация о созданной программе, начиная с ис&
ходных команд и их машинных эквивалентов и заканчивая распределением имен, памяти
и сегментов. При профессиональном программировании приходится довольно часто об&
ращаться к файлам листингов, но на первом этапе изучения языка в этом большой необ&
ходимости нет.

Синтаксические ошибки
Очень немного найдется программистов, которые сразу смогут написать даже неболь&
шую программу без ошибок. Поэтому ассемблер проверяет написанные команды и вы&
водит на экран все строки, в которых есть ошибки, причем с их объяснением. Например,
если в программе Hello первую команду MOV ошибочно набрать как MIV, то появится
следующее сообщение:

88 Глава 3. Введение в язык ассемблера

Стр. 88
отсутствует. 89

Assembling: Hello.asm
Hello.asm(17) : error A2008: syntax error : edx
Иными словами, компилятор зафиксирует синтаксическую ошибку перед операндом
edx в строке номер 17.

Компоновка программы
После компиляции будет получен объектный файл с расширением .obj, который не&
обходимо преобразовать в исполняемый модуль. На данном этапе используется про&
грамма&компоновщик, которая использует объектный файл (в данном случае
hello.obj) в качестве входного и создает выполняемый файл, называя его hello.exe.
Командная строка для компоновщика имеет следующий формат:
LINK [options] [files] [@commandfile]
Здесь link &&&& имя программы компоновщика, files &&&& файлы библиотек.
Не будем рассматривать все опции компоновщика, так как на первом этапе они не приго&
дятся. Необходимо знать только опцию /DEBUG (она заставляет компоновщик вставлять
в создаваемую программу нужные для отладчика коды) и опцию /SUBSYSTEM:CONSOLE
(приводит к созданию формата файла, который ‘‘понимает’’ Windows и рассчитан на ра&
боту в консольном режиме). Чтобы просмотреть все опции 32&разрядного отладчика,
просто наберите в командном окне имя отладчика link32. Также можно использовать
опцию /MAP, которая приводит к созданию файла распределения памяти с расширением
.map. Попробуйте создать этот файл и проанализируйте его. От его использования тоже
может быть ощутимая польза.
После работы компоновщика будет создан исполняемый файл с расширением .exe,
который уже можно запускать на выполнение как любую программу Windows.
Использование программы ML позволяет выполнять компиляцию и компоновку одной
командой. Но можно использовать отдельно компилятор (MASM) и 32&разрядный компо&
новщик (LINK32). Также необходимо отметить, что версии 32&разрядного компоновщика,
которые Microsoft использует в более поздних версиях, чем MASM615, называются просто
LINK, без цифр 32 (в версии MASM615 так называется 16&разрядный компоновщик). На&
чиная с этой версии, Microsoft уже не разрабатывает 16&разрядные компоновщики.

Запуск программы
Для запуска программы наберите в командной строке имя выполняемой программы
hello или просто щелкните на ней мышкой в среде Windows.

Отладчик
Основной инструмент, с которым приходится работать при создании программ на ас&
семблере, &&&& отладчик. В дальнейшем будут рассматриваться небольшие примеры про&
грамм на языке ассемблера, и лучшим способом для их изучения является использование
отладчика. Отладчик &&&& это программа, позволяющая отображать на экране значения необ&
ходимых переменных, получать состояние всех регистров и ячеек памяти при пошаговом
выполнении программы, вносить изменения в программу, указывать точки останова
и многое другое. Это необходимо при проверке написанных на языке ассемблера программ.
Существует несколько хороших отладчиков, но мы будем использовать универсаль&
ный и довольно удобный отладчик Microsoft, который распространяется бесплатно и на&
зывается dbg_x86_6.5.3.8.exe. Этот отладчик имеет оконный интерфейс и привыч&
ные для работающих в среде Windows функции.

Глава 3. Введение в язык ассемблера 89

Стр. 89
Простая программа
Для знакомства с отладчиком напишем на языке ассемблера небольшую программу
Sum, которая складывает три числа и сохраняет сумму в памяти. Оттранслируем и вы
полним компоновку программы с использованием опций отладки, как показано на
рис. 3.14, а затем начнем работу с отладчиком.

Рис. 3.14. Компиляция и компоновка программы с опциями отладки

Программа Sum
TITLE Программа суммирования (sum.asm)
.386
.MODEL flat, stdcall ; Задаем модель памяти.
WaitMsg PROTO ; Прототип функции WaitMsg.
ExitProcess PROTO, ; Прототип функции ExitProcess.
x: DWORD

.DATA ; Задаем сегмент данных.


sum DW ? ; Объявляем переменную sum, не присваивая значения.

.CODE ; Задаем сегмент кодов.


main PROC ; Точка входа в программу.
MOV EAX,5 ; Поместить в регистр AX значение 5.
ADD EAX,10 ; Сложить содержимое регистра EAX с 10.
ADD EAX,15 ; Сложить содержимое регистра EAX с 15.
MOV sum, AX ; Сохранить содержимое регистра EAX в sum.
invoke WaitMsg ; Запрос для нажатия клавиши.
invoke ExitProcess,0 ; Корректное окончание программы.
main ENDP ; Конец процедуры.
END main ; Конец программы (для компилятора).

Каждая строка в процедуре main этой програм


Память мы начинается с кода команды (MOV, ADD)
MOV EAX, 5 EAX = 5 011C
011E
с соответствующими операндами или вызова про
ADD EAX, 10 EAX = 15
35 0120 цедуры (макроопределение invoke). Если после
ADD EAX, 15 EAX = 35 0122 этого поставить точку с запятой, то до конца строки
0124 все будет восприниматься как комментарий, не
0126 влияющий на ход выполнения программы.
Рис. 3.15. Диаграмма выполнения Пошаговая диаграмма выполнения показана на
простой программы рис. 3.15.

90 Глава 3. Введение в язык ассемблера

Стр. 90
отсутствует. 91

Команда MOV заставляет процессор переместить или скопировать значение исходного


операнда в принимающий оператор, поэтому в первой строке число 5 перемещается
в регистр AX. Во второй строке происходит суммирование числа 10 и содержимого реги&
стра AX, в результате значение регистра становится равным 15. В третьей строке также
складываются содержимое регистра AX с числом 20. В регистре AX уже будет находиться
число 35. Далее это число копируется в ячейку памяти по адресу, определенному для пе&
ремененной sum. Команды в пятой и шестой строках необходимы для выхода из про&
граммы и передачи управления системе.
Как обычно, программу можно написать в текстовом редакторе и сохранить в файле
с именем sum.asm, после чего ее нужно откомпилировать с опцией /Zi, которая застав&
ляет отладчик включить в объектный модуль информацию для отладчика. Затем нужно
будет вызвать компоновщик для получения исполняемого файла. Однако поскольку эта
программа в дальнейшем будет использоваться с отладчиком, то необходимо запускать
компоновщик с опцией /DEBUG, т.е. команда вызова компоновщика будет включать оп&
цию, включающую информацию для отладчика, в исполняемый модуль:
/link /DEBUG
После получения выполняемого файла можно запустить отладчик и поработать с этой
программой, для чего необходимо сначала открыть исходный, а затем исполняемый файл
(это команда File Open Sourse File и команда File Open Executable). После чего мож&
но настроить рабочий стол отладчика, как показано на рис. 3.16.

Рис. 3.16. Рабочий стол отладчика dbg_x86_6.5.3.8

На рисунке вы видите открытым окно с исходной программой и дополнительные окна


Registers (значения всех регистров), Disassembly (восстановленная последовательность
команд) и Command (окно ввода команд). Все окна можно закрывать и открывать по желанию

Глава 3. Введение в язык ассемблера 91

Стр. 91
пользователя, создавая удобную для себя среду отладки. Все окна по умолчанию стараются
пристыковаться к ближайшей границе (dock) окна, хотя их можно сделать и плавающими
(floating). Потребуется некоторая практика, чтобы быстро создавать удобный рабочий стол от&
ладчика. Щелчок правой кнопкой мыши на заголовке окна приводит к появлению контекст&
ного меню, которое позволяет установить необходимый режим работы окна.
Окна можно открывать либо воспользовавшись пунктом меню Window, либо с помо&
щью отдельных кнопок, расположенных на управляющей панели. Названия кнопок, ко&
торые появляются в момент задержки курсора на кнопке, перечислены в табл. 3.16.

Таблица 3.16. Названия управляющих кнопок


Название Быстрые клавиши Описание

Open sourse file Ctrl&O Открыть файл


Cut Ctrl&X Удалить выделенный текст
Copy Ctrl&C Копировать выделенный текст
Paste Ctrl&V Вставить выделенный текст
Go F5 Выполнить программу
Restart Ctrl&Shft&F5 Подготовить отладчик к выполнению программы
Stop debugging Shft&F5 Закончить отладку
Break Ctrl&Break Передача управления отладчику
Step into F11 (F8) Заходить в процедуры
Step over F10 Выполнять программу без захода в процедуры
Step out Shft&F11 Выйти из процедуры
Run to cursor Ctrl&F10 (F11) Выполнить до курсора
Insert or remove breakpoint F9 Установить или убрать точку останова
Command Alt&1 Отобразить окно команд
Watch Alt&2 Показать текущие переменные
Locals Alt&3 Показать локальные переменные
Registers Alt&4 Показать регистры
Memory window Alt&5 Показать фрагмент памяти
Call stack Alt&6 Показать текущую информацию из стека
Disassembly Alt&7 Дисассемблировать исходный файл
Scratch pad Alt&8 Открыть окно буфера обмена
Sourse mode on Использовать исходный текст
Sourse mode off Использовать ассемблерный текст
Font Использовать шрифты
Options Использовать настройки

Работа с отладчиком не представляет особых трудностей. Необходимо только ввести


исходный файл (.asm), соответствующий ему исполняемый файл (.exe) и выполнить
дисассемблирование. Затем установить точку прерывания на процедуре main и выпол&
нить команду Go. Отладчик остановится на первой команде, после чего можно проводить
отладку, просматривая состояние всех регистров, флагов, памяти и текущих команд,

92 Глава 3. Введение в язык ассемблера

Стр. 92
отсутствует. 93

выполняя команды Step into, Step over, Run to cursor и другие. Результаты, получаемые
в процессе работы программы, будут отображаться в соответствующем консольном окне.
В окнах отладчика можно видеть как команды ассемблера, так и коды машинных ко&
манд. Все это не только значительно помогает в отладке программы, но и удобно при изу&
чении языка ассемблера, а также в процессе исследования операционной системы и аппа&
ратной части компьютера. Фрагмент дисассемблированного текста показан в табл. 3.17.

Таблица 3.17. Фрагмент дисассемблированного текста


Ячейка памяти Машинный код Команда

Sum!main:
00401038 b805000000 mov eax,0x5
0040103d 83c00a add eax,0xa
00401040 83c00f add eax,0xf
00401043 66a300404000 mov [Sum!sum (00404000)],ax
00401049 e8cbffffff call Sum!ILT+20(_WaitMsg (00401019)
0040104e 6a00 push 0x0
00401050 e8b7000000 call Sum!ExitProcess (0040110c)

В дальнейшем по мере необходимости будут объясняться команды отладчика и опи&


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

Резюме
В этой главе даны общие сведения о языке ассемблера, используемых форматах дан&
ных и принципах разработки программ на языке ассемблера. Описана работа с ассембле&
ром и основные этапы разработки программ с использованием ассемблера. Акцент сде&
лан на разработке библиотек, использование которых значительно облегчает работу
с программой. Отмечена важность этапа отладки программ и приведены базовые сведе&
ния о работе с отладчиком. Для лучшего понимания материала вниманию читателя пред&
ставлены простейшие примеры разработки и отладки программ.
В данной главе приводились примеры довольно длинных последовательностей ко&
манд, которые необходимы для ассемблирования исходного файла. Конечно, команды
можно набирать вручную, но лучше использовать командные файлы, которые позволяют
значительно облегчить работу. Существенно сэкономить время позволит использование
специального интерфейса пользователя (который можно найти на специальных сайтах
или разработать самому, если вы знаете язык высоко уровня). Такой интерфейс может
создавать необходимые последовательности команд при нажатии определенных кнопок
и запускать их на выполнение.
Четкое представление последовательности шагов, выполняемых ассемблером при об&
работке исходной программы и получении исполняемого файла, значительно облегчит
вашу работу в дальнейшем.

Глава 3. Введение в язык ассемблера 93

Стр. 93
Контрольные вопросы
1. Существует ли взаимно однозначное соответствие между командами языка ас&
семблера и машинными кодами?
2. Можно ли написать программу в машинных кодах?
3. Сколько бит находится в байте, слове, двойном и учетверенном слове?
4. Подсчитайте диапазон значений для слова без знака.
5. Что такое команды и что такое данные?
6. Что такое основание системы счисления?
7. Почему удобно использовать шестнадцатеричную систему счисления для отобра&
жения данных?
8. Как представлены в памяти числа без знака и со знаком?
9. Что такое дополнение до двух?
10. Как сохраняется в памяти строка символов? Как подсчитать размер занимаемой
памяти для отдельной строки?
11. Как сохраняется в памяти числовое значение? Как подсчитать размер занимаемой
памяти для числового значения?
12. Что такое константа? Чем константа отличается от переменной?
13. Какие типы утверждений используются в языке ассемблера?
14. Как разрабатывается программа на языке ассемблера?
15. Назовите основные этапы выполнения программы.
16. Назовите основные опции компилятора.
17. Для чего нужен отладчик?
18. Каковы особенности работы DOS в операционной системе Windows NT?
19. Можно ли использовать все возможности языка ассемблера при работе в DOS под
управлением Windows NT?
20. Чем отличаются ассемблеры Microsoft и Borland?

94 Глава 3. Введение в язык ассемблера

Стр. 94
Глава 4

Программирование
для Windows
В этой главе...

Программирование для консоли


Создание библиотеки загрузочных модулей
Работа с файлами
Управление окном
Функции времени и даты
Резюме
Контрольные вопросы

В этой главе рассмотрен процесс создания 32&разрядных приложений для работы только
с консолью. Разработку графических окон в стиле Windows вы изучите после того, как
ознакомитесь с основными командами и синтаксисом ассемблера. В этой главе также бу&
дут представлены некоторые функции операционной системы Windows и приведены от&
дельные примеры. Для более полного знакомства с библиотеками функций Windows
можно установить Microsoft Visual Studio, Delphi или Borland C++ и использовать соот&
ветствующую документацию. Справочную информацию по Windows вы найдете на сайте
www.msdn.microsoft.com.

Программирование для консоли


На первый взгляд 32&разрядные приложения, созданные для работы с консолью Windows,
ведут себя точно так, как и 16&разрядные программы для MS&DOS в текстовом режиме.
В обоих случаях можно делать переадресацию с помощью командной строки и в обоих
случаях можно отображать цветной текст. Однако между этими программами существуют
значительные различия и основным из них является то, что 32&разрядные приложения
запускаются в защищенном режиме (Protected mode), тогда как программы для MS&DOS
работают в режиме реальных адресов (Real&address mode). Поэтому используются совер&
шенно различные библиотеки. 32&разрядные приложения работают с теми же библиоте&
ками функций, которые используют и графические приложения Windows, а программы
для MS&DOS используют BIOS и прерывания MS&DOS, которые существуют со дня по&
явления первых персональных компьютеров IBM&PC.

Стр. 95
Под термином интерфейс прикладного программирования (API &&&& Application Programming
Interface) понимается набор типов, констант и функций, которые позволяют непосредст&
венно управлять объектами из программы. В дальнейшем мы будем использовать интер&
фейс Win32 API, который обеспечивает доступ к 32&разрядным версиям объектов плат&
формы MS&Windows.
С Win32 API непосредственно связан набор инструментальных средств разработки
платформы Microsoft (SDK &&&& Software Development Kit), содержащий прикладные про&
граммы, библиотеки, примеры кода и документацию, которые помогают программисту
создавать программы. Слово платформа означает операционную систему или группу тес&
но взаимосвязанных операционных систем.
При запуске приложения для Windows оно создает или консольное окно (консоль),
или графическое окно.
Для того чтобы получить консольное приложение, работающее в защищенном режи&
ме, необходимо при вызове компоновщика использовать опцию
/SUBSYSTEM:CONSOLE.
Программа для консоли ведет себя точно так, как и программа для MS&DOS, за ис&
ключением небольших улучшений. Чтение и запись происходят в стандартные входы
и выходы. Ошибки также записываются в стандартный файл ошибок. Консоль имеет
один входной буфер и один или несколько экранных буферов.
Входной буфер содержит последовательность входных записей, каждая из которых
включает данные о входных событиях (например, нажатие клавиш, щелчок мыши
или изменение размеров окна).
Экранный буфер содержит двумерный массив символов и данных о цвете, кото&
рые необходимы для отображения символов на экране.

Набор символов и функции Windows API


При вызове функций Win32 API можно использовать один из двух наборов символов. Это
набор 8&разрядных символов ASCII/ANSI и набор 16&разрядных символов Unicode (доступны
в Windows NT, 2000 и XP). Функции Windows API, работающие с текстом, обычно имеют две
версии: одна для работы с 8&разрядными символами ASCII/ANSI (имена оканчиваются сим&
волом A), а другая для работы с расширенным набором символов, включая Unicode (имена
оканчиваются символом W). Например, для отображения информации на консоли используют&
ся следующие функции:
WriteConsoleA
WriteConsoleW
Имена функций, оканчивающихся на W, не поддерживаются в Windows 95 или 98. На&
против, в Windows NT, 2000 и XP набор символов Unicode является встроенным для этих
систем. Поэтому когда вызывается такая функция, как WriteConsoleA, операционная
система сначала преобразовывает символы из набора ANSI в Unicode, а затем вызывает
функцию WriteConsoleW.

Уровни доступа
Можно использовать два уровня доступа: высокий и низкий, для простоты доступа
или полноты контроля соответственно.
Функции высокого уровня считывают поток символов из входного буфера консо&
ли. Выходные данные записываются в экранный буфер консоли. Как вход, так и вы&
ход может быть перенаправлен для чтения или записи в текстовый файл.

96 Глава 4. Программирование для Windows

Стр. 96
отсутствует. 97

Функции низкого уровня извлекают детальную информацию о нажатии клавиш


и событиях мыши, о работе пользователя с окном консоли (перетаскивание, изме
нение размеров и т.д.) Эти функции следят как за положением и размерами ок
на, так и за цветами.

Типы данных Windows


Функции Win32 API основаны на синтаксисе, используемом для языков C или C++.
В их объявлении типы всех параметров основаны на стандартных типах C или на предо
пределенных типах MSWindows, как показано в табл. 4.1. Необходимо отличать данные
типа ‘‘значение’’ от данных типа ‘‘указатель’’. Типы, имена которых начинаются с букв ‘‘LP’’,
являются дальними указателями на объекты.

Таблица 4.1. Соответствие типов MSWindows и MASM


Типы MS-Windows Типы MASM Описание
BOOL BYTE Булево значение
BSTR PTR BYTE 32разрядный указатель на символы
BYTE BYTE 8разрядное целое число без знака
COLORREF DWORD 32разрядное число, используемое как характеристика цвета
DWORD DWORD 32разрядное целое число без знака или адрес сегмента и
связанное с ним смещение
HANDLE DWORD 32разрядное целое число без знака
LONG SDWORD 32разрядное целое число со знаком
LPARAM DWORD 32разрядное значение, передаваемое как аргумент в оконные
процедуры или функции обратного вызова (может быть указателем)
LPCSTR PTR BYTE 32разрядный указатель на константу типа ‘‘символьная строка’’
LPSTR PTR BYTE 32разрядный указатель на символьную строку
LPCTSTR DWORD 32разрядный указатель на константу типа ‘‘символьная строка’’,
которая составлена из символов Unicode или набора двухбайтовых
символов
LPTSTR DWORD 32разрядный указатель на символьную строку, которая составлена
из символов Unicode или набора двухбайтовых символов
LPVOID DWORD 32разрядный указатель на неопределенный тип
LRESULT DWORD 32разрядное значение, возвращаемое оконной процедурой
или функцией обратного вызова
UINT DWORD 32разрядное целое число без знака
WNDPROC DWORD 32разрядный указатель на оконную процедуру
WORD WORD 16разрядное целое число без знака
WPARAM DWORD 32разрядное значение, передаваемое как аргумент в оконные
процедуры или функции обратного вызова
LPCRECT PTR RECT 32разрядный указатель на немодифицируемую структуру RECT
(константу)

Глава 4. Программирование для Windows 97

Стр. 97
Дескрипторы консоли
Почти все функции для работы с консолью требуют передачи дескриптора в качестве
первого аргумента. Дескриптор является 32&разрядным целым числом без знака, которое
уникально идентифицирует объект, такой как растровое изображение (bitmap) или чер&
тежное перо, либо входные и выходные устройства. В дальнейшем в главе по мере изло&
жения материала будут использоваться дескрипторы для следующих устройств:
STD_INPUT_HANDLE &&&& стандартный вход;
STD_OUTPUT_HANDLE &&&& стандартный выход;
STD_ERROR_HANDLE &&&& стандартный выход ошибок.
Будут также представлены дескрипторы для записи в экранные буферы.
Функция GetStdHandle возвращает дескриптор потока для консоли: вход, выход
или выход ошибок. Дескриптор необходим для создания входа&выхода в программах, ра&
ботающих с консолью. Ниже показан прототип функции.
GetStdHandle PROTO, nStdHandle:DWORD
Функция GetStdHandle может принимать значения STD_INPUT_HANDLE,
STD_OUTPUT_HANDLE или STD_ERROR_HANDLE. Эти константы описаны в стандартных
заголовочных файлах и имеют значения &&10, &&11 и &&12 соответственно. Функция возвра&
щает значение дескриптора в регистр EAX, которое должно быть сохранено для дальней&
шего использования. Ниже показан небольшой пример.
.DATA
inputHandle DWORD ?
.CODE
INVOKE GetStdHandle, STD_INPUT_HANDLE
MOV inputHandle,EAX
Поскольку константа STD_INPUT_HANDLE определяет устройство, для которого
в Windows используется численное значение –10, то в данном случае вместо идентифика&
тора STD_INPUT_HANDLE можно подставить число –10. Однако использование понятного
идентификатора значительно облегчает работу и освобождает от запоминания абсолютно
не информативных чисел. К тому же идентификатору STD_INPUT_HANDLE где&то должно
быть присвоено значение –10. Обычно это делается в заголовочных (подключаемых)
файлах, которые имеют расширение .inc и которые должны быть подключены к вашей
библиотеке или программе с помощью ключевого слова INCLUDE. Например, так:
INCLUDE G:/Masm/MyLib.inc
А в самом подключаемом файле должна находиться следующая строка
STD_INPUT_HANDLE = -10
или
STD_INTPUT_HANDLE EQU -11
Поэтому вы должны создать собственный заголовочный файл, в котором будете опре&
делять часто используемые константы, и подставлять его в свои программы. Такие про&
граммы будут более читабельны, поскольку в них будет отсутствовать информация, не
имеющая отношения к логической сути программы. А компилятор просто подставит все,
что находится в этом файле, вместо строки со словом INCLUDE.

98 Глава 4. Программирование для Windows

Стр. 98
отсутствует. 99

Функции консоли Win32


В табл. 4.2 приводится краткое описание всех функций для работы с консолью Win32.
Полные описания можно найти в библиотеке MSDN.

Таблица 4.2. Функции консоли Win32


Функция Описание
AllocConsole Создание новой консоли
CreateConsoleScreenBuffer Создание экранного буфера консоли
FillConsoleOutputAttribute Установка атрибутов текста и фона для указанных символов
FillConsoleOutputCharacter Запись символа в экранный буфер заданное число раз
FlushConsoleInputBuffer Выводит все содержимое буфера консоли
FreeConsole Отключает консоль от вызвавшего ее процесса
GenerateConsoleCtrlEvent Посылает специальный сигнал в процессы, связанные с консолью,
для разрешения доступа дополнительно к вызвавшему процессу
GetConsoleCP Извлекает входную кодовую страницу, используемую консолью
вызвавшего ее процесса
GetConsoleCursorlnfo Извлекает информацию о размере и видимости курсора для
указанного экранного буфера консоли
GetConsoleMode Извлекает информацию о текущем режиме ввода буфера
консоли или о режиме вывода для экранного буфера консоли
GetConsoleOutputCP Извлекает выходную кодовую страницу, используемую
консолью вызвавшего ее процесса
GetConsoleScreenBufferlnfo Извлекает информацию об указанном экранном буфере консоли
GetConsoleTitle Извлекает строку заголовка окна для текущего окна консоли
GetConsoleWindow Извлекает дескриптор окна консоли
GetLargestConsoleWindowSize Извлекает наибольший допустимый размер окна консоли
GetNumberOfConsolelnputEvents Извлекает число непрочитанных входных записей во входной
буфер консоли
GetNumberOfConsoleMouseButtons Возвращает число кнопок мыши, используемых с консолью
GetStdHandle Возвращает дескрипторы для стандартного входа, стандартного
выхода или стандартного выхода ошибок
HandlerRoutine Объявленная в приложении функция, используемая с функцией
SetConsoleCtrlHandler
PeekConsoleInput Считывает данные из входного буфера консоли без удаления
данных из буфера
ReadConsole Считывает данные из входного буфера консоли и удаляет
данные из буфера
ReadConsoleInput Считывает данные из входного буфера консоли и удаляет
данные из буфера
ReadConsoleOutput Считывает символы и атрибуты цвета из указанного
прямоугольного блока в экранном буфере консоли
ReadConsoleOutputAttribute Копирует указанное число последовательных цветовых
атрибутов для текста и фона из экранного буфера консоли

Глава 4. Программирование для Windows 99

Стр. 99
Окончание табл. 4.2
Функция Описание
ReadConsoleOutputCharacter Копирует указанное число последовательных символов
из экранного буфера консоли
ScrollConsoleScreenBuffer Перемещает блок данных в экранный буфер
SetConsoleActiveScrecnBuffer Устанавливает указанный экранный буфер как текущий
экранный буфер консоли
SetConsoleCP Устанавливает входную кодовую страницу, используемую
консолью для вызвавшего процесса
SetConsoleCtrlHandler Добавляет или удаляет определенные дескрипторы в приложении
HandlerRoutine из списка обработчиков функций для
вызываемого процесса
SetConsoleCursorlnfo Устанавливает размер и видимость курсора для экранного
буфера консоли
SetConsoleCursorPosition Устанавливает позицию курсора в экранном буфере консоли
SetConsoleMode Устанавливает режим ввода для входного буфера консоли
или режим вывода для экранного буфера консоли
SetConsoleOutputCP Устанавливает используемую консолью кодовую страницу
для вывода, связанную с вызываемым процессом
SetConsoleScreenBufferSize Изменяет размер экранного буфера консоли
SetConsoleTextAttribute Устанавливает цветовые атрибуты текста и фона символов,
записываемых в экранный буфер консоли
SetConsoleTitle Устанавливает заголовок текущего окна консоли
SctConsoleWindowInfo Устанавливает текущий размер и положение окна для
экранного буфера консоли
SetStdHandle Устанавливает дескриптор для стандартного входа,
стандартного выхода и стандартного выхода ошибок
WriteConsole Записывает символьную строку в экранный буфер консоли,
начиная с текущего положения курсора
WriteConsoleInput Записывает данные непосредственно в выходной буфер
консоли
WriteConsoleOutput Записывает символ и цветовые атрибуты в указанный
прямоугольный блок в экранном буфере консоли
WriteConsoleOutputAttribute Копирует несколько цветовых атрибутов для текста и фона
в последовательные ячейки экранного буфера консоли
WriteConsoleOutputCharacter Копирует несколько символов в последовательные ячейки
экранного буфера консоли

Использование функций
Для того чтобы лучше понять, как пишутся программы для платформы Win32, подробно
рассмотрим небольшой пример вывода на консоль текста. Ниже приведен полный листинг
программы, который можно ассемблировать, скомпоновать и выполнить. Номера строк
в программу не входят и добавлены для дальнейших ссылок из текста на отдельные строки.

100 Глава 4. Программирование для Windows

Стр. 100
отсутствует. 101

Листинг 4.1. Вывод на консоль


1 TITLE Вывод на консоль (Console.asm)
2 ; В программе используются следующие функции Win32:
3 ; GetStdHandle, ExitProcess, WriteConsole
4 .386
5 .MODEL flat, stdcall
6 STD_OUTPUT_HANDLE EQU -11
7 EXTERN GetStdHandle@4:NEAR
8 EXTERN ExitProcess@4:NEAR
9 EXTERN WriteConsoleA@20:NEAR
10 .DATA
11 endl EQU <0dh,0ah> ; Задаем идентификатор для конца строки.
12 message BYTE "---------------- Console.asm -----------------"
13 BYTE endl,endl
14 BYTE "Программа демонстрирует вывод на консоль сообщения,",endl
15 BYTE "которое вы сейчас видите. Использованы функции",endl
16 BYTE "Windows GetStdHandle и WriteConsole ",endl
17 BYTE "------------------------------------------------"
18 BYTE endl,endl,endl
19 messageSize = ($-message) ; Подсчитываем длину строки.
20 consoleHandle DWORD 0 ; Дескриптор выходного устройства.
21 bytesWritten DWORD ? ; Количество записанных байт.
22 .CODE
23 main PROC
24 ; Получить дескриптор консоли.
25 PUSH STD_OUTPUT_HANDLE ; Передаем параметр в функцию.
26 CALL GetStdHandle@4 ; Вызываем функцию.
27 MOV consoleHandle,EAX ; Сохраняем дескриптор.
29
30 ; Отобразить строку на консоли.
31 PUSH 0
32 PUSH offset bytesWritten
33 PUSH messageSize
34 PUSH offset message
35 PUSH consoleHandle
36 CALL WriteConsoleA@20
37
38 ; Завершить программу.
39 PUSH 0
40 CALL ExitProcess@4
41 main ENDP
42 END main

В первой строке находится необязательная директива TITLE, которая предваряет за&


головок программы. Затем идут объявления типа процессора, для которого пишется
программа, и модель памяти. Для 32&разрядных программ модель памяти обязательно
должна иметь тип flat. Также объявлена директива stdcall, которая показывает, что
передача параметров в процедуры должна производиться в стиле языка C (операционные
системы Windows написаны на этом языке), т.е. справа налево.
В строке 6 переменная STD_OUTPUT_HANDLE становится псевдонимом (директива EQU)
числового значения –11.
Затем объявляются функции, которые будут использоваться в программе: GetStdHandle,
ExitProcess, WriteConsoleA. Функции объявлены с директивой EXTERN, указываю&
щей на то, что описание и рабочий код данных функций находятся в другом модуле, из

Глава 4. Программирование для Windows 101

Стр. 101
которого они будут браться и который должен быть объявлен при компоновке. В нашем
случае это модуль KERNEL32.LIB. Этот модуль входит в поставку ассемблера и названия
указанных функций написаны в модуле следующим образом:
_GetStdHandle@4
_WriteConsoleA@20
_ExitProcess@4
Написание функций отличается от того, что приведено в таблице функций для консо&
ли, но ассемблер автоматически приводит их к виду, используемому в библиотеке функ&
ций, которые создавались для языка C. В соответствии с соглашениями для этого языка,
системные функции должны иметь в начале имени символ подчеркивания ‘‘_’’, а в конце
должно указываться количество байт, отводимых для параметров. Именно этим объясня&
ется такое несколько странное написание, хотя можно использовать макроопределения
и писать имена функций так, как они приведены в таблице (см. далее).
После описания функций идет описание того сообщения, которое должно быть выве&
дено на экран.
message BYTE "-------------- Console1.asm --------------------"
BYTE endl,endl
BYTE "Программа демонстрирует вывод на консоль сообщения,",endl
BYTE "которое вы сейчас видите. Использованы функции",endl
BYTE "Windows GetStdHandle и WriteConsole ",endl
BYTE "---------------------------------------------------------"
Перед этим сообщением в строке 11 объявляется переменная endl, определяющая
два символа, которые приводят к переходу на новую строку и помещению курсора в на&
чало строки (возврат каретки).
После объявления данных идет непосредственно раздел кодов, в котором функции
вызываются с помощью директивы CALL. При этом перед каждым вызовом происходит
помещение необходимых аргументов в стек в порядке справа налево, приведенном в опи&
сании функции. Например, функция WriteConsoleA представлена в справочнике сле&
дующим образом.
BOOL WriteConsole(
HANDLE hConsoleOutput, // Дескриптор экранного буфера консоли.
CONST VOID *lpBuffer, // Указатель на исходный текст.
DWORD nNumberOfCharsToWrite, // Количество символов для записи.
LPDWORD lpNumberOfCharsWritten, // Указатель на количество
// записанных символов.
LPVOID lpReserved // Зарезервировано.
);
Как видно из листинга 4.1, для данной функции сначала в стек помещается 0, затем
указатель на количество записанных символов и т.д.
Теперь запишем ту же программу с использованием макроопределений. Выглядеть
она будет так (листинг 4.2).

Листинг 4.2. Вывод на консоль Win32


TITLE Вывод на консоль Win32 (Console1.asm)
; В этой программе используются функции консоли Win32:
; GetStdHandle, ExitProcess, WriteConsole
.386
.MODEL flat, stdcall
INCLUDE E:\MASM615\INCLUDE\MyLib.inc

102 Глава 4. Программирование для Windows

Стр. 102
отсутствует. 103

.DATA
endl EQU <0dh,0ah> ; Конец строки.
message \
BYTE "-------------------- Console1.asm -----------------------"
BYTE endl,endl
BYTE "Программа демонстрирует вывод на консоль сообщения,",endl
BYTE "которое вы видите. Использованы функции Windows",endl
BYTE "GetStdHandle и WriteConsole ",endl
BYTE "---------------------------------------------------------"
BYTE endl,endl,endl
messageSize = ($-message)
consoleHandle DWORD 0 ; Дескриптор стандартного выходного устройства.
bytesWritten DWORD ? ; Количество записанных байт.
.CODE
main PROC
; Получить дескриптор стандартного выходного устройства.
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV consoleHandle,EAX

; Отобразить строку на консоли.


INVOKE WriteConsoleA,
consoleHandle,
ADDR message,
messageSize,
ADDR bytesWritten,
0

; Закончить выполнение.
INVOKE ExitProcess,0
main ENDP
END main

Эта программа делает абсолютно то же самое, что и предыдущая, но выглядит несколько


по&другому и более читабельна. Это сделано за счет того, что используются прототипы
функций, для описания которых создан специальный подключаемый файл MyLib.inc
(листинг 4.3).

Листинг 4.3. Подключаемый файл для функций Windows API


.NOLIST ; Не включать в листинг.
; Подключаемый файл для функций Windows API (MyLib.inc)
STD_OUTPUT_HANDLE EQU -11 ; Константа устройства вывода

ExitProcess PROTO, ; Окончание программы.


dwExitCode:DWORD ; Код возврата.

GetStdHandle PROTO, ; Получение стандартного дескриптора.


nStdHandle:DWORD ; Тип дескриптора.

WriteConsoleA PROTO, ; Запись в буфер консоли.


handle:DWORD, ; Дескриптор выходного устройства.
lpBuffer:PTR BYTE, ; Указатель на буфер.
nNumberOfBytesToWrite:DWORD, ; Размер буфера.
lpNumberOfBytesWritten:PTR DWORD,; Количество записанных байт
lpReserved:DWORD ; Зарезервировано.
.LIST

Глава 4. Программирование для Windows 103

Стр. 103
Этот файл подключается к основной программе с помощью директивы INCLUDE.
INCLUDE E:\MASM615\INCLUDE\MyLib.inc
В таком варианте уже не надо искажать оригинальные имена функций &&&& все это
будет сделано автоматически. Вызов функций производится с помощью директивы
INVOKE. В справочном отделе приведены полная библиотека необходимых функций
и заголовочные файлы.
Дальше рассмотрим, как можно произвести ввод информации с консоли.

Ввод с консоли
Консоль Win32 имеет входной буфер, содержащий массив входных записей. Каждое
событие, такое как нажатие клавиши, перемещение мыши или щелчок кнопкой мыши,
создает запись во входном буфере консоли. Функции высокого уровня ReadConsole
фильтруют и обрабатывают входные данные, возвращая только поток входных символов.
Функция ReadConsole
Функция ReadConsole используется для считывания текстовых данных и помеще&
ния их в буфер. Ниже приведен прототип функции.
ReadConsole PROTO,
handle:DWORD, ; Дескриптор входа.
pBuffer:PTR BYTE, ; Указатель на буфер.
maxBytes:DWORD, ; Количество символов для чтения.
pBytesRead:PTR DWORD, ; Указатель на считанные символы.
notUsed:DWORD ; Не используется.
Параметр handle &&&& это дескриптор, возвращаемый функцией GetStdHandle. Па&
раметр pBuffer представляет адрес символьного массива. Параметр maxBytes является
32&разрядным целым числом и указывает максимальное число символов, которые долж&
ны быть считаны. Параметр pBytesRead указывает на двойное слово, которое означает
число считанных символов. Последний параметр не используется и его можно заменять
любым числом, например нулем.
Ниже приведен пример программы для считывания данных с консоли.

Листинг 4.4. Считывание с консоли


TITLE Считывание с консоли (ReadConsole.asm)
; Считывание введенной строки символов из стандартного входа.
.386
.MODEL flat, stdcall
INCLUDE E:\MASM615\INCLUDE\MyLib.inc
BufSize = 80
.DATA
buffer BYTE BufSize DUP(?),0,0
consoleHandle DWORD 0 ; Дескриптор стандартного выходного устройства.
stdInHandle DWORD ? ; Дескриптор стандартного входного устройства.
bytesRead DWORD ?
.CODE
main PROC
; Получить дескриптор стандартного входного устройства.
INVOKE GetStdHandle, STD_INPUT_HANDLE
MOV stdInHandle,EAX

; Получить дескриптор стандартного выходного устройства.


INVOKE GetStdHandle, STD_OUTPUT_HANDLE

104 Глава 4. Программирование для Windows

Стр. 104
отсутствует. 105

MOV consoleHandle,EAX

; Ожидать ввода пользователя.


INVOKE ReadConsole, stdInHandle, ADDR buffer, BufSize-2,
ADDR bytesRead, 0

; Отобразить буфер.
INVOKE WriteConsoleA, consoleHandle, ADDR buffer, BufSize-2,
ADDR bytesRead, 0

INVOKE Crlf
INVOKE WaitMsg
EXIT
main ENDP
END main

В этой программе есть некоторые изменения относительно более ранних программ.


Например, выход из программы осуществляется с помощью команды EXIT, а не с помо&
щью директивы INVOKE ExitProcess,0. Чтобы программа была более четко и понятно
оформлена, выбрана команда EXIT &&&& синоним соответствующей директивы, т.е. в под&
ключаемый файл MyLib.inc добавлена следующая строка:
EXIT EQU <INVOKE ExitProcess,0> ; Выход из программы
А функция ReadConsole является синонимом функции ReadConsoleA, для чего
написана следующая строка:
ReadConsole EQU <ReadConsoleA>

Ввод одиночного символа


Для ввода одиночного символа придется применить искусственный прием и сделать
следующее.
Получить копию текущих флагов консоли, вызвав функцию GetConsoleMode,
и сохранить флаги в переменной.
Изменить флаги консоли с помощью функции SetConsoleMode.
Ввести символ, используя функцию ReadConsole.
Восстановить предыдущее значение флагов консоли с помощью функции Set-
ConsoleMode.
Функция GetConsoleMode возвращает текущее значение флагов для входного буфе&
ра консоли или режима выхода для экранного буфера консоли и копирует флаги в пере&
менную размером в двойное слово.
GetConsoIeMode PROTO,
hConsoleHandle:DWORD, ; Дескриптор выхода или входа.
ipMode:PTR DWORD ; Указывает на переменную DWORD.
Функция SetConsoleMode устанавливает значения флагов для входного буфера
консоли или режима выхода для экранного буфера консоли.
SetConsoleMode PROTO,
hConsoleHandle:DWORD, ; Дескриптор консоли.
dwMode:DWORD ; Флаги режима консоли.
Набор значений для параметра dwMode описан в документации Microsoft MSDN Online
Library. В нашем случае только отметим, что при вводе одиночного символа необходима ус&

Глава 4. Программирование для Windows 105

Стр. 105
тановка всех флагов в нуль, как показано в примере ниже, где выполняется ввод одиноч&
ного символа.
.DATA
saveFlags DWORD ? ; Копия флагов.
.CODE

; Получить и сохранить флаги текущего режима консоли.


INVOKE GetConsoleMode,
consolelnHandle,
ADDR saveFlags

; Сбросить все флаги.


INVOKE SetConsoleMode,
consolelnHandle,
0 ; Новые значения флагов.

; Считать символ с консоли.


INVOKE ReadConsole,
consolelnHandle, ; Дескриптор ввода консоли.
ADDR buffer, ; Указатель на буфер.
1, ; Максимальное количество символов для чтения.
ADDR bytesRead,
0 ; Возвращаемое значение.

; Восстановить предыдущие значения флагов.


INVOKE SetConsoleMode,
consolelnHandle,
saveFlags

Структуры данных
Некоторые функции консоли Win32 используют предопределенные структуры дан&
ных, такие как COORD и SMALL_RECT. Структура COORD содержит координаты экрана X и Y,
измеряемые в символах. Соответственно они могут принимать значения от 0 до 79 и от 0 до 24.
COORD STRUCT
X WORD ?
Y WORD ?
COORD ENDS

Структура SMALL_RECT содержит координаты окна, также измеренные в символах.


SMALL_RECT STRUCT
Left WORD ?
Top WORD ?
Right WORD ?
Bottom WORD ?
SMALL_RECT ENDS

Функция WriteConsoleOutputCharacter
Эта функция, прототип которой представлен ниже, копирует массив символов в по&
следовательные ячейки экранного буфера консоли, начиная с указанного места.
WriteConsoleOutputCharacter PROTO,
handleScreenBuf:DWORD, ; Дескриптор консоли.
pBuffer:PTR BYTE, ; Указатель на буфер.

106 Глава 4. Программирование для Windows

Стр. 106
отсутствует. 107

bufsize:DWORD, ; Размер буфера.


xyPos:COORD, ; Координаты первой ячейки.
pCount:PTR DWORD ; Счетчик.
Когда текст достигает конца строки, выполняется перенос на следующую строку.
Значения атрибутов экранного буфера не изменяются. Если функция не может записать
символ, возвращается нуль. Управляющие коды ASCII, такие как табуляция, возврат ка&
ретки, пропуск строки, игнорируются.

Создание библиотеки загрузочных модулей


Если в процессе работы над программой создано несколько полезных и часто исполь&
зуемых процедур, то хорошим решением будет создание библиотеки загрузочных моду&
лей. При этом отдельный исходный файл (файл с расширением .asm) должен быть соз&
дан для каждого модуля. Формат файла должен быть таким.
TITLE описание процедуры (имя файла)
.MODEL flat
PUBLIC перечень идентификаторов
объявление констант
.DATA
переменные
.CODE
процедуры
END
Все переменные, константы или процедуры, объявленные внутри модуля, являются
закрытыми по умолчанию. Такая концепция называется инкапсуляцией. Процедуры из
других программных модулей не могут получить доступ к членам этого модуля. Это весь&
ма удобно, поскольку позволяет использовать одинаковые имена и идентификаторы
в разных модулях, таким образом закрытые переменные оказываются защищены от не&
желательного воздействия из&за пределов модуля. Интерфейс модуля полностью состоит из
идентификаторов, которые были объявлены как public и доступны для внешних модулей.
Оба основных поставщика ассемблеров, Microsoft и Borland, включают в комплект постав&
ки утилиты, которые позволяют создавать загрузочные библиотеки, добавлять модули, уда&
лять их и выводить список всех процедур, входящих в состав библиотеки. Список всех команд
для утилит от Microsoft или Borland легко получить с помощью команды /help, набранной
в командной строке. В листингах ниже показаны команды утилиты LIB для ассемблера
MASM. Например, следующие команды создадут файл библиотеки с именем gWin.lib.
LIB gWin

Утилита LIB, входящая в состав ассемблера MASM615, работает только с 16&разрядными


программами. Необходимо использовать утилиту, входящую в состав компиляторов C++
или Microsoft Visual Studio.
Если файл библиотеки создан, в него можно добавлять отдельные объектные файлы
(модули). Например, можно добавить файл WriteString.obj в файл gWin.lib.
LIB gWin + WriteString
Для того чтобы увидеть перечень всех программ библиотеки, наберите имя библиоте&
ки и используйте CON вместо имени файла. Например:
LIB string,con
Для получения перечня опций введите команду lib /?. На экране будут отображены
все доступные опции, как в листинге 4.5.

Глава 4. Программирование для Windows 107

Стр. 107
Листинг 4.5. Вывод опций для Microsoft LIB
Microsoft (R) Library Manager Version 3.20.010
Copyright (C) Microsoft Corp 1983-1992. All rights reserved.
Usage: LIB library [options][commands][.listfile[.newlibrary]]
Options:
/? ; отобразить опции LIB
/HELP ; отобразить HELP для LIB
/IGNORECASE ; игнорировать регистр в именах
/NOEXTDICTIONARY ; не создавать расширенный словарь
/NOIGNORECASE ; не игнорировать регистр в именах
/NOLOGO ; не отображать поле заголовка
/PAGESIZE:n ; установить размер страницы n
Commands:
+name ; добавить объектный файл
-name ; стереть объектный файл
-+name ; заменить объектный файл
*name ; скопировать объектный файл
-*name ; переместить объектный файл

Если ввести команду lib /help, то появится окно с перечнем всех доступных спра&
вочных данных, как показано на рис. 4.1.

Рис. 4.1. Окно справки для команды LIB

Подготовка модуля для библиотеки


Поместим в библиотеку процедуру WriteString, которая выводит строку символов
на консоль. Листинг этой процедуры будет следующий:

Листинг 4.6. Модуль WriteString


TITLE Модуль WriteString (WriteString.asm)
.386
.MODEL flat, stdcall
.STACK 4096
INCLUDE E:\MASM615\include\MyLib.inc
.DATA
STD_OUTPUT_HANDLE EQU -11 ; Предопределенная константа Win API.
consoleOutHandle DWORD 0 ; Дескриптор консоли.
bytesWritten DWORD ?
.CODE

108 Глава 4. Программирование для Windows

Стр. 108
отсутствует. 109

Str_length PROC USES edi,


pString:PTR BYTE ; Указатель на строку.
; Производит подсчет длины строки с нулевым окончанием.
; Принимает указатель на строку pString.
; Возвращает EAX = длина строки.
MOV EDI,pString
MOV EAX,0 ; Подсчет количества символов.
L1:
CMP BYTE PTR [edi],0 ; Конец строки?
JE L2 ; Да: Выход.
INC EDI ; Нет: Перейти к следующему символу.
INC EAX ; Добавить единицу в счетчик.
JMP L1
L2: RET
Str_length ENDP

WriteString PROC
; Записывает строку с нулевым окончанием в стандартный
; выход. Входной параметр EDX указывает на строку.
PUSHAD
; Получаем дескриптор консоли.
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV [consoleOutHandle],EAX
; Получаем длину строки.
INVOKE Str_length,EDX ; Возврат длины строки в EAX.
CLD ; Сброс флага направления.
; Выводим строку на консоль.
INVOKE WriteConsoleA,
consoleOutHandle, ; Дескриптор консоли.
EDX, ; Указывает на строку.
EAX, ; Длина строки.
OFFSET bytesWritten, ; Число записанных байт.
0
POPAD
RET
WriteString ENDP
END

Данный модуль включает две процедуры: процедуру для подсчета длины строки
Str_length, которая возвращает количество символов в строке, и процедуру вывода
строки на консоль WriteString, в которой используется значение, возвращаемое про&
цедурой Str_length.
Все команды, используемые в этих процедурах, будут подробно описаны в дальней&
ших главах. Изучив их, всегда можно вернуться к этим программам и понять все тонко&
сти использования команд ассемблера. А пока отметим только тот факт, что ассемблерную
программу удобно разбить на фрагменты, которые легко разрабатывать и тестировать от&
дельно от основной программы. В дальнейшем все эти фрагменты быстро состыковывают&
ся и из отдельных процедур, которые не требуют дополнительной отладки, можно собрать
большую программу. В этом случае время, затрачиваемое на разработку программы, бу&
дет минимальным.
Для того чтобы получить объектный файл, нужно ассемблировать созданный модуль
c необходимыми процедурами. Для этого можно использовать команду
..\Masm615\ML -c -coff

Глава 4. Программирование для Windows 109

Стр. 109
или написать командный файл, при условии, что ассемблер находится в каталоге
E:\Masm615, со следующей строкой:
E:\Masm615\ML -c -coff %1.asm
В данном случае ассемблер находится на диске E. На вашем компьютере все может
быть установлено по&другому, поэтому внесите соответствующие изменения.
После ассемблирования будет получен объектный файл с именем WriteString.obj,
который необходимо расположить в каталоге с библиотеками.
Теперь можно использовать этот файл в процессе компоновки модулей. При этом ес&
ли раньше командный файл выглядел следующим образом:
ML -Zi -c -Fl -coff %1.asm
if errorlevel 1 goto terminate
LINK32 %1.obj E:\MASM615\LIB\KERNEL32.LIB /SUBSYSTEM:CONSOLE /DEBUG
if errorLevel 1 goto terminate
dir %1.*
:terminate
pause
то в данном случае строку с командой LINK32 запишем так:
LINK32 %1.obj E:\MASM615\LIB\KERNEL32.LIB
Е:\MASM615\LIB\WRITESTRING.OBJ /SUBSYSTEM:CONSOLE /DEBUG
Лучше переименовать этот файл и назвать его MyLib.obj. В дальнейшем мы будем
его дополнять и создавать собственную библиотеку функций, необходимых для работы.
Например, возьмем программу вывода заранее подготовленного текста на консоль.
Без использования созданных нами процедур она будет выглядеть так:

Листинг 4.7. Вывод на консоль


TITLE Вывод на консоль (Console.asm)
.386
.MODEL flat, stdcall
INCLUDE e:/masm615/include/gwin.inc
.DATA
endl EQU <0dh,0ah> ; Конец строки.
message \
BYTE "-------------------- Console1.asm -----------------------"
BYTE endl,endl
BYTE "Эта программа производит вывод текста на ",endl
BYTE "консоль с помощью процедур GetStdHandle и WriteConsole ",endl
BYTE "---------------------------------------------------------"
BYTE endl,endl,endl
messageSize = ($-message)
consoleHandle DWORD 0 ; Дескриптор выходного устройства
bytesWritten DWORD ? ; Число записанных байт
.CODE
main PROC
; Получить дескриптор стандартного выходного устройства:
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV consoleHandle,EAX
; Вывести строку на консоль:
INVOKE WriteConsole,
consoleHandle, ; Дескриптор консоли.
ADDR message, ; Указатель на строку.
messageSize, ; Длина строки.

110 Глава 4. Программирование для Windows

Стр. 110
отсутствует. 111

ADDR bytesWritten, ; Число записанных байт.


0 ; Не используется.
INVOKE WaitMsg
INVOKE ExitProcess,0
main ENDP
END main

А если использовать заранее созданные и отлаженные процедуры вывода на консоль,


то эта же программа будет выглядеть следующим образом:

Листинг 4.8. Вывод на консоль 2


TITLE Вывод на консоль (Console1.asm)
; Использование объектного файла
.386
.MODEL flat, stdcall
INCLUDE e:/masm615/include/gwin.inc
.DATA
endl EQU <0dh,0ah> ; Конец строки.
message \
BYTE "-------------------- Console1.asm -----------------------"
BYTE endl,endl
BYTE "Эта программа выводит текст на ",endl
BYTE "консоль с помощью процедуры WriteString.",endl
BYTE "---------------------------------------------------------",0
.CODE
main PROC
MOV EDX, OFFSET message
CALL WriteString
INVOKE WaitMsg
INVOKE ExitProcess,0
main ENDP
END main

Как видно, вторая программа занимает меньше места и ее назначение понятнее, чем в пер&
вом случае. В этой программе обратите внимание на нуль, который появился в конце тек&
ста, предназначенного для вывода на консоль. Дело в том, что процедура WriteString ра&
ботает только со строками с нулевым окончанием, и подсчет количества символов
производится до появления байта с нулевым значением. Для вызова процедур здесь исполь&
зуются директивы CALL и INVOKE. Их назначение приблизительно одинаковое, но дирек&
тива INVOKE более мощная и с ее помощью в процедуры можно передавать параметры.
Теперь объектный файл WriteString.obj можно преобразовать в библиотечный файл
с расширением .lib. Но здесь возникает определенная сложность, так как утилита LIB,
которая входит в поставку ассемблера, рассчитана на работу только с 16&разрядными про&
граммами. Поэтому для того чтобы создавать библиотеки с расширением .lib для 32&раз&
рядных программ, используйте утилиту LIB из поставки Microsoft Visual Studio, кото&
рую можно найти в каталоге ...\Microsoft Visual Studio\VC98\BIN\LIB\ или
...\MSVNET\VC7\BIN\.

Глава 4. Программирование для Windows 111

Стр. 111
Работа с файлами
Функция создания файла
Функция CreateFile или создает новый файл, или открывает уже существующий.
При успешном выполнении операции возвращается дескриптор открытого файла, в про&
тивном случае возвращается константа INVALID_HANDLE_VALUE. Ниже показан прото&
тип функции.
CreateFile PROTO,
pFilename:PTR BYTE, ; Указатель на имя файла.
desiredAccess:DWORD, ; Режим доступа.
shareMode:DWORD, ; Режим совместной работы.
IpSecurity:DWORD, ; Указатель на атрибуты защиты.
creationDisposition:DWORD, ; Опции создания файла.
flagsAndAttributes:DWORD, ; Атрибуты файла.
htemplate:DWORD ; Дескриптор шаблона файла.
Первый параметр указывает на строку с нулевым окончанием, содержащую или частич&
ное, или полностью определенное имя (drive:\path\filename). Параметр desiredAccess
указывает, как файл будет использоваться (чтение или запись). Параметр shareMode
определяет возможность доступа к файлу из нескольких программ, когда файл открыт.
Параметр creationDisposition указывает, какие действия необходимо предприни&
мать, если файл уже существует или не существует. Параметр flagsAndAttributes со&
стоит из отдельных флагов, которые определяют такие атрибуты файла, как архивный, шиф&
рованный, скрытый, обычный, системный и временный. Параметр htemplate включает
дополнительный дескриптор для временного файла, который содержит атрибуты файла
и расширенные атрибуты для создания файла. Несмотря на то что этот параметр не ис&
пользуется, он должен передаваться в функцию со значением 0.

Параметр DesiredAccess
Устанавливая этот параметр, можно получить режимы только для чтения, только для
записи, для записи/чтения или для устройства последовательного доступа. В табл. 4.3
приведено краткое описание всех режимов.

Таблица 4.3. Опции для параметра DesiredAccess


Режим Описание
0 Определяет устройство последовательного доступа. Приложение может запросить
атрибуты устройства без доступа к устройству
GENERIC_READ Определяет доступ только для чтения. Данные могут считываться с файла
и указатель файла может перемещаться. Объединяется с режимом GENERIC_WRITE
при доступе чтение/запись
GENERIC_WRITE Определяет доступ только для записи. Данные могут записываться в файл и
указатель файла может перемещаться. Объединяется с режимом GENERIC_READ
при доступе чтение/запись

Параметр CreationDisposition
Это параметр действия, которое будет выполняться в зависимости от того, существует
или не существует файл с заданным именем. Параметр может иметь одно из приведенных
в табл. 4.4 значений.

112 Глава 4. Программирование для Windows

Стр. 112
отсутствует. 113

Таблица 4.4. Опции параметра CreationDisposition


Режим Описание
CREATE_NEW Создается новый файл. Задача не выполняется, если файл с указанным именем
уже существует
CREATE_ALWAYS Создается новый файл. Если файл существует, то он переписывается,
сбрасываются имеющиеся в наличии атрибуты и комбинируются атрибуты
файла и флагов, указанных в параметре с предустановленной константой
FILE_ATTRIBUTE_ARCHIVE
OPEN_EXISTING Открывается существующий файл. Задача не выполняется, если файл
с указанным именем отсутствует
OPEN_ALWAYS Открывается существующий файл. Если файл не существует, то создается файл
как при режиме CREATE_NEW
TRUNCATE_EXISTING Открывается существующий файл. После открытия размер файла становится
равным нулю. Необходимо открывать файл по крайней мере с доступом
GENERIC_WRITE. Задача не выполняется, если файл с указанным именем отсутствует

В табл. 4.5 перечислены наиболее часто используемые атрибуты для параметра


flagsAndAttributes. Допустимы любые комбинации этих атрибутов.

Таблица 4.5. Атрибуты для параметра FlagsAndAttributes


Атрибут Описание
FILE_ATTRIBUTE_ARCHIVE Файл должен быть архивным. Приложения используют этот атрибут
при сохранении или удалении файла
FILE_ATTRIBUTE_HIDDEN Файл должен быть скрытым. Он не включается в список при
просмотре каталогов
FILE_ATTRIBUTE_NORMAL Файл не имеет установленных атрибутов. Этот атрибут недопустимо
использовать совместно с другими атрибутами
FILE_ATTRIBUTE_READONLY Файл используется только для чтения. Приложение может прочитать
файл, но не может произвести в него запись или стереть его
FILE_ATTRIBUTE_TEMPORARY Файл используется как временный

Примеры работы с файлами


В следующих примерах иллюстрируются методы создания и открытия файлов.
Открытие существующего файла для чтения.
INVOKE CreateFile,
ADDR filename, ; Указатель на имя файла.
GENERIC_READ, ; Режим доступа.
DO_NOT_SHARE, ; Совместная работа.
NULL, ; Указатель на атрибуты защиты.
OPEN_EXISTING, ; Опции создания файла.
FILE_ATTRIBUTE_NORMAL, ; Атрибуты файла.
0 ; Дескриптор шаблона файла.
Открытие существующего файла для записи.
INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE, ; Режим доступа.
DO_NOT_SHARE,
NULL,

Глава 4. Программирование для Windows 113

Стр. 113
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0
Создание нового файла с нормальными атрибутами, удаление существующего файла
с указанным именем.
INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE,
DO_NOT_SHARE,
NULL,
CREATE_ALWAYS, ; Переписать существующий файл
FILE_ATTRIBUTE_NORMAL,
0
Создать файл, если файла с указанным именем еще не существует.
INVOKE CreateFile,
ADDR filename,
GENERIC_WRITE,
DO_NOT_SHARE,
NULL,
CREATE_NEW, ; Не удалять существующий файл
FILE_ATTRIBUTE_NORMAL,
0
При этом в подключаемом файле должны быть определены следующие константы:
DO_NOT_SHARE = 0
NULL = 0

Функция CloseHandle
Эта функция используется для открытия и закрытия дескриптора файла. Ее прото&
тип такой:
CloseHandle PROTO, handle:DWORD

Функция ReadFile
Эта функция считывает текст из входного файла. Функция ReadFile может исполь&
зоваться в асинхронном режиме. Это означает, что программе не нужно ожидать, когда
считывание всего файла будет завершено. Прототип функции следующий:
ReadFile PROTO, ; Считывание в буфер из входного файла.
handle:DWORD, ; Дескриптор файла.
pBuffer:PTR BYTE, ; Указатель на буфер.
nBufsize:DWORD, ; Количество байт для считывания.
pBytesRead:PTR DWORD, ; Количество реально считанных байт.
pOverlapped:PTR DWORD ; Указатель на информацию асинхронного режима.
Параметр handle является дескриптором открытого файла, возвращаемого функци&
ей CreateFile. Параметр pBuffer указывает на буфер, в который будут записываться
данные из файла. Параметр nBufsize определяет максимальное число байт для считы&
вания из файла. Параметр pBytesRead указывает на целочисленную переменную, кото&
рая содержит число реально считанных байт. Дополнительный параметр pOverlapped
указывает на структуру, которая определяет, как функция может считывать файл в асин&
хронном режиме. Для оператора синхронизации по умолчанию передается указатель на
нулевое значение.

114 Глава 4. Программирование для Windows

Стр. 114
отсутствует. 115

Функция WriteFile
Эта функция записывает данные в файл, используя дескриптор выходного устройст&
ва. Дескриптором может быть и дескриптор экранного буфера или дескриптор одного из
текстовых файлов. Функция начинает запись в файл с позиции, определяемой внутрен&
ним указателем файла. По окончании записи указатель устанавливается на позицию по&
сле последнего записанного байта. Прототип функции следующий:
WriteFile PROTO,
fileHandle:DWORD, ; Дескриптор выходного устройства.
pBuffer:PTR BYTE, ; Указатель на буфер.
nBufsize:DWORD, ; Размер буфера.
pBytesWritten:PTR DWORD, ; Число записанных байт.
pOverlapped:PTR DWORD ; Указатель на информацию асинхронного режима.

Пример записи в файл


Следующая программа создает новый файл и записывает некоторый текст в файл.
Здесь использована опция CREATE_ALWAYS, поэтому если существует файл с указанным
именем, он будет затерт.

Листинг 4.9. Использование WriteFile


TITLE Использование WriteFile (WriteFile.asm)
.386
.MODEL flat, stdcall
INCLUDE e:/masm615/include/gwin.inc
.DATA
consoleHandle DWORD 0 ; Дескриптор консоли.
buffer BYTE "Этот текст будет записан в выходной файл",0dh,0ah
bufSize = ($-buffer)
errMsg BYTE "Невозможно создать файл",0dh,0ah,0
errMsgSize = ($ - errMsg)
filename BYTE "output.txt",0
fileHandle DWORD ? ; Дескриптор выходного файла.
bytesWritten DWORD ? ; Количество записанных байт.
bytesRead DWORD ? ; Количество считанных байт.
.CODE
main PROC
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV consoleHandle,EAX
INVOKE CreateFile,
ADDR filename, GENERIC_WRITE, DO_NOT_SHARE, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0
MOV fileHandle,EAX ; Сохранить дескриптор файла.
.IF EAX == INVALID_HANDLE_VALUE
MOV EDX,OFFSET errMsg ; Отобразить сообщение об ошибке.
; Отобразить буфер.
INVOKE WriteConsoleA, consoleHandle, ADDR errMsg, errMsg-2,
ADDR bytesRead, 0
JMP QuitNow
.ENDIF
INVOKE WriteFile, ; Записать текст в файл.
fileHandle, ; Дескриптор файла.
ADDR buffer, ; Указатель на буфер.
bufSize, ; Количество байт для записи.
ADDR bytesWritten, ; Количество записанных файлов.

Глава 4. Программирование для Windows 115

Стр. 115
0 ; Флаг перекрытия исполнения.
INVOKE CloseHandle, fileHandle
QuitNow:
INVOKE ExitProcess,0 ; Конец программы.
main ENDP
END main

После выполнения этой программы в текущем каталоге появится новый файл


output.txt с указанным для переменной buffer текстом. Здесь нужно отметить, что
подключаемый файл gwin.inc должен быть дополнен необходимыми синонимами и конс&
тантами, такими как:
FILE_ATTRIBUTE_READONLY = 1
FILE_ATTRIBUTE_HIDDEN = 2
FILE_ATTRIBUTE_SYSTEM = 4
FILE_ATTRIBUTE_DIRECTORY = 10h
FILE_ATTRIBUTE_ARCHIVE = 20h
FILE_ATTRIBUTE_DEVICE = 40h
FILE_ATTRIBUTE_NORMAL = 80h
FILE_ATTRIBUTE_TEMPORARY = 100h
FILE_ATTRIBUTE_SPARSE_FILE = 200h
FILE_ATTRIBUTE_REPARSE_POINT = 400h
FILE_ATTRIBUTE_COMPRESSED = 800h
FILE_ATTRIBUTE_OFFLINE = 1000h
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 2000h
FILE_ATTRIBUTE_ENCRYPTED = 4000h
FILE_READ_DATA = 1
FILE_WRITE_DATA = 2
FILE_APPEND_DATA = 4
FILE_DELETE_CHILD = 40h
FILE_BEGIN = 0
FILE_CURRENT = 1
FILE_END = 2
CREATE_NEW = 1
CREATE_ALWAYS = 2
OPEN_EXISTING = 3
OPEN_ALWAYS = 4
TRUNCATE_EXISTING = 5
GENERIC_READ = 80000000h
GENERIC_WRITE = 40000000h
GENERIC_EXECUTE = 20000000h
GENERIC_ALL = 10000000h

Перемещение указателя файла


Функция SetFilePointer перемещает указатель открытого файла. Эта функция ис&
пользуется при добавлении данных в файл или при получении случайного доступа к фай&
лу. Прототип функции такой:
SetFilePointer PROTO,
handle:DWORD, ; Дескриптор файла.
nDistanceLo:SDWORD, ; Расстояние в байтах.
pDistanceHi:PTR SDWORD, ; Указатель на старшие разряды.
moveMethod:DWORD ; Начальная точка.
Параметр moveMethod определяет начальную точку отсчета перемещения. Возможны
три значения: FILE_BEGIN, FILE_CURRENT и FILE_END (от начала, положения курсора,

116 Глава 4. Программирование для Windows

Стр. 116
отсутствует. 117

конца). Расстояние указывается с помощью 64&разрядного целочисленного значения, со&


стоящего из двух частей:
nDistanceLo &&&& младшие 32 разряда;
pDistanceHi &&&& указатель на переменную, содержащую старшие 32 разряда.
Если значение pDistanceHi равняется нулю, то для перемещения указателя будет
использоваться только значение nDistanceLo. В небольшом примере ниже указатель
устанавливается в конец файла для добавления данных.
INVOKE SetFilePointer,
fileHandle, ; Дескриптор файла.
0, ; Величина перемещения.
0, ; Величина перемещения.
FILE_END ; Точка отсчета.

Пример чтения файла


Программа чтения файла открывает текстовый файл, созданный ранее программой
WriteFile с именем output.txt, считывает данные из файла, закрывает файл и ото&
бражает данные на консоли.

Листинг 4.10. Чтение файла


TITLE Чтение файла (ReadFile.asm)
.386
.MODEL flat, stdcall
INCLUDE e:/masm615/include/gwin.inc
.DATA
buffer BYTE 500 DUP(?)
bufSize = ($-buffer)
errMsg BYTE "Cannot open file",0dh,0ah,0
filename BYTE "output.txt",0
fileHandle DWORD ? ; Дескриптор выходного файла.
byteCount DWORD ? ; Число записанных байт.
.CODE
main PROC
INVOKE CreateFile, ; Открыть файл.
ADDR filename,GENERIC_READ,
DO_NOT_SHARE,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0
MOV fileHandle,EAX ; Сохранить дескриптор файла
.IF EAX == INVALID_HANDLE_VALUE
MOV EDX,OFFSET errMsg ; Отобразить сообщение об ошибке.
CALL WriteString
JMP QuitNow
.ENDIF
INVOKE ReadFile, ; Считать файл в буфер.
fileHandle,ADDR buffer,
bufSize,ADDR byteCount,0
INVOKE CloseHandle, ; Закрыть файл.
fileHandle
MOV ESI,byteCount ; Вставить нулевой ограничитель
MOV buffer[esi],0 ; в буфер.
MOV EDX,OFFSET buffer ; Отобразить буфер.
CALL WriteString
QuitNow:

Глава 4. Программирование для Windows 117

Стр. 117
INVOKE WaitMsg
INVOKE ExitProcess,0 ; Конец программы.
main ENDP
END main

Управление окном
Интерфейс Win32 API предоставляет некоторые возможности по управлению окном кон
соли и экранным буфером, который содержит отображаемые на экране данные. На рис. 4.2
показано соответствие экранного буфера и отображаемого на консоли текста. Информа
ция, сохраняемая в экранном буфере, значительно превышает размер отображаемого
текста, который является небольшой частью экранного буфера.

Рис. 4.2. Соответствие экранного буфера и отображаемого


текста

118 Глава 4. Программирование для Windows

Стр. 118
отсутствует. 119

Ниже перечислены функции, которые изменяют положение отображаемого текста


относительно начала экранного буфера.
SetConsoleWindowInfo &&&& устанавливает размеры и положение отображаемого
текста относительно экранного буфера.
GetConsoleScreenBufferInfo &&&& возвращает координаты отображаемого тек&
ста относительно экранного буфера.
SetConsoleCursorPosition &&&& устанавливает положение курсора в экранном
буфере. Если в поле отображаемого текста курсор не попадает, то он сдвигается
для того, чтобы быть видимым.
ScrollConsoleScreenBuffer &&&& перемещает часть текста или весь текст в эк&
ранном буфере.

Функция SetConsoleTitle
Эта функция позволяет изменить название окна консоли. Например, можно назвать
окно ‘‘Моя программа’’.
.DATA
titleStr BYTE "Моя программа",0
.CODE
INVOKE SetConsoleTitle, ADDR titleStr

Функция GetConsoleScreenBufferlnfo
Эта функция возвращает информацию о текущем состоянии окна консоли. Функция
содержит два параметра: дескриптор экранного буфера консоли и указатель на структуру,
которая заполняется функцией.
GetConsoleScreenBufferlnfo PROTO,
outHandle:DWORD, ; Дескриптор экранного буфера консоли.
pBufferlnfo:PTR CONSOLE_SCREEN_BUFFER_INFO
Структура CONSOLE_SCREEN_BUFFER_INFO, с которой работает функция, имеет
следующий вид:
CONSOLE_SCREEN_BUFFER_INFO STRUCT
dwSize COORD <>
dwCursorPos COORD <>
wAttributes WORD ?
srWindow SMALL_RECT <>
maxWinSize COORD <>
CONSOLE_SCREEN_BUFFER_INFO ENDS
Параметр dwSize возвращает размер экранного буфера в количествах символов по длине
и ширине. Параметр dwCursorPos возвращает положение курсора. Оба поля имеют тип COORD.
Параметр wAttributes возвращает цвета символов и фона символов, записанных функ&
циями, такими как WriteConsole. Параметр srWindow возвращает координаты отобра&
жаемого текста относительно экранного буфера. Параметр maxWinSize возвращает мак&
симально допустимый размер окна консоли на основе размера экранного буфера, размера
шрифта и отображаемых размеров. Ниже показан пример использования функции.
.DATA
consolelnfo CONSOLE_SCREEN_BUFFER_INFO <>
.CODE
INVOKE GetConsoleScreenBufferInfo, outHandle, ADDR consoleInfo

Глава 4. Программирование для Windows 119

Стр. 119
Функция SetConsoleWindowlnfo
Эта функция позволяет установить размеры и положение окна консоли относительно
экранного буфера. Ее прототип следующий:
SetConsoleWindowInfo PROTO, ; Установить положение окна консоли.
nStdHandle:DWORD, ; Дескриптор экранного буфера.
bAbsolute:DWORD, ; Тип координат.
pConsoleRect:PTR SMALL_RECT ; Указатель на границы окна.
Параметр bAbsolute определяет, на что указывают координаты структуры, на кото&
рую указывает параметр pConsoleRect. Если значение bAbsolute равно true, то ко&
ординаты определяют верхний левый и нижний правый углы окна, в противном случае
координаты должны быть добавлены к текущим оконным координатам.
Ниже показана программа, которая записывает пятьдесят строк текста в экранный
буфер. Затем размеры их изменяются и строки перемещаются в зону отображения в окне
консоли. Используется функция SetConsoleWindowInfo.
Листинг 4.11. Прокрутка окна консоли
TITLE Прокрутка окна консоли (Scroll.asm)
.386
.MODEL flat, stdcall
.DATA
STD_OUTPUT_HANDLE EQU -11
message BYTE ": Эта строка будет записана "
BYTE "в экранный буфер",0dh,0ah
messageSize = ($-message)
outHandle DWORD 0 ; Дескриптор стандартного выхода.
bytesWritten DWORD ? ; Число записанных байт.
lineNum DWORD 0
windowRect SMALL_RECT <0,0,60,11> ; Левый, верхний, правый, нижний.
.CODE
main PROC
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV outHandle,EAX
.REPEAT
MOV EAX,lineNum
CALL WriteDec ; Отобразить каждый номер.
INVOKE WriteConsoleA,
outHandle, ; Дескриптор консоли.
ADDR message, ; Указатель на строку.
messageSize, ; Длина строки.
ADDR bytesWritten, ; Количество записанных байт.
0 ; Зарезервировано.
inc lineNum ; Следующий номер строки.
.UNTIL lineNum > 50
; Изменяет размеры и положение окна консоли относительно
; экранного буфера.
INVOKE SetConsoleWindowInfo,
outHandle,
TRUE,
ADDR windowRect ; Размеры окна.
CALL Readchar ; Ожидание нажатия клавиши.
CALL Clrscr ; Очистка экранного буфера.
CALL Readchar ; Ожидание нажатия клавиши.
INVOKE WaitMsg
INVOKE ExitProcess,0
main ENDP
END main

120 Глава 4. Программирование для Windows

Стр. 120
отсутствует. 121

Лучше запускать эту программу непосредственно из Windows, чем из интегрирован&


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

Функция SetConsoleScreenBufferSize
Эта функция позволяет установить размеры экранного буфера с количеством колонок X
и количеством строк Y. Прототип функции следующий:
SetConsoleScreenBufferSize PROTO,
outHandle:DWORD, ; Дескриптор экранного буфера.
dwSize:COORD ; Новые размеры буфера.

Управление курсором
Интерфейс Win32 API предоставляет несколько функций для установки размера
курсора, видимости и размещения. Необходимые данные собраны в структуре
CONSOLE_CURSOR_INFO.
CONSOLE_CURSOR_INFO STRUCT
dwSize DWORD ?
bVisible BYTE ?
CONSOLE__CURSOR_INFO ENDS
Поле dwSize устанавливает процент (1...100) символьных ячеек, заполняемых курсо&
ром. Если значение поля bVisible равно true, то курсор видим.
Функция GetConsoleCursorlnfo
Эта функция возвращает текущие размеры и видимость курсора консоли. Возвраща&
ется указатель на структуру CONSOLE_CURSOR_INFO.
GetConsoleCursorlnfo PROTO,
outHandle:DWORD, ; Дескриптор выходного устройства.
pCursorInfo:PTR CONSOLE_CURSOR_INFO ; Информация о курсоре.
По умолчанию размер курсора имеет значение 25 (т.е. 25% символьных ячеек запол&
нено курсором).
Функция SetConsoleCursorlnfo
Эта функция устанавливает текущие размеры и видимость курсора консоли. Переда&
ется указатель на структуру CONSOLE_CURSOR_INFO.
SetConsoleCursorlnfo PROTO,
outHandle:DWORD, ; Дескриптор выходного устройства.
pCursorlnfo:PTR CONSOLE_CURSOR_INFO ; Информация о курсоре.

Функция SetConsoleCursorPosition
Эта функция устанавливает координаты положения курсора X, Y. Передается струк&
тура COORD и дескриптор входного устройства.
SetConsoleCursorPosition PROTO,
outHandle:DWORD, ; Дескриптор.
coords:COORD ; Координаты X,Y.

Управление цветом
Устанавливать цвета окна консоли можно двумя путями. Можно изменять текущий
цвет символов с помощью функции SetConsoleTextAttribute, которая действует на
все последующие символы, выводимые на консоль, или устанавливать атрибуты отдель&
ных ячеек, вызывая функцию WriteConsoleOutputAttribute.

Глава 4. Программирование для Windows 121

Стр. 121
Функция SetConsoleTextAttribute
Эта функция позволяет установить цвет символов и цвет фона для выводимого на
консоль текста. Ее прототип следующий:
SetConsoleTextAttribute PROTO,
outHandle:DWORD, ; Дескриптор консоли.
nColor:DWORD ; Атрибуты цвета.
Значения цвета сохраняются в младшем байте параметра nColor.
Функция WriteConsoleOutputAttribute
Эта функция копирует массив значений атрибутов последовательных ячеек экранного
буфера консоли начиная с указанной позиции. Ее прототип следующий:
WriteConsoleOutputAttribute PROTO,
outHandle:DWORD, ; Дескриптор выхода.
pAttribute:PTR WORD, ; Записываемые атрибуты.
nLength:DWORD, ; Количество ячеек.
xyCoord:COORD, ; Координаты первой ячейки.
IpCountr:PTR DWORD ; Число записанных ячеек.
Параметр pAttribute указывает на массив атрибутов, где каждый младший байт
включает информацию о цвете. Параметр nLength определяет длину массива. Параметр
xyCoord определяет начальную ячейку. Параметр IpCountr указывает на переменную,
которая содержит число записанных ячеек.

Пример использования значений цвета


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

Листинг 4.12. Вывод цветного текста


TITLE Вывод цветного текста (WriteColors.asm)
.386
.MODEL flat, stdcall
INCLUDE e:/masm615/include/gwin.inc
.DATA
outHandle DWORD ?
cellsWritten DWORD ?
xyPos COORD <10,2>
; Массив кодов символов:
buffer BYTE 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20
BufSize = ($ - buffer)
; Массив атрибутов:
attributes WORD 0Fh,0Eh,0Dh,0Ch,0Bh,0Ah,9,8,7,6
WORD 5,4,3,2,1,0F0h,0E0h,0D0h,0C0h,0B0h
.CODE
main PROC
; Получить стандартный дескриптор консоли:
INVOKE GetStdHandle,STD_OUTPUT_HANDLE
MOV outHandle,EAX
; Установить цвета соседних ячеек:
INVOKE WriteConsoleOutputAttribute, outHandle, ADDR attributes,
BufSize, xyPos, ADDR cellsWritten

122 Глава 4. Программирование для Windows

Стр. 122
отсутствует. 123

; Записать коды символов от 1 до 20:


INVOKE WriteConsoleOutputCharacterA, outHandle, ADDR buffer,
BufSize, xyPos, ADDR cellsWritten
INVOKE WaitMsg
Exit ; Конец программы.
main ENDP
END main

На рис. 4.3 показан результат работы программы, где все 20 символов отображаются
различным цветом.

Рис. 4.3. Вывод разноцветных символов

Функции времени и даты


Интерфейс Win32 API предлагает большой выбор функций даты и времени. Поначалу
можно пользоваться только функциями получения и установки текущей даты и времени,
которые описаны в этом разделе. Полный перечень функций приведен в табл. 4.6.

Таблица 4.6. Функции даты и времени Win32 API


Функция Описание
CompareFileTime Сравнивает два 64&разрядных значения времени создания файлов
DosDateTimeToFileTime Преобразовывает значения даты и времени MS&DOS в 64&разрядную
характеристику файла
FileTimeToDosDateTime Преобразовывает 64&разрядную характеристику файла в значения
даты и времени MS&DOS
FileTimeToLocalFileTime Преобразовывает UTC (универсальное время) файла в локальные
характеристики файла
FileTimeToSystemTime Преобразовывает 64&разрядную характеристику файла в формат
системного времени
GetFileTime Извлекает дату и время создания файла, время последнего
обращения и время последней модификации
GetLocalTime Извлекает текущие локальные дату и время
GetSystemTime Извлекает текущие системную дату и время в формате UTC
GetSystemTimeAdjustment Определяет, может ли система применить периодическую
настройку к внутреннему генератору времени
GetSystemTimeAsFileTime Извлекает текущие системную дату и время в формате файла
GetTickCount Извлекает число миллисекунд, которое прошло с момента старта
системы
GetTimeZoneInformation Извлекает текущие параметры временной зоны

Глава 4. Программирование для Windows 123

Стр. 123
Окончание табл. 4.6
Функция Описание
LocalFileTimeToFileTime Преобразовывает локальное время в характеристику файла
на основе UTC
SetFileTime Устанавливает дату и время создания файла, время
последнего обращения и время последней модификации
SetLocalTime Устанавливает текущие локальные дату и время
SetSystemTime Устанавливает текущую системную дату и время
SetSystemTimeAdjustment Разрешение или запрет периодической настройки
внутреннего системного генератора времени
SetTimeZoneInformation Устанавливает текущие параметры временной зоны
SystemTimeToFileTime Преобразовывает системное время в характеристику файла
SystemTimeToTzSpecificLocalTime Преобразовывает время в формате UTC для определенной
временной зоны в соответствующее локальное время

Функциями Windows API используется структура SYSTEMTIME для системного времени.


SYSTEMTIME STRUCT
wYear WORD ? ; Год (4 цифры).
wMonth WORD ? ; Месяц (1-12).
wDayOfWeek WORD ? ; День недели (0-6).
wDay WORD ? ; День (1-31).
wHour WORD ? ; Часы (0-23).
wMinute WORD ? ; Минуты (0-59).
wSecond WORD ? ; Секунды (0-59).
wMilliseconds WORD ? ; Миллисекунды (0-999).
SYSTEMTIME ENDS
Значение поля wDayOfWeek начинается с воскресенья (0). Значение поля wMilli-
seconds не является точным, поскольку системные часы периодически модифицируются.

Функции GetLocalTime и SetLocalTime


Функция GetLocalTime возвращает текущую дату и время дня на основе системных
часов. Время устанавливается в соответствии с временной зоной. При вызове в функцию
передается структура SYSTEMTIME.
GetLocalTime PROTO,
pSystemTime: PTR SYSTEMTIME
Функция SetLocalTime устанавливает текущую дату и время дня на основе систем&
ных часов. Время устанавливается в соответствии с временной зоной. При вызове в функ&
цию передается указатель на структуру SYSTEMTIME.
SetLocalTime PROTO,
pSystemTime: PTR SYSTEMTIME
Если функция выполняется успешно, то возвращается ненулевое целочисленное зна&
чение. В противном случае возвращается нуль. Ниже приводится пример вызова функ&
ции GetLocalTime
.DATA
sysTime SYSTEMTIME <>
.CODE
INVOKE GetLocalTime,ADDR sysTime

124 Глава 4. Программирование для Windows

Стр. 124
отсутствует. 125

Функция GetTickCount
Эта функция возвращает число миллисекунд, прошедших со времени старта компьютера.
GetTickCount PROTO ; Значение возвращается в регистре EAX
Поскольку возвращаемое значение имеет тип двойного слова, то значение будет пе&
риодически обнуляться с периодом 49,7 дней. Эту функцию можно использовать для
контроля прошедшего времени и выполнять определенные действия после превышения
порогового времени. Например, в следующей программе каждые 100 миллисекунд на эк&
ране отображается точка и время контролируется каждые 5000 миллисекунд. Этот код
можно использовать в различных программах.

Листинг 4.13. Подсчет прошедшего времени


TITLE Подсчет прошедшего времени (TimingLoop.asm)
; В программе используется функция GetTickCount для подсчета
; числа миллисекунд, прошедших с момента запуска компьютера.
.386
.MODEL flat, stdcall
INCLUDE e:/masm615/include/gwin.inc
TIME_LIMIT = 5000
.DATA
startTime DWORD ?
dot BYTE ".",0
.CODE
main PROC
INVOKE GetTickCount ; Количество миллисекунд.
MOV startTime,EAX
L1:MOV EDX,OFFSET dot ; Отобразить точку.
CALL WriteString
INVOKE Sleep,100 ; Задержка на 100 мс.
INVOKE GetTickCount
sub EAX,startTime ; Проверка прошедшего времени.
CMP EAX,TIME_LIMIT
JB L1
L2:exit
main ENDP
END main

Функция Sleep
Эта функция приостанавливает выполнение текущей программы на заданное число
миллисекунд.
Sleep PROTO,
dwMilliseconds:DWORD

Процедура получения локального времени


Процедура GetDateTime возвращает 64&разрядное целочисленное значение, содер&
жащее количество временных интервалов по 100 наносекунд, прошедших с 1 января 1601
года. Как ни странно, Microsoft использует эти значения при создании характеристик
файлов. При работе с датами желательно выполнять следующие шаги.

Глава 4. Программирование для Windows 125

Стр. 125
Вызовите функцию, подобную GetLocalTime, для заполнения структуры
SYSTEMTIME.
Преобразуйте структуру SYSTEMTIME в структуру FILETIME с помощью функции
SystemTimeToFileTime.
Скопируйте структуру FILETIME в учетверенное слово.
Структура FILETIME формирует 64&разрядное значение из двух 32&разрядных значений.
FILETIME STRUCT
loDateTime DWORD ?
hiDateTime DWORD ?
FILETIME ENDS
Процедура принимает указатель на 64&разрядную переменную и сохраняет текущие
дату и время в формате FILETIME.
GetDateTime PROC,
pStartTime:PTR QWORD
LOCAL sysTime:SYSTEMTIME, flTime:FILETIME
; Получить и сохранить текущее локальное время и дату как
; 64-разрядное целочисленное значение (формат FILETIME Win32)
; Получить системное локальное время
INVOKE GetLocalTime,
ADDR sysTime
; Преобразовать формат SYSTEMTIME в формат FILETIME
INVOKE SystemTimeToFileTime,
ADDR sysTime,
ADDR flTime
; Скопировать FILETIME в 64-разрядное целочисленное значение
MOV ESI,pStartTime
MOV EAX,flTime.loDateTime
MOV DWORD PTR [ESI],EAX
MOV EAX,flTime.hiDateTime
MOV DWORD PTR [esi+4],EAX
ret
GetDateTime ENDP

Создание секундомера
Функцию GetTickCount можно использовать при разработке двух процедур, кото&
рые совместно будут действовать как секундомер. Одна из процедур, назовем ее Timer-
Start, записывает текущее время. Вторая процедура TimerStop возвращает число мил&
лисекунд, прошедших с момента последнего вызова процедуры TimerStart.
Программа Timer.asm использует обе процедуры и создает некоторую задержку, вы&
зывая функцию Sleep.

Листинг 4.14. Подсчет времени


TITLE Подсчет времени (Timer.asm)
; Демонстрация простого секундомера. Используется функция GetTickCount
.386
;.MODEL flat, stdcall
INCLUDE G:\MyASM\MASM\INCLUDE\Study32.inc
TimerStart PROTO,
pSavedTime: PTR DWORD

126 Глава 4. Программирование для Windows

Стр. 126
отсутствует. 127

TimerStop PROTO,
pSavedTime: PTR DWORD
.DATA
msg BYTE " milliseconds have elapsed",0dh,0ah,0
timerl DWORD ?
.CODE
main PROC
INVOKE TimerStart, ; Запуск таймера ADDR timerl.
ADDR timerl
INVOKE Sleep, 5000 ; Задержка 5 секунд.
INVOKE TimerStop, ; Количество миллисекунд timerl в EAX
ADDR timerl
CALL WriteDec ; Отобразить время.
MOV EDX,OFFSET msg
CALL WriteString
INVOKE WaitMsg
exit
main ENDP

TimerStart PROC uses EAX esi,


pSavedTime: PTR DWORD
; Запуск секундомера.
; Принимает указатель на переменную, содержащую текущее время.
INVOKE GetTickCount
MOV esi,pSavedTime
MOV [esi],EAX
ret
TimerStart ENDP

TimerStop PROC uses esi,


pSavedTime: PTR DWORD
; Остановить секундомер.
; Принимает указатель на переменную, содержащую сохраненное время.
; Возвращает число миллисекунд в EAX.
INVOKE GetTickCount
MOV esi,pSavedTime
sub EAX,[esi]
ret
TimerStop ENDP
END main

Процедура TimerStart принимает указатель на двойное слово, в котором сохраня&


ется текущее время. Процедура TimerStop принимает указатель на то же самое двойное
слово и возвращает разницу в миллисекундах между текущим временем и ранее записан&
ным временем.

Резюме
В данной главе описаны возможности создания программ для Win32 на языке ассемб&
лера. Все программы рассчитаны на работу с консолью, но несмотря на это точно так же
можно обращаться и к графическим окнам Windows, о чем будет сказано в дальнейшем.
Большое количество демонстрационных программ способствует лучшему пониманию
специфики разработки программ для Windows и позволит создать собственную библио&
теку необходимых процедур и функций. Основное отличие разработки программ для

Глава 4. Программирование для Windows 127

Стр. 127
Windows от программ для DOS связано с графическим интерфейсом пользователя. Это
значит, что все команды и директивы ассемблера те же самые (о чем речь пойдет далее),
а изменения связаны с необходимостью обмена информацией с системой через интерфейс
Windows, для чего используются функции операционной системы Windows. Также необ&
ходимо четко представлять, что в защищенном режиме, в котором работает Windows NT,
можно использовать только плоскую модель памяти и нельзя непосредственно обра&
щаться к отдельным устройствам.

Контрольные вопросы
1. Какую опцию компоновщика необходимо использовать, чтобы получить консоль&
ное приложение для Win32?
2. Чем отличаются названия функций для работы с 16&разрядными символами, та&
кими как Unicode?
3. В каких операционных системах набор символов Unicode является встроенным?
4. Что такое тип данных? Какие типы данных вы можете назвать?
5. Что такое дескриптор?
6. С помощью какой функции можно получить дескриптор стандартного входа?
7. Что такое прототип функции?
8. Что записывается в подключаемом файле?
9. Какие структуры данных можете описать?
10. С помощью какой функции можно произвести считывание данных с консоли?
11. Что такое библиотека загрузочных модулей?
12. Какие расширения может иметь файл библиотеки загрузочных модулей?
13. Что такое инкапсуляция?
14. Как можно создать файл с помощью функций Win32?
15. Какие данные необходимы для отображения символа в окне консоли?
16. Какие форматы времени вы можете назвать?

128 Глава 4. Программирование для Windows

Стр. 128
Глава 5

Фундаментальные понятия
языка ассемблера
В этой главе...

Директивы размещения данных


Символические константы
Модели памяти
Команды пересылки данных
Арифметические команды
Tипы операндов
Косвенная адресация
Команды процессоров 80386 и более старших моделей
Резюме
Контрольные вопросы

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

Директивы размещения данных


Переменная является символическим именем для определенного места в памяти, где
размещены данные. Имя переменной связывается с участком памяти, где эта переменная
расположена. Например, если объявить массив из четырех символов, то его имя str1
определяет адрес первого символа (A).
.DATA
str1 DB "ABCD"
Если, условно говоря, адрес первого символа равен 1000, то адрес второго символа
будет равен 1001, следующего &&&& 1002 и т.д. Адрес имени str1 равен 1000, как и адрес
первого символа.
Директивы размещения данных служат для заполнения памяти, при этом использует&
ся несколько основных предопределенных типов. В этой главе рассмотрим директивы

Стр. 129
DB, DW и DD, остальные будут описаны в последующих главах. В табл. 5.1 представлены
все директивы с кратким описанием. В скобках приводятся синонимы для данных директив.

Таблица 5.1. Директивы размещения данных


Директива Описание Байт Атрибут
DB (BYTE) Объявить байт 1 Байт
DW (WORD) Объявить слово 2 Слово
DD (DWORD) Объявить двойное слово 4 Двойное слово
DF (FWORD) Объявить шесть байтов 6 Шесть байтов
DP Объявить удаленный указатель 6 Удаленный указатель
DQ (QWORD) Объявить учетверенное слово 8 Учетверенное слово
DT (TBYTE) Объявить десять байтов 10 Десять байтов

При использовании последних версий MASM лучше писать директивы как BYTE,
WORD и т.д. Это расширенные директивы и с их помощью можно задавать отрицательные
значения, например использовать директиву SBYTE (знаковый байт). Эти директивы
можно трактовать как директивы задания типа. Базовые типы, которые можно использо&
вать для распределения данных, перечислены в табл. 5.2.

Таблица 5.2. Базовые типы


Тип Описание
BYTE 8&разрядное целое число без знака
SBYTE 8&разрядное целое число со знаком
WORD 16&разрядное целое число без знака
SWORD 16&разрядное целое число со знаком
DWORD 32&разрядное целое число без знака
SDWORD 32&разрядное целое число со знаком
FWORD 48&разрядное целое число (используется как указатель)
QWORD 64&разрядное целое число
TBYTE 80&разрядное целое число
REAL4 32&разрядное IEEE short real (короткое вещественное)
REAL8 64&разрядное IEEE long real (вещественное)
REAL10 80&разрядное IEEE extended real (расширенное вещественное)

Объявление байтов
Директива объявления байта DB выделяет память для одного или нескольких 8&раз&
рядных значений. Из определения синтаксиса видно, что имя не является обязательным,
но инициализация, по крайней мере, одного значения должна быть проведена. Если зна&
чений больше одного, они разделяются запятыми.
[имя] DB инициализатор[, инициализатор] . . .
Каждое значение может быть константным выражением, включающим цифровые ли&
тералы, определенные символы и заключенные в кавычки символы или строки символов.

130 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 130
отсутствует. 131

Если значение представлено числом со знаком, то оно может изменяться от -128 до +127;
если числом без знака &&&& от 0 до 255. В следующем фрагменте листинга приведено не&
сколько объявлений байт.
char1 DB 'A' ; Символ в кодировке ASCII.
char2 DB 'A'-10 ; Выражение.
signed1 DB -128 ; Наименьшее значение числа со знаком.
signed2 DB +127 ; Наибольшее значение числа со знаком.
unsigned1 DB 255 ; Наибольшее значение числа без знака.
Значение переменной можно не указывать, поставив знак вопроса на месте инициа&
лизатора.
myval DB ?

Множественная инициализация
Иногда имя переменной указывает на начало последовательности байтов. В таком
случае множественная инициализация используется для присвоения значений отдельным
байтам. В следующем примере переменная с именем list имеет смещение 0000. Это
означает, что для значения 10 смещением будет 0000, для 20 &&&& 0001, для 30 &&&& 0002
и для 40 &&&& 0003.
list DB 10,20,30,40
Символы и целочисленные значения могут присваивать одно и то же значение. Пере&
менные получат одинаковое значение в любом из случаев, приведенных в листинге ниже.
char DB 'A' ; Символ (ASCII 41h).
hex DB 41h ; Шестнадцатеричное число.
dec DB 65 ; Десятичное число.
bin DB 010000001b ; Двоичное число.
oct DB 101q ; Восьмеричное число.
Каждый инициализатор может использовать любое основание системы счисления
при последовательном присвоении значений. Цифры, символы и строковые константы
могут находиться в одном ряду. Если шестнадцатеричное значение начинается с буквы (A-F),
перед ней необходимо поставить нуль (0), чтобы ассемблер не принял это число за метку.
Например, переменные list1 и list2 получат одинаковые значения.
list1 DB 10, 32, 41h,00l000l0b
list2 DB 0Ah,20h,'A',22h

Представление строк символов


Строка символов может быть определена переменной, которая указывает смещение на&
чала строки. Универсального формата хранения строк символов нет, хотя строки, окан&
чивающиеся нулем, как принято в языке С, используются при вызове функций Microsoft
Windows. Строки символов, оканчивающиеся нулем, обозначают Cstring. Другое обо&
значение, Pstring, используется, когда длина строки символов указывается в первом
байте, как принято в языке Pascal.
Cstring DB "С добрым утром",0
Pstring DB 14,"С добрым утром"
Директива DB идеально подходит для размещения последовательности символов лю&
бой длины. Последовательность символов может размещаться на нескольких строках,
как показано в листинге ниже на примере строки символов, оканчивающейся нулем.

Глава 5. Фундаментальные понятия языка ассемблера 131

Стр. 131
LongString DB "Это пример длинной последовательности "
DB "символов, которые занимают несколько "
DB "строк при их объявлении",0
Ассемблер может автоматически подсчитать пространство памяти, занятое перемен&
ной, вычитая смещение переменной из смещения следующей переменной. Оператор ‘‘$’’
возвращает текущее состояние счетчика, как показано в примере.
0000 mystring DB "Это заданная строка"
0010 mystring_len = ($ - mystring)
В данном примере значение mystring_len будет равно 10h или 16.

Оператор DUP
Оператор DUP используется после таких директив, как DB или DW. С помощью этого
оператора можно продублировать одно или несколько значений для размещения их в па&
мяти. Это особенно эффективно при размещении строк символов или массивов.
DB 20 DUP(0) ; 20 байт, все равны нулю.
DB 20 DUP(?) ; 20 байт, инициализированы.
DB 4 DUP("ABC") ; 12 байт: "ABCABCABCABC".
DB 4096 DUP(0) ; Буфер размером 4096 байт, все нули.
Операторы DUP можно вкладывать друг в друга. В первом примере ниже показано
создание в памяти строки символов 000ХХ000ХХ000ХХ000ХХ. Второй пример можно
рассматривать как таблицу, состоящую из трех строк и четырех колонок.
aTable DB 4 DUP(3 DUP(0),2 DUP('X'))
aMatrix DW 3 DUP(4 DUP(0))

Объявление слов
Директива определения слова DW выделяет пространство памяти для одного или не&
скольких 16&разрядных значений. Ее синтаксис такой:
[имя] DW инициализатор[, инициализатор] . . .
Каждый целочисленный инициализатор может принимать значения от 0 до 65535
(FFFFh). Если инициализатор выражается числом со знаком, то его значения будут нахо&
диться в диапазоне от -32768 (8000h) до +32767 (7FFFh). Символьная константа мо&
жет быть сохранена в нижней половине слова. В качестве инициализатора можно ис&
пользовать знак вопроса (?), как показано в приведенных примерах.
DW 0,65535 ; Наименьшее/наибольшее число без знака.
DW -32768,+32767 ; Наименьшее/наибольшее число со знаком.
DW 256 * 2 ; Рассчитываемое выражение.
DW 1000h,4096,'AB',0 ; Множественная инициализация.
DW ? ; Инициализировано неопределенным значением.
DW 5 DUP(1000h) ; Инициализировано 5 слов, каждое равно 1000h.
DW 5 DUP(?) ; 5 слов, значение не определено.

Указатели
Смещение переменной или подпрограммы может быть сохранено в другой перемен&
ной, называемой указателем. В следующем примере ассемблер инициализирует указа&
тель P смещением переменной list.
list DW 256,257,258,259 ; Объявлено 4 слова.
P DW list ; P указывает на переменную list.

132 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 132
отсутствует. 133

При написании имен соблюдайте определенные правила, позволяющие лучше понимать


назначение переменной. Например, указатели начинайте набирать с символа ‘‘p’’. Это
первая буква английского слова pointer, которое переводится как ‘‘указатель’’.

Формат перестановки в памяти


Ассемблер переставляет байты слова, когда сохраняет его в памяти. Последний байт
слова будет иметь младший адрес в памяти. Когда слово перемещается из памяти в 16&раз&
рядный регистр, процессор восстанавливает первоначальный порядок. Например, значе&
ние 1234h будет сохранено в памяти в следующем порядке.
Смещение: 00 01
Значение: 34 12

Объявление двойного слова


Директива DD распределяет память для одного или нескольких 32&разрядных двойных
слов. Синтаксис директивы имеет следующий вид.
[имя] DD инициализатор[, инициализатор] . . .
Значение инициализатора должно находиться в диапазоне от 0 до 0FFFFFFFFh, на&
пример:
signed_val DD 0, 0BCDA1234h, -2147483648
DD 100h DUP(?) ; 256 двойных слов (1024 байт).
Каждый байт в двойном слове переставляется в соответствии с форматом перестанов&
ки. Например, значение 12345678h будет сохранено так:
смещение: 00 01 02 03
значение: 78 56 34 12
Двойное слово может содержать 32&разрядный адрес ‘‘сегмент&смещение’’ перемен&
ной или процедуры. В следующем примере ассемблер автоматически инициализирует
указатель pointer1 адресом подпрограммы subroutinel:
pointer1 DD subroutinel

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

Директива равенства
Директива равенства (=) создает константу, присваивая имени числовое значение.
имя = выражение
В отличие от директив DB и DW, директива равенства не распределяет память. Во вре&
мя трансляции программы все соответствующие имена будут заменены выражениями.
В результате вычисления выражения должно получиться 32&разрядное число со знаком
или без него (для процессоров 386 и более старших моделей), как показано в примере.
prod = 10 * 5 ; Вычисление выражения.
maxInt = 7FFFh ; Максимальное значение 16-разрядного числа со знаком.
minInt = 8000h ; Минимальное значение 16-разрядного числа со знаком.
maxUInt = 0FFFFh ; Максимальное значение 16-разрядного числа без знака.

Глава 5. Фундаментальные понятия языка ассемблера 133

Стр. 133
string = 'XV' ; Допустимо до двух символов.
count = 500
endvalue = count + 1 ; Можно использовать предопределенное имя.
maxLong = FFFFFFFh ; Максимальное значение 32-разрядного числа со знаком.
minLong = 8000000h ; Минимальное значение 32-разрядного числа со знаком.
maxULong = 0FFFFFFFFh ; Максимальное значение 32-разрядного числа без знака.
Символическое имя, определенное с помощью директивы равенства, может быть переоп&
ределено сколь угодно много раз. В следующем примере имя count изменяет значение не&
сколько раз. С правой стороны показано, как ассемблер использует эту константу (табл. 5.3).

Таблица 5.3. Использование директивы знака равенства


Команда Транслируется
count = 5
MOV AL,count MOV AL,5
MOV DL,AL MOV DL,AL
count = 10
MOV CX,count MOV CX,10
MOV DX,count MOV DX,10
count = 2000
MOV AX,count MOV AX,2000

Директива EQU
Директива EQU присваивает символическое имя строке символов или цифровой кон&
станте. Это повышает удобочитаемость программ и позволяет изменить многочисленные
вхождения констант в одном месте. Однако директива EQU имеет одно важное ограниче&
ние &&&& определенное один раз имя нельзя переопределить.
Выражения, включающие целочисленные значения, определяют цифровую констан&
ту, но выражения с вещественными числами интерпретируются как строка символов.
Строки символов могут быть заключены в угловые скобки (<...>) для подтверждения
того, что это строка символов. Таким образом исключается неоднозначность при опреде&
лении имен, как показано в примере ниже (табл. 5.4).

Таблица 5.4. Использование директивы EQU


Пример Тип значения
maxInt EQU 32767 Цифровой
maxUint EQU 0FFFFh Цифровой
count EQU 10 * 20 Цифровой
float EQU <2,345> Строка символов

Директива TEXTEQU
Можно присвоить строке символов имя и потом использовать строку под этим име&
нем. Синтаксис присваивания может быть следующим.
имя TEXTEQU <текст>
имя TEXTEQU макроопределение текста
имя TEXTEQU %константное выражение

134 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 134
отсутствует. 135

В данном случае текст &&&& это последовательность любых символов, заключенных в


угловые скобки. Макроопределение текста представляет предварительно заданный текст,
а константное выражение преобразовывается в текст. Макроопределения текста могут
создаваться в любом месте программы, а имя, определенное с помощью TEXTEQU, может
быть позже переопределено.
Символическое имя может быть присвоено строке символов, в дальнейшем оно все&
гда будет заменяться строкой символов. Например, переменная prompt1 будет указывать
на текст, заданный макроопределением continueMsg, как показано в листинге ниже.
continueMsg TEXTEQU <"Вы желаете продолжать (Y/N)?">
.DATA
prompt1 DB continueMsg
Можно создать альтернативное имя для мнемокода команды. Например:
; Объявление идентификаторов:
move TEXTEQU <MOV>
address TEXTEQU <OFFSET>

; Исходный текст:
move BX,address value1
move AL,20

; Ассемблируется как:
MOV BX,OFFSET value1
MOV AL,20
В следующем примере TEXTEQU используется для определения указателя p1 на стро&
ку. Позже p1 будет определять символ '0'.
.DATA
myString DB "A string",0
.code
p1 TEXTEQU <OFFSET myString>
MOV BX,p1 ; BX = OFFSET myString.
p1 TEXTEQU <0>
MOV SI,p1 ; SI = 0.

Модели памяти
С помощью директивы .MODEL можно выбрать одну из стандартных моделей памяти
для программ на языке ассемблера. Модель памяти можно рассматривать как конфигура&
цию, которая определяет, каким образом надо комбинировать используемые сегменты.
Каждая модель имеет свои ограничения по максимальному размеру памяти, выделяемой
для команд и данных. Здесь важно понимать, как в каждой модели происходит вызов
подпрограмм и данных.
При программировании для 32&разрядного режима используется только плоская модель
памяти.

Выбирая модель памяти, обычно делают выбор между скоростью, гибкостью и разме&
рами программы. Модель памяти, которая ограничивает размеры всех данных одним
сегментом в 64 Кбайт, гарантирует, что все вызовы будут только ближними и, соответст&
венно, потребуются только 16&разрядные значения. Таким образом, доступ к данным бу&
дет более быстрым, потому что 16&разрядный адрес может быть загружен значительно

Глава 5. Фундаментальные понятия языка ассемблера 135

Стр. 135
быстрее, чем 32&разрядный адрес ‘‘сегмент&смещение’’. Модель памяти, которая разме&
щает подпрограммы в различных сегментах, также требует для их вызова использования
двух регистров CS и IP.
Различные модели памяти используют различное число байтов для команд и данных.
Например, когда длина сегмента кодов ограничена объемом в 64 Кбайт, то нет необходимо&
сти в командах использовать большие числа. В табл. 5.2 показано различие между всеми
моделями памяти. При использовании всех моделей памяти, кроме тонкой, создаются вы&
полняемые программы типа .exe. При использовании тонкой модели памяти создаются
программы .com. Все модели, исключая плоскую модель, рассчитаны на функционирова&
ние в реальном режиме процессора. Плоская модель памяти может использоваться только
для работы в защищенном режиме. Например, операционные системы Windows NT,
Windows 2000 и более поздние являются 32&разрядными и работают в защищенном режиме.

Таблица 5.5. Модели памяти, используемые в MASM


Модель Описание
tiny Коды и данные вместе должны занимать не более 64 Kбайт
(тонкая)
small Код <= 64 Kбайт, данные <= 64 Kбайт. Один сегмент кодов, один сегмент данных
(малая)
medium Данные <= 64 Kбайт, код любого размера. Много сегментов кодов, один сегмент данных
(средняя)
compact Код <= 64 Kбайт, данные любого размера. Один сегмент кодов, много сегментов данных
(компактная)
large Код >64 Kбайт, данные > 64 Kбайт. Много сегментов кодов и данных
(большая)
huge Аналогично модели large, за исключением того, что отдельные переменные, такие как
(огромная) массив, могут быть больше 64 Kбайт
flat Нет сегментов. 32&разрядная адресация используется как для кодов, так и для данных.
(плоская) Только защищенный режим

При разработке программ для современных 32&разрядных операционных систем необхо&


димо использовать плоскую модель памяти.

Выполняемые программы
Надо хорошо представлять себе, как выглядят выполняемые программы после загруз&
ки их в память. Сегмент кодов и сегмент памяти находятся по разным адресам памяти,
что можно увидеть с помощью отладчика.
На рис. 5.1 показано состояние процессора и распределение памяти для сегментов.
Три сегмента имеют совместное пересечение, однако пересечение физически зани&
маемой памяти отсутствует. Каждый программный сегмент (иногда называемый логиче%
ским сегментом) получается очень небольшим и не заходит в пространство другого сег&
мента. Регистр IP способен принимать и большие значения, это может случиться, когда
программист забудет поставить заключительную команду окончания кодовой последова&
тельности. Если будет превышена граница сегмента данных при реальном режиме работы
процессора, то разрушится стек. В защищенном режиме произойдет прерывание при по&
пытке выйти за границы сегмента данных.

136 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 136
отсутствует. 137

Рис. 5.1. Состояние процессора памяти для программы hello

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

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


Ассемблер, написанный для семейства процессоров Intel, поддерживает большой
диапазон процессоров от 8086 до Pentium. Ассемблеры Borland и Microsoft имеют дирек&
тивы установки типа процессора, которые определяют минимально возможный тип
применяемого процессора. Если в программе используется, например, директива .8086,
она может запускаться на компьютере с процессором 8086 и с более поздними процессо&
рами. Но если используется директива .386, то компьютер с процессором 8086 уже не
сможет выполнить эту программу. В табл. 5.6 перечислены все директивы установки
процессора для реального режима. Если планируется использование 32&разрядных реги&
стров, то директива .386 должна находиться перед любым 32&разрядным регистром
в программе. Существует также ряд директив (директивы .286P, .386P, .486P, .586P
и .686P), разрешающих работать с привилегированными командами. В этом списке их
нет, они нужны при программировании на уровне операционной системы.

Таблица 5.6. Директивы установки типа процессора


Директива Описание
.8086 Допустимы команды для процессоров 8086 и 8088. Команды для процессоров 80186
и более поздних не используются. Команды для 8087 разрешены
.186 Разрешены команды для процессора 80186, команды для последующих процессоров
не используются

Глава 5. Фундаментальные понятия языка ассемблера 137

Стр. 137
Окончание табл. 5.6
Директива Описание
.286 Доступны непривилегированные команды для 80286, команды для более поздних
процессоров не используются
.386 Доступны непривилегированные команды для 80386, команды для более поздних
процессоров не используются
.486 Доступны непривилегированные команды для 80486, команды для процессоров Pentium
не используются
.586 Доступны непривилегированные команды для процессоров Pentium
.287 Используются команды вычислений с плавающей запятой для математического
сопроцессора 80287
.387 Используются команды вычислений с плавающей запятой для математического
сопроцессора 80387
.686 Доступны непривилегированные команды для процессоров Pentium Pro
.686P Доступны привилегированные команды для процессоров Pentium Pro

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


Команда MOV
Команда пересылки данных MOV копирует данные из одного операнда в другой, по&
этому эта команда относится к группе команд пересылки данных. Используются следую&
щие основные формы команды MOV, где первый операнд представляет собой место, куда
пересылают данные (операнд%получатель), а второй &&&& место, откуда пересылают данные
(операнд%отправитель):
MOV reg,reg
MOV mem,reg
MOV reg,
MOV mem,immed
MOV reg,immed
В таком формате reg может представлять любой регистр (кроме регистра указателя
команд IP), который не может быть операндом&получателем. Слово mem указывает на
место в памяти, а слово immed представляет непосредственное значение. Размеры обоих
операндов должны быть одинаковыми. Например, 16&разрядный регистр может быть по&
слан только в 16&разрядный блок памяти.
Начинающие программисты часто не обращают внимания на размеры операндов, что при&
водит к ошибкам компиляции.

Когда используются регистры сегментов (обозначение segreg), следующие переме&


щения вполне возможны, за исключением того, что нельзя использовать регистр CS в ка&
честве операнда&получателя.
MOV segreg,reg16
MOV segreg,mem16
MOV reg16,segreg
MOV mem16,segreg

138 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 138
отсутствует. 139

Необходимо отметить, что в команде MOV нельзя использовать два операнда памяти.
Для этого приходится применять регистры для копирования байта, слова или двойного
слова из одного места памяти в другое. Следующие команды скопируют слово из пере&
менной var1 в var2.
MOV AX,var1
MOV var2,AX
Ниже приведены примеры команды MOV со всеми тремя типами операндов.
.DATA
count DB 10
total DW 4126h
bigVal DD 12345678H
.code
MOV AL,BL ; 8-разрядный регистр в регистр.
MOV BL,count ; 8-разрядный блок памяти в регистр.
MOV count,26 ; 8 разрядов непосредственно в память.
MOV BL,1 ; 8 разрядов непосредственно в регистр.
MOV DX,CX ; 16-разрядный регистр в регистр.
MOV BX,8FE2h ; 16 разрядов непосредственно в регистр.
MOV total,1000h ; 16 разрядов непосредственно в память.
MOV EAX,EBX ; 32-разрядный регистр в регистр.
MOV EDX,bigVal ; 32-разрядный блок памяти в регистр.

Контроль соответствия типов


Когда с помощью директив DB, DW, DD или других создаются переменные, ассемблер
присваивает им определенные типы (байт, слово, двойное слово и т.д.) в зависимости от
их размера. Этот тип проверяется при использовании переменной, и если ее тип не соот&
ветствует тому, который необходим, фиксируется ошибка. Например, следующая коман&
да MOV неправильная, потому что типом переменной count является слово, а регистр AL
представляет байт.
.DATA
count DW 20h
.code
MOV AL,count ; Ошибка: размеры операндов не совпадают.
Проверка соответствия типов помогает избежать логических ошибок. Даже когда мень&
ший размер перемещается в больший, ошибка несоответствия типов все равно возникает.
.DATA
byteval DB 1
.code
MOV AX,byteval ; Ошибка: размеры операндов не совпадают.

Ошибка несоответствия типов возникает даже при перемещении значения меньшего типа
в больший.

При необходимости можно использовать директиву LABEL для создания нового име&
ни с тем же смещением, но другими атрибутами, благодаря чему те же самые данные уже
не вызовут ошибки.
.DATA
countB LABEL byte ; Атрибут байт.
countW DW 20h ; Атрибут слово.
.code
MOV AL,countB ; Извлекает младший байт из countB.
MOV CX,countW ; Извлекает все из countW.

Глава 5. Фундаментальные понятия языка ассемблера 139

Стр. 139
Использование дополнительного смещения в операторах
К имени операнда памяти можно добавить дополнительное смещение (сдвиг), ис&
пользуя метод, называемый адресацией с прямым смещением. Это позволяет обеспечить
доступ к значениям в памяти, у которых нет собственных имен (например, к массивам
байтов, слов и двойных слов).
arrayB DB 10h,20h
arrayW DW 100h,200h
arrayD DD 10000h,20000h
Обозначение arrayB+1 ссылается на место в памяти за первым байтом, на который
указывает идентификатор arrayB, обозначение arrayW+2 ссылается на третье двойное
слово от arrayW. Операнды можно также записывать в команде MOV, используя такой
вид обозначений для копирования данных из памяти. В следующем примере показано
значение регистра AL после каждого изменения переменной.
MOV AL,arrayB ; AL = 10h.
MOV AL,arrayB+1 ; AL = 20h.
Когда выполняются команды с 16&разрядными операндами, смещение каждого по&
следующего члена массива увеличивается на два байта.
MOV AX,arrayW ; AX = 100h.
MOV AX,arrayW+2 ; AX = 200h.
Члены массива учетверенных слов смещаются на четыре байта от предыдущего.
MOV EAX,arrayD ; EAX = 10000h.
MOV EAX,arrayD+4 ; EAX = 20000h.

Команда XCHG
Команда обмена значениями XCHG обменивает содержание двух регистров или со&
держание регистра и переменной. Она имеет следующий синтаксис.
XCHG reg, reg
XCHG reg, mem
XCHG mem, reg
Команда XCHG наиболее эффективна при обмене значений двух операндов, поскольку
нет необходимости использовать дополнительный регистр для временного значения. Эта
команда значительно повышает скорость работы в приложениях сортировки данных.
Один или оба операнда могут быть регистрами, регистр можно также скомбинировать
с операндом в памяти, но нельзя использовать два операнда памяти. Например:
XCHG AX,BX ; Обменивает два 16-разрядных регистра.
XCHG AH,AL ; Обменивает два 8-разрядных регистра.
XCHG var1,BX ; Обменивает 16-разрядный операнд в памяти с регистром BX.
XCHG EAX,EBX ; Обменивает два 32-разрядных регистра.
Если необходимо обменять содержание двух переменных, можно временно использо&
вать регистр для хранения операнда. В листинге 5.1 производится обмен содержимого
двух переменных.

Листинг 5.1. Обмен двух переменных


title Обмен двух переменных
.386
.MODEL flat, stdcall

140 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 140
отсутствует. 141

INCLUDE G:\MyASM\MASM\INCLUDE\Study32.inc
.DATA
value1 DWORD 0Ah
value2 DWORD 14h
.code
main PROC
MOV EAX,value1 ; Загрузить в регистр EAX.
INVOKE WriteInt ; Отобразить содержимое EAX.
XCHG value2,EAX ; Обменять EAX с value2.
INVOKE Crlf ; Перевод каретки (курсора)
INVOKE WriteInt ; Отобразить содержимое EAX.
MOV value1,EAX ; Сохранить EAX в value1.
INVOKE Crlf
INVOKE WaitMsg ; Ожидание нажатия <Enter>.
main ENDP
END main

Арифметические команды
Набор команд процессора 8086 включает команды для выполнения целочисленных
арифметических операций с 8&разрядными и 16&разрядными операндами. Для арифме&
тических операций с вещественными числами используется специальное программное
обеспечение или дополнительная микросхема арифметического сопроцессора 8087. Про&
цессор 80386 может выполнять целочисленные операции с 32&разрядными целыми чис&
лами. Процессор Intel 486 содержит встроенный блок сопроцессора для вычислений с
вещественными числами, что характерно и для всех последующих моделей процессоров.
В этой главе будут рассмотрены только операции сложения и вычитания. Операции
умножения и деления будут подробно описаны в дальнейшем.

Команды INC и DEC


Команда инкремента INC и декремента DEC добавляет или вычитает единицу из ка&
кого&либо одного операнда.
INC операнд
DEC операнд
Операнд может быть регистром или операндом памяти. Используются все флаги со&
стояния, за исключением флага переноса.
INC AL ; Инкремент 8-разрядного регистра.
DEC BX ; Декремент 16-разрядного регистра.
INC EAX ; Инкремент 32-разрядного регистра.
INC membyte ; Инкремент операнда памяти.
DEC BYTE PTR membyte ; Инкремент 8-разрядного операнда в памяти.
DEC memword ; Декремент операнда памяти.
INC WORD PTR memword ; Инкремент 16-разрядного операнда в памяти.
В этих примерах оператор BYTE PTR идентифицирует 8&разрядный оператор, а WORD
PTR идентифицирует 16&разрядный операнд.

Команда ADD
Команда ADD складывает операнд&отправитель и операнд&получатель одинакового
размера. Команда имеет следующий синтаксис.
ADD операнд-получатель, операнд-отправитель

Глава 5. Фундаментальные понятия языка ассемблера 141

Стр. 141
Исходный операнд&отправитель не изменяется в процессе выполнения команды,
а целевому операнду&получателю присваивается значение суммы. Размеры операндов
должны быть одинаковыми, и только один операнд может быть операндом памяти. Ре&
гистр сегмента не может быть целевым операндом. Используются все флаги состояния.
ADD CL,AL ; Суммирует два 8-разрядных регистра.
ADD EAX,EDX ; Суммирует 32-разрядные регистры.
ADD BX,1000h ; Суммирует непосредственное значение с
; 16-разрядным регистром.
ADD var1,AX ; Суммирует 16-разрядный регистр с операндом в памяти.
ADD DX,var1 ; Суммирует 16-разрядный операнд в памяти с регистром.
ADD varl,10 ; Суммирует непосредственную величину с операндом
; в памяти.
ADD DWORD PTR memVal, ECX
Оператор DWORD PTR определяет 32&разрядный операнд в памяти.

С помощью команды ADD складываются операнды только одинакового размера.

Команда SUB
Команда вычитания SUB вычитает операнд&отправитель из операнда&получателя.
Синтаксис этой команды следующий:
SUB операнд-получатель, операнд-отправитель
Размеры обоих операндов должны быть одинаковыми, и только один операнд может
быть операндом памяти. В процессоре исходный операнд сначала инвертируется, а затем
складывается с целевым операндом. Например, 4 – 1 будет выполняться как 4 + (–1).
Вспомните, что двоичное дополнение используется для отрицательных чисел, поэтому –1
будет представлено как 11111111. Приведем следующий пример вычитания:

00000100 ( 4 )
+11111111 (-1 )
---------
00000011 ( 3 )

Листинг с примерами использования команды SUB с различными типами операндов


показан ниже.
SUB EAX,12345h ; Вычитание 32-разрядного числа из регистра.
SUB CL,AL ; Вычитание 8-разрядного регистра из регистра.
SUB EDX,EAX ; Вычитание 32-разрядного регистра из регистра.
SUB BX,1000h ; Вычитание непосредственного значения из
; 16-разрядного регистра.
SUB var1,AX ; Вычитание 16-разрядного регистра из операнда памяти.
SUB DX,var1 ; Вычитание 16-разрядного операнда памяти из регистра.
SUB var1,10 ; Вычитание непосредственного значения из
; операнда памяти.

Флаги, используемые в командах ADD и SUB


Если после выполнения команд ADD или SUB получается нулевой результат, устанав&
ливается флаг нуля, а если результат отрицательный, устанавливается флаг знака. В сле&

142 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 142
отсутствует. 143

дующих примерах в строке 1 получается нулевой результат, а в строке 4 результат будет


равен -1 (FFFFh).
MOV AX,10
SUB AX,10 ; AX = 0, ZF = 1.
MOV BX,1
SUB BX,2 ; BX = FFFF, SF = 1.
Флаг нуля устанавливается, когда результат арифметической операции равен 0. Следует
отметить, что команды INC и DEC используют флаг нуля, но не используют флаг переноса.
MOV BL,4Fh
ADD BL,0BEh ; BL = 0, ZF = 1, = 1
MOV AX,0FFFFh
INC AX ; ZF = 1 (CF не используется).
Определение операнда как числа со знаком или без него выполняется программи&
стом. Процессор обновляет флаги знака и переноса отдельно для каждого случая.

Операнды без знака


Когда выполняются арифметические операции, используется флаг переноса. Если
в результате операции сложения получается большее число, чем это допустимо для прием&
ного операнда, устанавливается флаг переноса. Например, сумма 0FFh + 1 равна 100h, но
только две цифры (00) будут записаны в регистр AL, при этом установится флаг переноса.
MOV AX,0FFh
ADD AL,1 ; AL = 00, CF = 1.
В этом случае использовалось 8&разрядное сложение, так как был задействован ре&
гистр AL, размер которого равен одному байту. Для получения правильного ответа необ&
ходимо использовать 16&разрядное сложение, что означает использование регистра AX,
как показано ниже.
MOV AX,0FFh
ADD AX,1 ; AX = 0100, CF = 0.
Подобные ситуации случаются, когда вычитается больший операнд из меньшего. В сле&
дующем примере устанавливается флаг переноса, если результирующее число в регистре
AL будет некорректным.
MOV AL,1
SUB AL,2 ; AL = FF, CF = 1.

Знаковое переполнение
Флаг переполнения устанавливается, когда в результате арифметической операции
получается число со знаком, превышающее максимально допустимое для операнда&
получателя. Установленный флаг переполнения говорит о том, что операнд&получатель
содержит некорректное число.
MOV AL,01111110b ; +126.
ADD AL,00000010b ; 126 + 2 => 10000000b, OF = 1.
MOV AL,10000000b ; -128.
ADD AL,11111110b ; -128 + (-2) => 01111110b, OF = 1.
Знаковое переполнение возникает, если суммируются два положительных числа, а ре&
зультат получается отрицательный, а также если суммируются два отрицательных числа,
а в результате получается положительное число. Процессор сравнивает значение флага
переноса со значением, которое переносится в знаковый бит операнда&получателя. Если
они не равны, устанавливается флаг переполнения. Например, при сложении двоичных

Глава 5. Фундаментальные понятия языка ассемблера 143

Стр. 143
76543210 чисел 10000000 и 1111111 нет переноса из бита 6 в бит 7,
CF = 1 10000000 но производится перенос из бита 7 во флаг переноса и, соот
Нет переноса + 11111110 ветственно, устанавливается флаг переноса (рис. 5.2).
из бита 6 = 01111110
в бит 7
Рис. 5.2. Пример знакового переполнения

Tипы операндов
Существует три основных типа операндов: непосредственный операнд, регистр
(регистровый операнд) и значение памяти (память). Непосредственный операнд является
константой. Регистровый операнд представляет значение одного из регистров процессо
ра. Операнд ‘‘память’’ ссылается на значение в памяти.
В системе команд Intel используется много способов представления операндов для
значений памяти, чтобы облегчить управление массивами и сложными структурами дан
ных. Шесть типов операндов памяти показаны в табл. 5.7. В этой главе будут рассмотрены
первые три типа операндов: прямой, прямое смещение и косвенный регистр.

Таблица 5.7. Типы операндов для значений памяти


Тип операнда Пример Описание

прямой op1 EA представляет смещение переменной


bytelist
прямое смещение bytelist + 2 EA представляет сумму смещения переменной
и дополнительного смещения
косвенный регистр [SI] EA является содержимым базового или индексного регистров
[BX]
индексированный list[BX] EA представляет сумму базового или индексного
[list+BX] регистров и дополнительного смещения
list[DI]
[SI+2]
индексированный по базе [BX+DI] EA представляет сумму базового и индексного регистров
[BX][DI]
[BP-DI]
индексированный по базе [BX+SI+2] EA представляет сумму базового, индексного регистров
со смещением list[BX+SI] и дополнительного смещения
list[BX][SI]

Здесь необходимо пояснить некоторые используемые термины. Дополнительное смеще


ние  это или число, или идентификатор. Эффективный адрес EA операнда  это сме
щение (расстояние) данных от начала соответствующего сегмента. Каждый тип операнда
в таблице ссылается на содержание памяти с помощью эффективного адреса. Режим ад
ресации используется в команде в зависимости от типа операндов. Например, следую
щая команда использует режим косвенной адресации:
MOV AX,[SI]

Регистровые операнды
Регистровым операндом может быть любой регистр. Обычно режим регистровой адре
сации наиболее эффективный, потому как регистры находятся непосредственно в процес
соре и нет необходимости обращаться к памяти. Ниже приведены некоторые примеры ис
пользования команды MOV с регистровыми операндами.

144 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 144
отсутствует. 145

MOV EAX,EBX
MOV CL,20h
MOV SI,OFFSET var1

Непосредственные операнды
Непосредственные операнды являются константным выражением, таким как число,
символьная константа, арифметическое выражение или символическая константа. Ас&
семблер определяет значение непосредственного операнда во время трансляции. Это
значение вставляется непосредственно в машинные команды. Пример использования
непосредственных операндов:
MOV AL,10
MOV EAX,12345678h
MOV DL,'X'
MOV AX,(40 * 50)

Операнды с прямой адресацией


Прямой операнд ссылается на содержание памяти, адрес которой определяется зна&
чением сегмента данных. Во время выполнения программы процессор считает, что сме&
щение любой переменной определяется от значения, указанного в сегменте данных DS.
Ниже приводятся примеры операнда прямой адресации с использованием байта, слова
и двойного слова.
.DATA
count DB 20
wordList DW 1000h,2000h
longVal DD 0F63B948Ch
.code
MOV AL,count
MOV BX,wordList + 2
MOV EDX,longVal

Оператор OFFSET
Оператор смещения OFFSET возвращает 16&разрядное значение смещения перемен&
ной. Ассемблер определяет смещение каждой переменной во время трансляции про&
граммы. В следующем примере переменная aWord имеет смещение 0000, команда MOV
перемещает 0 в регистр BX.
.DATA
aWord DW 1234h
.code
MOV BX,OFFSET aWord ; BX = 0000

Операнды со смещением
Особенно удобно использовать операторы сложения и вычитания (+,-) для доступа
к списку переменных. Оператор + добавляет значение к смещению переменной. В пред&
ставленной последовательности команд первый байт массива array перемещается в ре&
гистр AL, второй &&&& в регистр BL, третий &&&& в CL и четвертый &&&& в DL. Значение каждого
регистра после перемещения показано в комментарии.

Глава 5. Фундаментальные понятия языка ассемблера 145

Стр. 145
.DATA
array DB 0Ah,0Bh,0Ch,0Dh
.code
MOV AL,array ; AL = 0Ah
MOV BL,array+l ; BL = 0Bh
MOV CL,array+2 ; CL = 0Ch
MOV DL,array+3 ; DL = 0Dh
Можно вычитать из смещения метки. В следующем примере значение метки endlist раз&
мещается сразу за последним байтом списка list. Чтобы переместить последний байт из
list в регистр AL, необходимо написать следующий код. Напомню использование ди&
рективы LABEL. Если она расположена после объявления переменной, как показано ни&
же, то эта переменная будет ссылаться на то место в памяти, где предполагается разме&
щать переменную. Тип переменной определяется в соответствии с указанным типом.
.DATA
list byte 1,2,3,4,5
endlist LABEL byte
.code
MOV AL,endlist-1 ; переместить 5 в AL
Если в списке имеются 16&разрядные числа, то к смещению каждого последующего
элемента добавляется значение 2, что и показано в следующем примере.
.DATA
wvals DW 1000h,2000h,3000h,4000h
.code
MOV AX,wvals ; AX = 1000h
MOV BX,wvals+2 ; BX = 2000h
MOV CX,wvals+4 ; CX = 3000h
MOV DX,wvals+6 ; DX = 4000h

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

Если выражение связано с регистром или операндом памяти, его значение должно
быть не больше, чем размер операнда&получателя. Например, разрешены следующие
присваивания.
.386
.code
MOV DL,3 * 5
MOV AX,100h + 500h
MOV EAX,(1000h * 50h) + 26
В табл. 5.8 приведен полный набор операторов, распознаваемых большинством ас&
семблеров, с их кратким описанием. (Более полное описание для некоторых операторов
будет приведено в соответствующих разделах.) Из этого набора видно, что ассемблер &&&&
достаточно мощный инструмент даже для профессионального программирования.

146 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 146
отсутствует. 147

Таблица 5.8. Операторы языка ассемблера


Оператор Описание
( ) Выделяет выражение для приоритетного вычисления
* Умножение целочисленных выражений. Использует также режимы адресации
процессора 80386 (когда одно выражение находится в регистре)
+ (бинарный) Сложение двух выражений
+ (унарный) Указывает, что выражение является положительным
- (бинарный) Вычитание двух выражений
- (унарный) Изменяет знак выражения
. Используется для выбора элемента структуры
/ Деление целочисленных выражений
: Используется при переопределении сегмента или группы
? Используется при инициализации переменных с неопределенными данными
[] MASM: используется для выделения сложения или косвенных операндов в памяти
AND Поразрядная логическая операция И
BYTE PTR Преобразует адресное выражение к размеру в один байт
CODEPTR Возвращает используемый по умолчанию размер адреса процедуры
DUP Повторяет операцию выделения памяти для данных столько раз, сколько задано
счетчиком
DWORD PTR Преобразует адресное выражение к размеру в двойное слово
EQ Оператор отношения. Возвращает значение истина (число 0FFFFh), если
выражения равны, и ложь (0) &&&& когда не равны
FAR PTR Преобразует адресные выражения в дальние указатели
FASTIMUL Генерирует код, который перемножает регистр или адрес в памяти на значение
и помещает его в целевой регистр
FLIPFLAG Оптимизированная форма команды XOR, которая выполняет поразрядное
дополнение с помощью максимально коротких инструкций. Применяется
в случае, если флаги не используются
FWORD PTR Преобразует адресные выражения в 32&разрядные дальние указатели
GE Возвращает значение истина, если одно выражение больше или равно другому
GETFIELD Генерирует код, который извлекает значение поля из регистра или адреса памяти
и устанавливает это значение в приемник
GT Возвращает значение истина, если одно выражение больше другого
HIGH Возвращает старшую часть (8 бит) выражения
LARGE Задает для смещения выражения размер 32 бита
LE Возвращает значение истина, если одно выражение меньше или равно другому
LENGTH Возвращает число элементов данных, выделенных для имени
LOW Возвращает младшую часть выражения
LT Возвращает значение истина, если одно выражение меньше другого
MASK Возвращает битовую маску переменной. Битовая маска сохраняет только важные
биты, сбрасывая все остальные в нуль. Переменная должна быть определена
директивой RECORD

Глава 5. Фундаментальные понятия языка ассемблера 147

Стр. 147
Продолжение табл. 5.8
Оператор Описание
MAKEFLAG Оптимизированная форма команды AND, которая сбрасывает биты максимально
быстро. Применяется в случае, если флаги не используются
MOD Возвращает остаток от деления двух выражений
NE Возвращает значение истина, если одно выражение не равно другому
NEAR Преобразует адресное выражение в ближний указатель
NEAR PTR Преобразует адресное выражение в ближний указатель
NOT Выполняет поразрядное дополнение (инвертирование) выражения
OFFSET Возвращает смещение выражения в текущем сегменте (а также в группе, которой
принадлежит сегмент, если используются упрощенные директивы определения
сегментов)
OR Выполняет поразрядную логическую операцию ИЛИ
PROC PTR Преобразует адресное выражение в ближний или дальний указатель
PTR Устанавливает размер операнда, особенно в тех случаях, когда это неясно из контекста
PWORD PTR Преобразует адресное выражение в 32&разрядный дальний указатель
QWORD PTR Преобразует адресное выражение в размер учетверенного слова
SEG Возвращает сегмент выражения, независимо от того, является ли оно переменной,
именем сегмента или группы, меткой или другим символом
SETFIELD Генерирует код, присваивающий значение полю записи. Устанавливает значение поля
(целевой регистр или адрес памяти) в соответствии с содержимым регистра&источника
SETFLAG Оптимизированная форма команды OR, которая устанавливает биты максимально
быстро. Применяется в случае, если флаги не используются
SHL Сдвигает выражение влево на число бит, заданных счетчиком. Отрицательное
значение счетчика задает сдвиг данных в противоположном направлении
SHORT Преобразует выражение в указатель короткого типа (не больше &&128 или +127 байт
от текущего адреса программы). Часто используется в командах JMP, например,
JMP SHORT Label1
SHR Сдвигает значение выражения вправо на число бит, заданных значением счетчика.
Отрицательное значение приводит к тому, что данные будут сдвигаться
в противоположную сторону
SIZE Возвращает общее число байтов, выделенных для переменной. В режиме MASM
оператор SIZE рассчитывается как длина (LENGTH), умноженная на тип (TYPE)
SMALL Задает для смещения выражения размер 16 бит
TBYTE PTR Преобразует адресное выражение в размер 10 байт
TESTFLAG Оптимизированная форма инструкции TEST, которая проверяет биты
максимально быстро
THIS Создает операнд, адресом которого будет текущий сегмент и счетчик адреса.
Тип описывает размер операнда и то, представляет ли он собой код или данные
.TYPE Возвращает байт, который определяет тип и область видимости выражения.
Результирующие биты различают метку и переменную в программе, определяют
данные, внутренний и внешний идентификатор
TYPE Применяет тип существующей переменной или элемента структуры к другой
переменной или элементу структуры

148 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 148
отсутствует. 149

Окончание табл. 5.8


Оператор Описание
TYPE Возвращает число, определяющее размер или тип переменной. Например,
оператор TYPE для переменной типа слово возвращает значение 2
WIDTH Возвращает количество битов, занимаемое переменной, которая была объявлена
с помощью директивы RECORD
WORD PTR Преобразует адресное выражение в размер слова
XOR Выполняет для двух выражений поразрядную логическую операцию ‘‘исключающее
ИЛИ’’. Для форматной печати включается безусловный перевод страницы
& Подставляет фактическое значение параметра макрокоманды
<> Интерпретирует текст как литерал, независимо от возможно содержащихся в нем
специальных символов
! Интерпретирует символ как литерал, независимо от специального значения,
которое он может иметь
% Интерпретирует текст как выражение, вычисляет его значение и заменяет текст
полученным результатом. Текстом может быть либо числовое выражение либо
текстовое присваивание
;; Подавляет выделение памяти для комментария в макроопределении

Арифметические операторы
Арифметические операторы могут использоваться только с целыми числами, за исклю&
чением унарных плюс (+) и минус (-), которые также могут функционировать и с реальны&
ми числами. Примеры использования арифметических операторов показаны в табл. 5.9.

Таблица 5.9. Использование арифметических операторов


Выражение Результат
1000h * 50h Результат B0000h
-4 + -2 Результат –6
count + 2 count является константой
31 MOD 6 Результат 1
6 / 4 Результат 1
'2' - 30h Результат 2

Символьная константа совместима с целыми числами, поэтому такое выражение, как


'2'-30h, представляет собой вычитание 30h из 32h, поскольку в кодировке ASCII зна&
чение цифры ‘2’ будет равно 32.

Старшинство и ассоциативность операторов


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

Глава 5. Фундаментальные понятия языка ассемблера 149

Стр. 149
Таблица 5.10. Старшинство операторов
Оператор Уровень Описание
( ) 1 Круглые скобки
+, - 2 Знаки плюс и минус (унарные)
*, / 3 Умножение, деление
mod 4 Деление по модулю
+, - 5 Сложение, вычитание

В табл. 5.11 показано использование старшинства операторов с кратким описанием


последовательности операций.

Таблица 5.11. Использование старшинства операторов


Выражение Последовательность действий
3 + 2 * 5 умножение, сложение
count / 5 MOD 3 деление, модуль
+4 * 3 – 1 знак, умножение, вычитание
(1000h – 30h) * 4 вычитание, умножение
–((count MOD 5) + 2) * 4 модуль, сложение, знак, умножение

Операторы также имеют свойство, называемое ассоциативностью, которое необходи&


мо использовать, если в выражении встречаются операторы одинакового уровня стар&
шинства. Все операторы, представленные в табл. 5.8, выполняются слева направо. Таким
образом, в следующих выражениях деление выполняется перед умножением, а вычита&
ние &&&& перед сложением.
count / 5 * 3
count - 2 + 3

Операторы OFFSET, SEG, PTR, LABEL и EVEN


Оператор OFFSET
Этот оператор возвращает расстояние метки или переменной от начала сегмента. При&
емный операнд должен быть 16&разрядным регистром, как показано в следующем примере.
MOV BX,OFFSET count ; BX указывает на count.
Оператор BX указывает на count, так как в BX находится адрес переменной count. Ес&
ли регистр содержит адрес, его называют указателем. В следующем примере смещение
каждого элемента массива перемещается в индексный и базовый регистры. Предполага&
ется, что bList размещается по адресу 0000.
.DATA
bList DB 10h,20h,30h,40h
wList DW 1000h,2000h,3000h
.code
MOV DI,OFFSET bList ; DI = 0000
MOV BX,OFFSET bList+1 ; BX = 0001
MOV SI,OFFSET wList+2 ; SI = 0006

150 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 150
отсутствует. 151

Оператор SEG
Оператор SEG возвращает сегментную часть адреса метки или переменной. Это его
свойство обычно используется, когда переменная находится в сегменте, отличном от того,
на который указывает регистр DS. В следующем примере текущее значение сегмента DS
помещается в стек, значение изменяется на значение сегмента, в котором находится пе&
ременная array, а затем восстанавливается исходное значение DS.
PUSH DS ; Сохранение DS.
MOV AX,seg array ; В DS помещается значение сегмента массива array.
MOV DS,AX
MOV BX,OFFSET array ; Получение смещения array.
... ; Работа с массивом array.
POP DS ; Восстановление DS.

Оператор PTR
Оператор PTR изменяет размер операнда, принятый по умолчанию. В командах, где
размер операнда не определен однозначно, оператор PTR может явно установить размер. PTR
может использоваться в сочетании со стандартными типами данных: BYTE, SBYTE, WORD,
SWORD, DWORD, SDWORD, FWORD, QWORD и TBYTE. Например:
MOV AL,BYTE PTR count
MOV AX,WORD PTR newVal
MOV EAX,DWORD PTR listPointer ; Требует директивы .386.
Часто размер операнда нельзя установить, исходя из контекста команды. Рассмотрим
следующую команду, которая приводит к ошибке ‘‘операнд должен иметь размер’’ (Operand
must have size).
INC [BX] ; Косвенный операнд.
Ассемблер не может определить, является ли указатель BX байтом или словом. В дан&
ной ситуации оператор PTR поможет внести ясность.
INC BYTE PTR [BX]
Оператор PTR также полезен, когда необходимо изменить размеры операнда, уста&
новленные по умолчанию. Предположим, есть 32&разрядное двойное слово и необходимо
загрузить старшую половину в регистр DX, а младшую &&&& в регистр AX (для двойного сло&
ва младшая часть имеет меньший адрес). В приведенном ниже примере будет зафиксиро&
вана синтаксическая ошибка.
.DATA
val32 DD 12345678h
.code
MOV AX,val32 ; Получить младшую часть слова (Ошибка).
MOV DX,val32+2 ; Получить старшую часть слова (Ошибка).
Однако в следующем случае ассемблер сочтет все правильным, так как оператор PTR
изменит значение по умолчанию переменной val32.
MOV AX,WORD PTR val32 ; AX = 5678h.
MOV DX,WORD PTR val32+2 ; DX = 1234h.

Директива LABEL
Директива LABEL разрешает установить метку и присвоить атрибут размера без раз&
мещения в памяти. Любой из стандартных атрибутов размера (например, BYTE, WORD,
DWORD, QWORD и т.д.) может использоваться с LABEL.

Глава 5. Фундаментальные понятия языка ассемблера 151

Стр. 151
В общем случае LABEL создает альтернативное имя и атрибут размера для сущест&
вующих переменных в сегменте данных. Это весьма удобный путь удовлетворения требо&
ваний языка ассемблера для переменных, которые должны соответствовать другим опе&
рандам в команде. В следующем примере метка val16 объявлена перед переменной
val32 и имеет атрибуты слова.
.DATA
val16 LABEL WORD
val32 DD 12345678h
.code
MOV AX,val16 ; AX = 5678h.
MOV DX,vall6+2 ; DX = 1234h.
Обратите внимание, что команда MOV ссылается на val16, которая является только
псевдонимом для того же пространства памяти, в котором находится переменная val32.

Директивы EVEN и EVENDATA


Директива EVEN выравнивает следующую команду в сегменте кодов по четным 16&раз&
рядным смещениям. Это повышает скорость работы программы для процессоров, ис&
пользующих 16&разрядную шину данных. В следующем примере однобайтовая команда
NOP (90h) вставляется в программу, где используется директива EVEN. Последующие ко&
манды начинаются с четного адреса.
0000 MOV AX,@DATA
0003 MOV DS,AX
0005 EVEN ; Вставляется байт с командой 90h.
0006 MOV BX,OFFSET array
Та же последовательность наблюдается в следующем примере: директива EVENDATA
вставляет нулевой байт (0) перед переменной array, что приводит к изменению смеще&
ния переменной на 0004 вместо 0003.
0000 .DATA
0000 str1 DB 3 DUP('X')
0003 EVENDATA
0004 array DW 10 DUP(0FFFFh)

Операторы типа и размера


Оператор SHORT
Оператор SHORT чаще всего используется с командой JMP, когда известно, что пере&
ход меньше или равен 127 байт от текущего положения. Это позволяет ассемблеру сгене&
рировать короткую однобайтовую команду перехода вместо двухбайтовой.

Оператор TYPE
Оператор TYPE возвращает размер в байтах одного элемента переменной. Например,
8&разрядная переменная имеет размер, равный 1, а 32&разрядная переменная возвратит
размер 4, массив байтов возвратит 1, а массив из 16&разрядных целых чисел возвратит 2.
Тип ближней метки будет определять значение FFFFh, а тип удаленной &&&& FFFEFFFFh.
На примерах показано типичное использование этих операторов.
.DATA
varl DB 20h
var2 DW 1000h

152 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 152
отсутствует. 153

var3 DD ?
var4 DB 10,20,30,40,50
msg DB 'Файл не найден',0
.code
L1: MOV AX,TYPE var1 ; AX = 0001.
MOV AX,TYPE var2 ; AX = 0002.
MOV AX,TYPE var3 ; AX = 0004.
MOV AX,TYPE var4 ; AX = 0001.
MOV AX,TYPE msg ; AX = 0001.
MOV AX,TYPE L1 ; AX = FFFF.

Оператор LENGTH
Оператор LENGTH подсчитывает число отдельных элементов в переменной, которая
может быть определена с помощью оператора DUP. Если DUP не используется, то опера&
тор LENGTH возвращает значение 1. Если используются вложенные операторы DUP, то
учитывается только внешний оператор DUP, как это показано ниже.
.DATA
var1 DW 1000h
var2 DB 10,20,30
array DW 32 DUP(0)
array2 DW 5 DUP(3 DUP(0))
message DB 'Файл не найден',0
.code
MOV AX,LENGTH var1 ; LENGTH = 1.
MOV AX,LENGTH var2 ; LENGTH = 1.
MOV AX,LENGTH array ; LENGTH = 32.
MOV AX,LENGTH array2 ; LENGTH = 5.
MOV AX,LENGTH message ; LENGTH = 1.

Оператор SIZE
Оператор SIZE возвращает значение, получаемое после умножения значений LENGTH
и TYPE. Например, массив из 16&разрядных значений имеет TYPE, равный 2, и LENGTH &&&& 32.
Тогда значение SIZE будет 64.
intArray DW 32 DUP(0) ; SIZE = 64
Следующая строка имеет SIZE, равный 1, так как ее LENGTH и TYPE оба равны 1.
message DB 'Это сообщение',0

Команды JMP и LOOP


Процессор автоматически выполняет последовательность команд. Как только коман&
да выполнена, процессор инкрементирует указатель команд для получения смещения
следующей команды и загружает команду во внутреннюю очередь. Но не всегда все так
просто. Если встречаются команды IF, GOTOS или LOOPS, то контроль передается в дру&
гое место программы.
Передача управления, или ветвление, необходимы для того, чтобы изменить последо&
вательный порядок, в котором выполняются команды. Такие команды есть в любом язы&
ке программирования. Они делятся на две категории.
Команды безусловного перехода. Программа начинает выполнение с новой ветви
в любом случае; новое значение загружается в указатель команд и выполнение на&
чинается с нового адреса (например &&&& команда JMP).

Глава 5. Фундаментальные понятия языка ассемблера 153

Стр. 153
Команды условного перехода. Программа начинает выполнение с новой ветви
только в том случае, если определенное условие является истиной. Intel предос&
тавляет широкий диапазон команд условного перехода, с помощью которых мож&
но создать сложные логические структуры. Процессор определяет условия
ложь/истина исходя из анализа регистра CX и регистра флагов.

Команда JMP
Команда JMP заставляет процессор продолжать выполнение с нового места програм&
мы. Новое место может быть отмечено меткой, которую процессор преобразовывает
в адрес. Например, с помощью команды JMP легко создавать циклические конструкции:
top:
...
...
jmp top
Команда JMP может совершать переход из текущей процедуры, от одной процедуры
к другой, от одного сегмента к другому, полностью выходя за пределы текущей програм&
мы или в любое место памяти RAM или ROM. Принципы структурного программирования
не поддерживают такие переходы, но они иногда необходимы при прикладном програм&
мировании на системном уровне.
При программировании для 32&разрядного режима нет необходимости думать о сег&
ментах, из которых состоит программа, что упрощает написание кода. Но при програм&
мировании для 16&разрядного режима необходимо учитывать расстояние, на которое
может происходить переход. (Подробнее об этом читайте в главе 11.)

Команда LOOP
Команда LOOP &&&& самый удобный способ, с помощью которого осуществляется по&
вторение блока команд определенное количество раз. Регистр CX автоматически исполь&
зуется как счетчик, который декрементируется при каждом повторении блока команд.
Синтаксис команды будет такой:
LOOP пункт назначения
При использовании команды LOOP сначала из регистра CX вычитается единица. Если
результат не равен нулю, контроль передается в пункт назначения. При программирова&
нии для 16&разрядного режима пункт назначения должен находиться на расстоянии от
-128 до +127 от текущего положения (ассемблер использует внутренний счетчик для
определения расстояния). В следующем примере цикл повторяется пять раз.
MOV CX,5 ; В регистре CX считаются циклы.
start:
.
LOOP start ; Переход к метке start.
Если значение CX становится равным нулю, то блок циклических команд не выпол&
няется и управление передается команде, следующей сразу за блоком. Флаг нуля не ис&
пользуется, хотя CX = 0.
В следующем примере к регистру AX добавляется 1 при каждом цикле. Когда цикл за&
канчивается, будут получены значения AX = 5 и CX = 0.
MOV AX,0 ; Установить AX в 0.
MOV CX,5 ; Счетчик циклов.

154 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 154
отсутствует. 155

top:
INC AX ; Добавить 1 к AX.
LOOP top ; Повторять до CX = 0.
Следующий фрагмент программы выдает на печать букву А 960 раз, используя коман&
ду LOOP. Значение, помещенное в счетчик CX, является произведением 12 строк экрана
с шириной 80 символов.
MOV EX,12*80 ; Установить в счетчике значение 960.
next:
MOV AH,2 ; Функция: отобразить символ.
MOV DL,'A' ; Отображение на экране буквы A.
INT 21h ; Вызов функции DOS.
LOOP next ; Декремент CX и повторение.
Начинающие программисты часто забывают установить значение счетчика CX, в итоге
там оказывается значение 0. В результате этой ошибки цикл повторяется 65535 раз, так
как после декремента нуля в CX получится значение FFFFh.
Будьте внимательны и не модифицируйте регистр CX во время исполнения цикла. В сле&
дующем примере CX инкрементируется внутри повторяющегося блока команд. Значение
счетчика никогда не станет равным нулю, и цикл никогда не закончится.
MOV AH,2 ; Функция DOS: отображение символа.
MOV CX,10 ; Счетчик циклов.
MOV DL,'*' ; Отображение символа.
top:
INT 21h ; Вызов функции DOS.
INC СX ; Добавляем 1 к CX (Этого делать нельзя!).
LOOP top ; Повторение до CX = 0.

Нельзя модифицировать регистр CX во время исполнения цикла.

Команды LOOP, LOOPW, LOOPD


Команда LOOPD для процессора 80386 и более современных моделей разрешает ас&
семблеру использовать в качестве счетчика циклов регистр ECX. Это позволяет создавать
циклы с количеством повторений 4294 967 295(2 −1) раз. Например:
32

MOV ECX,A0000000h
L1:
.
LOOPD L1 ; Использование ECX как счетчика циклов.
Если программа транслируется в 32&разрядном режиме, команда LOOP автоматически
использует регистр ECX в качестве счетчика циклов. Команду LOOPW можно также ис&
пользовать для отмены установок по умолчанию с применением регистра CX.

Косвенная адресация
Косвенные операнды
Косвенный операнд &&&& это регистр, который содержит смещение данных в памяти.
Когда смещение переменной помещено в регистр, оно становится указателем. Для пере&
менной, имеющей одно значение, это не дает никаких преимуществ, однако для массивов

Глава 5. Фундаментальные понятия языка ассемблера 155

Стр. 155
указатель можно инкрементировать, выбирая последовательные элементы. 16&разрядные
регистры SI, DI, BX и BP можно использовать в качестве косвенных операндов. Директи&
вы .386, .486 или .586 позволяют использовать любой 32&разрядный регистр общего
назначения в качестве косвенного операнда (исключение составляет ESP, о котором речь
пойдет далее).
Например, если в памяти создается строка по адресу 0200, то, помещая в регистр BX
ее смещение, можно обеспечить доступ к любому элементу строки. Символ F в следую&
щем примере имеет смещение 5 от начала строки.
.DATA
aString DB "ABCDEFG"
.code
MOV BX,OFFSET aString ; BX = 0200.
ADD BX,5 ; BX = 0205.
MOV DL,[BX] ; DL = 'F'.

Сегменты по умолчанию
Смещение, получаемое с использованием косвенных операндов, отсчитывается от
начала сегмента данных (при 16&разрядном программировании его значение находится
в регистре DS), за исключением ситуации, когда регистры BP и EBP являются частью кос&
венного операнда. В этом случае используется сегмент стека (регистр SS). Полагая, что
сегмент данных и сегмент стека занимают в памяти различные места, третья и вторая ко&
манды MOV будут иметь доступ к различным местам памяти.
MOV SI,BP ; SI и BP равны.
MOV DL,[SI] ; Смотрим в сегменте данных.
MOV DL,[BP] ; Смотрим в сегменте стека.

Изменение значений по умолчанию


Иногда возникает необходимость обеспечить косвенный доступ к данным в сегмен&
тах, отличных от DX. Можно изменить (переопределить сегмент) установки по умолча&
нию, чтобы использовать нужный сегмент.
MOV AL,CS:[SI] ; Смещение от CS.
MOV EAX,ES:[EDI] ; Смещение от ES.
MOV BX,FS:[EDX] ; Смещение от FS.
MOV DL,SS:[DI] ; Смещение от SS.
MOV AX,GS:[ECX] ; Смещение от GS.
Кроме того, если есть желание использовать BP или EBP для доступа к данным, опре&
деляемым сегментами DS, CS или ES, необходимо использовать переопределение.
MOV DL,DS:[BP] ; Смещение от DS.
MOV AL,ES:[EBP] ; Смещение от ES.
MOV DL,CS:[BP] ; Смещение от CS.
MOV AL,FS:[EBP] ; Смещение от FS.
Во фрагменте программы, приведенной в листинге 5.2, рассчитывается сумма трех
8&разрядных чисел с использованием косвенной адресации. Если сумма чисел будет
больше, чем FFh, произойдет переполнение регистра AL и результат будет неверным. До&
полнительный бит не переносится автоматически в регистр AH. Чтобы убедиться в этом,
оттранслируйте и используйте пошаговый режим для представленной программы со сле&
дующими значениями.
aList DB 50h,60h,70h

156 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 156
отсутствует. 157

Листинг 5.2. Сложение нескольких 8*разрядных целых чисел


.DATA
aList DB 10h,20h,30h
sum DB 0
.code
MOV BX, OFFSET aList
MOV AL,[BX] ; AL = l0h.
INC BX
ADD AL,[BX] ; AL = 30h.
INC BX
ADD AL,[BX] ; AL = 60h.
MOV SI,OFFSET sum ; Получаем смещение суммы.
MOV [SI],AL ; Сохраняем сумму.

Тот же самый результат можно получить, используя дополнительное смещение (сдвиг)


в регистре BX, когда производится доступ ко второму и третьему числам. Таким образом
можно исключить необходимость в отдельных командах для инкремента BX. Примите во
внимание, что сумма размещается непосредственно за тремя этими числами.
MOV BX,OFFSET aList
MOV AL,[BX] ; Первое число.
ADD AL,[BX+1] ; Второе число.
ADD AL,[BX+2] ; Третье число.
MOV [BX+3],AL ; Сохраняем сумму.
В следующем фрагменте (листинг 5.2) используется косвенная адресация для сложе&
ния нескольких 16&разрядных чисел. Существенное различие между этим примером и при&
мером из предыдущего листинга 5.1 (сложение 8&разрядных целых чисел) заключается
в размерах операндов. Смещение должно изменяться на 2 при доступе к каждому после&
довательному члену.

Листинг 5.3. Сложение 16*разрядных целых чисел


.DATA
wordList DW 1000h,2000h,3000h
sum DW 0
.code
MOV BX,OFFSET wordList
MOV AX,[BX] ; Первое число.
ADD AX,[BX+2] ; Второе число.
ADD AX,[BX+4] ; Третье число.
MOV [BX+6],AX ; Сохраняем сумму.

Использование команды LOOP для отображения строки


В программе из приведенного ниже листинга комбинируется косвенная адресация
с командой LOOP для отображения на экране строки символов. Каждый символ помещается
в регистр DL и отображается с помощью прерывания INT 21. В выражении ($ —строка),
которое используется для расчета длины строки, вычитается начальное смещение строки
из текущего значения счетчика команд ($).

Листинг 5.4. Вывод строки для 16*разрядного режима


.DATA
string DB "Это строка."
COUNT = ($ - string) ; Расчет длины строки.

Глава 5. Фундаментальные понятия языка ассемблера 157

Стр. 157
.code
...
MOV СX,COUNT ; Счетчик циклов.
MOV SI,OFFSET string
L1:
MOV AH,2 ; Вывод символа на экран.
MOV DL,[SI] ; Получить символ.
INT 21h
INC SI ; Указать на следующий символ.
LOOP L1 ; Декремент CX, повторение.

В листинге 5.4 цикл используется для вывода каждого символа, составляющего стро&
ку. Эта же программа для 32&разрядного режима будет выглядеть следующим образом.

Листинг 5.5. Вывод строки для 32*разрядного режима


TITLE Вывод на консоль Win32 (Console1.asm)
; В этой программе используются функции консоли Win32:
; GetStdHandle, ExitProcess, WriteConsole
.386
.MODEL flat, stdcall
INCLUDE E:\MASM615\INCLUDE\gWin.inc
.data
consoleHandle DWORD 0 ; Дескриптор стандартного выходного устройства.
bytesWritten DWORD ? ; Количество записанных байт.
string DB " Это строка."
COUNT = ($ - string)
.code
main PROC
; Получить дескриптор стандартного выходного устройства.
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov consoleHandle,eax
; Отобразить строку на консоли.
INVOKE WriteConsole,
consoleHandle,
ADDR string,
COUNT,
ADDR bytesWritten,
0
; Закончить выполнение.
Exit
main ENDP
END main

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


дельным устройствам (нельзя, например, непосредственно вызвать прерывание INT 21h).
Все это скрыто в используемых функциях и программисту необходимо только знать на&
значение функции и используемые параметры.

Использование команды LOOP для суммирования массива целых чисел


В листинге 5.6 программа суммирует значения массива, используя косвенную адресацию
и команду LOOP. Сначала регистр&аккумулятор AX устанавливается в нуль, при этом регистр DI
содержит смещение массива. Регистр CX содержит счетчик цикла, равного количеству чисел.
В данном случае это значение не может быть больше размера сегмента данных. Значение сум&
мы размещается в 16&разрядном аккумуляторе AX и может достигать максимума.

158 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 158
отсутствует. 159

Листинг 5.6. Суммирование массива целых чисел


TITLE Суммирование целых чисел
.386
INCLUDE G:\MyASM\MASM\INCLUDE\Study32.inc
.DATA
intarr WORD 1,2,3,4
cnt = ($-intarr)/2
.CODE
main PROC
MOV EAX, 0 ; Аккумулятор равен нулю.
MOV EDI, OFFSET intarr ; Адрес массива.
MOV ECX, cnt ; Счетчик циклов.
L1:
ADD AX,[EDI] ; Сложить числа.
ADD EDI,2 ; Указать на следующее число.
LOOP L1 ; Повторять до CX = 0.
INVOKE WriteInt ; Вывод значения на экран.
INVOKE Crlf ; Перевод каретки.
INVOKE WaitMsg ; Вывод запроса на окончание.
Exit ; Выход из программы.
main ENDP
END main

На рис. 5.3 показано окно CPU отладчика, где можно увидеть исходный текст, со&
стояние регистров процессора и дамп памяти.

Рис. 5.3. Окна отладчика для программы сложения целых чисел

В регистре AX при каждой итерации будет находиться очередная сумма последова&


тельности чисел 1, 2, 3, 4. Эти числа находятся в дампе памяти.

Глава 5. Фундаментальные понятия языка ассемблера 159

Стр. 159
Базовые и индексные операнды
Базовый и индексный операнды по существу аналогичны. Значение регистра (базового
или индексного) добавляется к смещению для получения эффективного адреса перемен&
ной. Постоянное значение дополнительного смещения или сдвига может быть, напри&
мер, смещением переменной. Различие между базовым и индексным операндом состоит
в том, что для базового операнда используются регистры базы EBX или EBP, а для ин&
дексного &&&& ESI или EDI. Ассемблер допускает различные формы записи, как показано
в табл. 5.12 для 16&разрядных регистров.

Таблица 5.12. Формы записи индексных операндов


Регистр добавляется к смещению Регистр добавляется к константе
MOV DX,array[BX] MOV AX,[BX+rowval]
MOV DX,[DI+array] MOV DX,[BP+4]
MOV DX,[array+SI] MOV DX,2[SI]

Если создать массив байтов по адресу 0200 и установить BX равным 5, то BX будет


указывать на число со смещением 5 от начала массива.
.DATA
rowsize = 5
array DB 2,16,4,22,13,19,42,64,44,88
.code
MOV BX,rowsize
MOV AL,array[BX] ; AL = 19.

32*разрядные регистры
Если команды для процессора 386 доступны (применяется директива .386), то мож&
но использовать любой регистр общего назначения как базовый или индексный операнд.
.386 ; Доступны 32-разрядные регистры.
MOV AX,[EBX+3] ; 16-разрядный операнд.
MOV DL,string[EDX] ; 8-разрядный операнд.
MOV ECX,6[ESI] ; 32-разрядный операнд.
MOV EAX,myList[EDI] ; 32-разрядный операнд.
При программировании в 16&разрядном режиме будьте внимательны и не используйте
32&разрядные значения индексных регистров, которые могут превысить допустимый
размер сегмента (0 -FFFFh).
MOV ESI,10000h
MOV AL,[ESI] ; выход за диапазон 16-разрядного режима

Использование 32&разрядных значений индексных регистров при программировании в 16&


разрядном режиме не рекомендуется.

Базо*индексные операнды
Базо&индексные операнды добавляют значение регистра базы к значению индексного
регистра для получения смещения. Такой тип операнда часто используется при необхо&
димости доступа к двумерным массивам, когда базовый регистр содержит смещение
строки, а индексный &&&& смещение столбца. Программа, представленная в листинге 5.7,
использует массив 8&разрядных целых чисел. Если этот массив размещен по адресу 0150

160 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 160
отсутствует. 161

и регистр BX установлен на начало второй строки, а регистр SI указывает смещение


третьего столбца, то смещение необходимой ячейки будет BX+SI=0157 (рис. 5.4).
0150 0155 0157
10 20 30 40 50 60 70 80 ... ... ...

array BX = 5

Рис. 5.4. Расчет смещения ячейки

Листинг 5.7. Пример использования двумерного массива


.DATA
ROWSIZE = 5
array DB 10h,20h,30h,40h,50h,60h,70h,80h,90h
DB 0A0h,0B0h,0С0h,0D0h,0E0h,0F0h
.code
MOV BX,OFFSET array ; Смещение array равно 0150.
ADD BX,ROWSIZE ; Выбор второй строки.
MOV SI,2 ; Выбор третьего столбца.
MOV AL,[BX + SI] ; Получаем смещение 0157.

Поскольку BX и BP являются 16разрядными регистрами базы, а SI и DI  индекс


ными регистрами, возникает одно важное исключение: нельзя комбинировать два базо
вых или два индексных регистра.
MOV AL,[BP+BX] ; Ошибка.
MOV AL,[SI+DI] ; Ошибка.
В следующем примере показано использование базоиндексных операндов. Регистры
отличаются своим расположением. В последних двух строках процессор присваивает зна
чения различным сегментам в зависимости от того, в каком месте находится регистр EBP.
.386
MOV AL,[EBX+ESI] ; Смещение от DS.
MOV BX,[ECX][EDX] ; Смещение от DS.
MOV AX,[EBP+ESI] ; Смещение от STACK.
MOV AX,[ESI+EBP] ; Смещение от DS.

Базоиндексные операнды с дополнительным смещением


Эффективный адрес операнда может быть получен комбинацией базового регистра,
индексного регистра и дополнительного смещения (сдвига). Это можно записать по
разному:
MOV DX,array[BX][SI]
MOV AX,[BX+SI+array]
ADD DL,[BX+SI+3]
SUB CX,array[BP+SI]
Как и базоиндексные операнды, этот тип операндов хорошо подходит для двумерных
таблиц. Имя массива можно использовать как смещение для операнда, базовый регистр
может содержать смещение строки таблицы, а индексный регистр  смещение столбца
в строке. В следующем примере BX содержит расстояние в байтах между второй строкой
и началом таблицы, а SI включает расстояние от начала строки до третьего столбца.

Глава 5. Фундаментальные понятия языка ассемблера 161

Стр. 161
rowsize = 5
MOV BX,rowsize ; Выбираем вторую строку.
MOV SI,2 ; Выбираем третий столбец.
MOV AL,array[BX+SI] ; Получаем величину смещения 0157.
На рис. 5.5 показаны значения регистров BX и SI для двумерного массива.
0150 0155 0157
10 20 30 40 50 60 70 80 90 A0

[BX] [BX+SI]

Рис. 5.5. Использование регистров для получения


значений массива

Команды процессоров 80386 и более


старших моделей
Команды MOVZX и MOVSX
Команда MOVZX для процессора 80386 перемещает 8 или 16разрядный операнд в боль
ший 16 или 32разрядный регистр. Незаполненные разряды регистраприемника при
нимают нулевое значение. В следующем примере происходит перемещение BL в AX с по
мощью команды MOVZX. Старшие разряды регистра AX заполняются нулями.
MOV BL,22h
MOVZX AX,BL ; AX = 0022h.
При перемещении командой MOVZX 16разрядного операнда в регистр EDX старшие
16 разрядов регистра станут равны нулю.
.DATA
var16 DW 1234h
.code
MOVZX EDX,var16 ; EDX = 00001234h.
Подобным образом команда MOVSX перемещает операнд в младшие разряды регист
раполучателя, но при этом старшие разряды CX заполняются значением знакового раз
ряда переменной FEh.
.DATA
var8 DB -2 ; FEh.
.code
MOVSX CX,var8 ; CX = FFFEh.
Например, если попытаться сложить два регистра разного размера, как показано ниже
MOV AX,5
MOV BL,-10
ADD AX,BL,
то ничего не получится, так как команда ADD допускает только сложение операндов
одинакового размера. Но если сделать приведение размера с помощью команды MOVSX,
то суммирование будет происходить корректно.
MOV AX,5
MOV BL,-10
MOVSX BX,BL
ADD AX,BX

162 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 162
отсутствует. 163

Команда XADD
Команда XADD для процессора 80486 суммирует значения операндов отправителя
и получателя и сохраняет значение в операнде&приемнике. В то же время исходное зна&
чение операнда&приемника перемещается в операнд&получатель. Используются флаги
переполнения, знака, нуля, дополнительного переноса, четности и переноса. Например,
можно добавить значение регистра BX к AX, сохранив значение, которое было в AX.
MOV AX, 1000h
MOV BX, 2000h
XADD AX, BX ; AX = 3000h, BX = 1000h.
Одной командой XADD можно заменить следующие три команды.
PUSH AX
ADD AX,BX
POP BX

Процедуры
Представленные в книге примеры содержали последовательности команд, выполняв&
шиеся один раз для решения определенной задачи. Однако если возникает необходимость
выполнить ту же последовательность команд в другом месте программы, то придется или
продублировать последовательность команд, или использовать обращение к процедурам.
Обычно дублирование неэффективно и значительно удлиняет программу, поэтому лучше
оформить повторяющуюся последовательность команд как процедуру. Процедура (или под&
программа) представляет собой совокупность команд, которая написана один раз, но при
необходимости может быть использована в любом месте программы.
Процесс передачи управления из основной части программы в процедуру называется
вызовом. При вызове процедуры микропроцессор исполняет ее команды, а затем воз&
вращается к тому месту, откуда был сделан вызов.
Команды, обеспечивающие исполнение процедур, должны выполнить три функции:
сохранить содержимое указателя команд IP. Когда процедура исполнена, находя&
щееся в этом указателе значение (адрес возврата) используется микропроцессором
для возврата к месту вызова;
заставить микропроцессор начать исполнение процедуры;
использовать сохраненное содержимое указателя команд IP для возврата в про&
грамму и обеспечить продолжение ее исполнения с этого места.
Все эти функции выполняются двумя командами: CALL (вызвать процедуру) и RET (возв&
ратиться из процедуры). Команда CALL осуществляет функции запоминания адреса возвра&
та и передачи управления процедуре.
Команда CALL имеет следующий формат.
CALL имя,
где имя &&&& имя вызываемой процедуры (т.е. адрес ее начала).
После сохранения адреса возврата команда CALL загружает смещение адреса метки
имя в указатель команд IP.
Команда RET заставляет микропроцессор возвратиться из процедуры в программу,
вызвавшую эту процедуру, восстанавливая все, что сохранила команда CALL. Команда
RET обязательно должна быть последней командой процедуры, исполняемой микропро&
цессором, но это не значит, что команда RET должна стоять в конце процедуры &&&& она
должна исполняться последней. Команда RET извлекает из стека адрес возврата.

Глава 5. Фундаментальные понятия языка ассемблера 163

Стр. 163
Например, для вызова процедуры my_proc из некоторого места вашей программы
может использоваться следующая последовательность команд.
04F0 CALL my_proc ; Вызвать процедуру.
04F3 NEXT: MOV AX,BX ; Вернуться из процедуры.
0500 my_proc PROC ; Начало процедуры.
0500 MOV CL,6 ; Первая команда процедуры.
О51Е RET ; Вернуться в вызвавшую программу.
051F my_proc ENDP ; Конец процедуры.
При исполнении команды CALL микропроцессор помещает в стек смещение адреса
метки NEXT (04F3H), затем загружает смещение адреса процедуры my_proc (0500H) в ука&
затель команд IP.
Содержимое регистра IP изменилось, поэтому микропроцессор продолжит исполне&
ние команды с новым смещением адреса. В нашем примере такой командой будет MOV CL,6.
Когда микропроцессор обнаруживает команду RET, он извлекает адрес возврата из
стека и помещает его в указатель команд IP. Это заставляет его возобновить исполнение
команды, имеющей метку NEXT.
Помимо прямого вызова команды CALL, о котором говорилось выше, можно делать
косвенные вызовы процедуры через регистр или ячейку памяти. При косвенных вызовах
через ячейку памяти микропроцессор извлекает значение указателя команд IP для про&
цедуры из сегмента данных, если только не используется регистр ВР или не указана заме&
на сегмента. Если для адресации ячейки памяти используется регистр ВР, то микропро&
цессор извлекает значение указателя команд IP из сегмента стека.
Процедуру можно вызвать косвенно, используя переменную размером в двойное сло&
во, например:
CALL DWORD PTR[BX]
CALL mem_dword
CALL DWORD PTR SS:variable_mame[SI]
Здесь первые две команды CALL извлекают адреса процедур из сегмента данных, а по&
следняя &&&& из сегмента стека.
Процедура может сама вызывать другие процедуры. Например, процедура чтения сим&
вола с клавиатуры может его декодировать и в зависимости от результата вызвать одну из
дополнительных процедур. Вызов одной процедуры из другой называется вложением проце%
дур. Так как каждая команда CALL помещает в стек два или четыре байта адреса, то число
уровней вложения ограничено только размером сегмента стека, но поскольку сегмент стека
может иметь размер до 64 Кбайт, то возможности вложения практически не ограничены.
Помимо команды CALL можно использовать директиву INVOKE, которая расширяет воз&
можности этой команды и позволяет удобным способом передавать в процедуру аргументы.
Более подробно использование процедур будет рассмотрено в следующей главе.

Резюме
В данной главе рассмотрены основные понятия языка ассемблера. Необходимо четко
представлять, какие бывают типы операндов, знать все операторы, основные директивы
и команды, без которых нельзя составить даже простейшую программу. В главе также
описаны базовые принципы построения программы и приведено несколько примеров
полностью работоспособных программ, внимание акцентируется на тех моментах, кото&
рые обычно не учитывают начинающие программисты.

164 Глава 5. Фундаментальные понятия языка ассемблера

Стр. 164
отсутствует. 165

Контрольные вопросы
1. Что такое переменная и как ее можно интерпретировать?
2. Для чего используются директивы размещения данных?
3. Сколько значений можно инициализировать с помощью одной директивы раз!
мещения данных?
4. Как можно представить строку символов?
5. Для чего используется оператор DUP?
6. Что такое указатель?
7. Как ассемблер сохраняет данные в памяти?
8. Что такое константа и как ее можно создать?
9. Можно ли использовать имя для строки?
10. Что определяет директива .MODEL?
11. Назовите шесть различных моделей памяти.
12. Для чего нужно разбивать программу на сегменты?
13. При какой модели памяти сегменты не используются?
14. Каково назначение оператора SIZE?
15. Зачем нужны директивы установки типа процессора?
16. Предположим, что указатель SP инициализирован значением 0100. Какой будет
абсолютный адрес первого слова, помещенного в стек, если сегмент кодов загру!
жен по абсолютному адресу 18400h?
17. Когда возникает ошибка несоответствия типов?
18. Что такое знаковое переполнение?
19. Какие типы операндов используются в ассемблере?
20. Что такое косвенный операнд?
21. Что такое выражение?
22. Какие группы операторов вы знаете?
23. Для каждой из приведенных переменных укажите значения TYPE, LENGTH и SIZE.

Переменная TYPE LENGTH SIZE


var1 DD 5 DUP(0)
var2 DW 10 DUP(0FFFFh)
var3 DB 20
msg DB "Привет всем",0

24. Почему оператор WORD PTR необходим в команде ADD WORD PTR [SI],5?

Глава 5. Фундаментальные понятия языка ассемблера 165

Стр. 165
Глава 6

Процедуры и прерывания
В этой главе...

Операции со стеком
Процедуры
Параметры процедур
Прерывания
Ввод на уровне BIOS
Видеоконтроль на уровне BIOS
Резюме
Контрольные вопросы

В предыдущей главе уже было кратко рассказано о процедурах и показан пример их ис&
пользования. Необходимость использования процедур возникает, как только программа
становится большой и трудно понимаемой, и поэтому ее приходится разбивать на от&
дельные логические фрагменты. В хорошо разработанной программе каждая процедура
выполняет отдельную задачу независимо от остальной части программы. Каждая проце&
дура подобна отдельной прикладной программе, которую можно использовать в разных
ситуациях и вызывать много раз.
Программу любого размера сложно писать как единую последовательность команд от
начала до конца, поэтому необходимо разбить ее на отдельные, компактные и хорошо пони&
маемые модули, что значительно облегчит работу по написанию, отладке и сопровождению.
Язык ассемблера не навязывает создание структурированных программ, но использование
принципов структурного программирования при написании программ на языке ассемблера
позволяет создавать простые для понимания и легко модернизируемые программы.
Большая часть этой главы посвящена специальному типу процедур, или подпро&
грамм, позволяющих программе взаимодействовать с другими устройствами. Эти под&
программы оказывают существенную помощь при работе с консолью, диском, принте&
ром при вводе&выводе или контроле времени и размещении памяти. Эти подпрограммы
позволяют не вдаваться в детали программирования на уровне аппаратуры и обеспечи&
вают работу программы на любых устройствах с процессорами архитектуры x86.

Операции со стеком
Стек &&&& это специальный буфер в оперативной памяти, который используется для вре&
менного хранения адресов и данных. Стек размещается в сегменте стека. К каждой 16&раз&
рядной (32&разрядной) ячейке стека можно обратиться с помощью регистра SP (ESP),

Стр. 166
отсутствует. 167

называемого указателем стека. Указатель стека содержит адрес последнего элемента, по
мещенного в стек. Последнее значение, добавленное в стек, является также первым значе
нием, которое может быть извлечено из стека. Для обозначения таких структур используют
аббревиатуру LIFO (Last In First Out), что означает ‘‘последним зашел  первым вышел’’.

Команда PUSH
Эта команда копирует значение в стек. Когда новое значение помещается в стек, указа
тель стека (E)SP декрементируется до того, как значение будет помещено в стек (SP всегда
должен указывать на последнее помещенное значение). Использование команды PUSH
показано в следующем примере.
MOV AX,0A5h
PUSH AX
Команда PUSH не изменяет содержимое регистра AX, она только копирует значение
AX в стек.
Предположим, что значения регистров BX и CX равны 0001 и 0002. Ниже приведены
команды помещения их в стек.
PUSH BX ; Помещает BX в стек.
PUSH CX ; Помещает CX в стек.
Значения 0001 и 0002 будут помещены в стек, как показано на рис. 6.1.
Старшие адреса

006A
00B8
0001
0002 SP (указатель стека)

Младшие адреса

Рис. 6.1. Размещение значений в стеке

Команда PUSH декрементирует указатель стека (E)SP и копирует 16 или 32разряд
ные регистры или операнды памяти в стек по адресу, на который указывает указатель
стека. При использовании процессора 80286 и более поздних моделей в стек можно по
местить непосредственное значение. Ниже приведено несколько примеров.
PUSH AX ; Поместить в стек 16-разрядный регистр.
PUSH ECX ; Поместить в стек 32-разрядный регистр.
PUSH memval ; Поместить в стек 16-разрядный операнд памяти.
PUSH 1000h ; Поместить в стек значение: только 80286 и выше.

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

Глава 6. Процедуры и прерывания 167

Стр. 167
Команда POP копирует содержимое ячейки, на которую указывает указатель стека
(E)SP, и инкрементирует значение указателя. Регистры CS и IP использоваться в каче&
стве операндов не могут.
POP CX ; Извлечение из стека в 16-разрядный регистр.
POP memval ; Извлечение из стека в 16-разрядный операнд памяти.
POP EDX ; Извлечение из стека в 32-разрядный регистр.
Приведем несколько правил использования стека в программах.
Стек отлично подходит для временного хранения значений всех регистров. Реги&
стры можно использовать как оперативное пространство в программе, а затем бы&
стро восстановить их состояние.
При вызове подпрограмм процессор сохраняет адрес возврата в стеке (место в прог&
рамме, из которого была вызвана подпрограмма).
Когда вызывается процедура, можно разместить аргументы в стеке. Аргументы (зна&
чения параметров) могут быть извлечены из стека вызванной процедурой. В язы&
ках высокого уровня параметры обрабатываются именно так.
Языки высокого уровня создают пространство в стеке для подпрограмм, назы&
ваемое стековым фреймом. В этом пространстве размещаются временные пере&
менные, создаваемые подпрограммой. При возврате из подпрограммы все перемен&
ные удаляются.
Далее показано, когда возникает необходимость в многократном использовании ре&
гистров. В следующем примере строки символов отображаются на экране. Предполагает&
ся, что регистры EDX и EAX содержат важные значения, которые должны быть восстанов&
лены после отображения строки на экране. Стек является структурой LIFO, поэтому
последний сохраненный регистр будет восстановлен первым.
.DATA
message DB “Это сообщение.”,0
.CODE
PUSH EAX ; Сохранение EAX.
PUSH EDX ; Сохранение EDX.
MOV EDX,OFFSET message ; DX указывает на строку.
CALL Writestring ; Отображение строки.
POP EDX ; Восстановление EDX.
POP EAX ; Восстановление EAX.

Команды PUSHF и POPF


Команда PUSHF помещает регистр флагов в стек, сохраняя его на случай возможных
изменений. Позже состояние флагов можно восстановить с помощью команды POPF.
В следующем примере происходит сохранение состояния флагов перед вызовом под&
программы, которая может их изменить.
PUSHF ; Сохранение флагов.
CALL display_sub ; Вызов подпрограммы.
POPF ; Восстановление флагов.
В процессоре Intel 386 и более поздних моделях команды PUSHFD и POPFD сохраняют
и восстанавливают 32&разрядный регистр флагов.

Команды PUSHA и PUSHAD


Команда PUSHA, имеющаяся в процессоре 80286 и более поздних моделях, сохраняет
регистры AX, CX, DX, BX, SP, BP и DI в стеке в указанном порядке. Команда POPA извлекает

168 Глава 6. Процедуры и прерывания

Стр. 168
отсутствует. 169

регистры в обратном порядке. Команда PUSHAD (Intel 386 и поздние модели) сохраняет
в стеке регистры EAX, ECX, EDX, EBX, ESP, EBP, EDI, а восстанавливаются они коман&
дой POPAD.

Процедуры
Сначала остановимся на общей терминологии. В языках высокого уровня термином
функция обозначают подпрограмму, которая возвращает значение, а слово процедура
обычно используется для подпрограмм, которые не должны возвращать значение. Под&
программа &&&& более широкое понятие и обычно означает блок команд, которые могут
быть вызваны из другого места памяти. Вызов подпрограммы подразумевает, что обяза&
тельно будет совершен возврат из подпрограммы. Это не переход, возврат из которого не
требуется. В языке ассемблера термины подпрограмма, процедура и функция обычно име&
ют один и тот же смысл, хотя в некоторых случаях учитывается их смысловое различие.

Директивы PROC и ENDP


В языке ассемблера директивы PROC и ENDP отмечают начало и конец процедуры. В лис&
тинге 6.1 показаны процедуры main и mySub. Здесь команда CALL в процедуре main за&
ставляет процессор перейти к началу подпрограммы mySub. В конце подпрограммы ко&
манда RET возвращает управление в main.

Листинг 6.1. Вызов процедуры


.CODE
main PROC
CALL mySub
exit
main ENDP

mySub PROC
.
.
RET
mySub ENDP

Простая программа
Ниже приведена небольшая рабочая программа, в которой производится вызов трех
процедур. Процедура KeyCode получает символ с клавиатуры и помещает его характери&
стики в регистры AL, AH и DX.
Процедура KeyChar просто считывает символ нажатой клавиши и отображает его на
экране, а процедура Zapros используется для отображения на экране приглашения для
ввода символа.
В данном случае возникает несогласованность между кодировками Windows и DOS,
которые используются при компиляции и отображении в окне консоли. Поэтому вводите
текст английскими буквами. Для перекодировки текста можно использовать процедуру
CharToOemA. Например:
PUSH OFFSET STR1
PUSH OFFSET STR1
CALL CharToOemA

Глава 6. Процедуры и прерывания 169

Стр. 169
Листинг 6.2. Демонстрация вызовов подпрограмм
TITLE Демонстрация вызовов подпрограмм.
; В этой программе используется три процедуры: две для
: ввода с клавиатуры и одна для вывода приглашения.
.386
include Study32.inc
.data
str1 byte "ascii = ",0
str2 byte "scan = ",0
str3 byte "key = ",0
ascii byte ?
scan byte ?
key word ?

.code
main PROC
call Zapros
call KeyChar
call KeyCode
invoke WaitMsg
exit
main endp

KeyCode PROC
LookForKey:
mov eax,50 ; Время задержки.
call Delay ; Задержка для ОС.
call ReadKey ; Считывание клавиши.
jz LookForKey ; Нет нажатия.

mov ascii, al ; Запоминаем результат.


mov scan, ah
mov key, dx

mov edx, offset str1 ; Вывод str1.


invoke WriteString
mov eax,0
mov al,ascii
invoke WriteInt ; Вывод кода ASCII.
invoke Crlf

mov edx, offset str2 ; Вывод str2.


invoke WriteString
mov eax,0
mov al,scan
invoke WriteInt ; Вывод скан-кода.
invoke Crlf

mov edx, offset str3 ; Вывод str3.


invoke WriteString
mov eax,0
mov ax,key ; Вывод виртуального
invoke WriteInt ; кода клавиши.
invoke Crlf
ret
KeyCode endp

170 Глава 6. Процедуры и прерывания

Стр. 170
отсутствует. 171

Zapros PROC
.data
zapr byte "Press any key ",0
.code
mov edx, offset zapr ; Вывод приглашения.
invoke WriteString
invoke Crlf
ret
Zapros endp

KeyChar PROC
invoke ReadChar
invoke WriteChar
invoke Crlf
ret
KeyChar endp
end main

Разберем подробнее эту программу. Хорошо видно, что сама программа занимает
всего несколько строчек между ее началом (main PROC) и окончанием (main endp).
Все остальное &&&& это процедуры, написанные специально для этой программы, но кото&
рые можно использовать и в других программах. Поэтому в конечном итоге их необходи&
мо будет вынести в отдельную библиотеку и применять при необходимости. При этом не
надо будет вновь писать код процедуры.
Также обратите внимание на повторяющуюся три раза последовательность из строк:
mov edx, offset str2 ; Вывод str2.
invoke WriteString
mov eax,0
mov al,scan
invoke WriteInt ; Вывод скан-кода.
invoke Crlf
Очевидно, что такое повторение увеличивает размеры листинга и затрудняет понима&
ние всей программы. Лучше повторяющуюся последовательность строк оформить как
макроопределение с соответствующим информативным именем.
mToScreen MACRO subject:REQ, value:REQ
mov edx, offset subject ; Вывод обозначения.
invoke WriteString
movzx ax,value
invoke WriteInt ; Вывод значения.
invoke Crlf
endm
Теперь программа будет выглядеть следующим образом.

Листинг 6.3. Демонстрация вызовов подпрограмм (вариант 2)


TITLE Демонстрация вызовов подпрограмм.
; В этой программе используется три процедуры: две для
: ввода с клавиатуры и одна для вывода приглашения.

.386
include Study32.inc
.data
str1 byte "ascii = ",0

Глава 6. Процедуры и прерывания 171

Стр. 171
str2 byte "scan = ",0
str3 byte "cod = ",0
ascii word ?
scan word ?
key word ?

mToScreen MACRO subject:REQ, value:REQ


mov edx, offset subject ; Вывод обозначения.
invoke WriteString
movzx eax,value
invoke WriteInt ; Вывод значения.
invoke Crlf
endm

.code
main PROC
call Zapros
call KeyChar
call KeyCode
invoke WaitMsg
exit
main endp

KeyCode PROC
LookForKey:
mov eax,50 ; Время задержки.
call Delay ; Задержка для ОС.
call ReadKey ; Считывание клавиши.
jz LookForKey ; Нет нажатия.

mov BYTE PTR ascii, al ; Запоминаем результат.


mov BYTE PTR scan, ah
mov key, dx

mToScreen str1,ascii
mToScreen str2,scan
mToScreen str3,key

ret
KeyCode endp

Zapros PROC
.data
zapr byte "Press any key ",0
.code
mov edx, offset zapr ; Вывод приглашения.
invoke WriteString
invoke Crlf
ret
Zapros endp

KeyChar PROC
invoke ReadChar
invoke WriteChar
invoke Crlf
ret
KeyChar endp
end main

172 Глава 6. Процедуры и прерывания

Стр. 172
отсутствует. 173

Обратите внимание на ‘‘мелочи’’, которые введены в этом варианте программы. Чтобы


макроопределение подходило для всех случаев, используется тип WORD, поэтому при присваи&
вании значения переменным ascii и scan используется приведение типов, т.е. в тип WORD
передается тип BYTE. И чтобы не было синтаксической ошибки, которую не пропустит ком&
пилятор (компилятор проверяет соответствие типов), применяется конструкция BYTE PRT.
Команда MOVZX в макроопределении заменяет используемые ранее две команды
mov eax,0
mov ax,value
А это значит, что программа становится более компактной и быстродействующей. Хотя
в данном случае повышение скорости работы невелико, но при большом количестве таких
незначительных улучшений можно добиться значительного прироста скорости работы.
В результате работы программы вы получите символ нажатой клавиши, ее значение
в коде ASCII, скан&код и виртуальный код клавиши:
Press any key
к
ascii = +170
scan = +19
cod = +82
Press [Enter] to continue...

Вложенные вызовы процедур


Подпрограммы могут вызывать другие подпрограммы, как показано в листинге 6.3.
Стек содержит список адресов возврата, так что процессор может вернуться к исходной
точке программы. К моменту вызова подпрограммы sub3 в стеке уже находятся три ад&
реса возврата. Команда RET в конце процедуры извлечет из стека значение 0060 и поместит
его в регистр IP, а выполнение продолжится в подпрограмме sub2. Когда будет достигнута
команда RET в подпрограмме sub2, из стека будет извлечено значение 0050 и помещено
в указатель команд IP, а выполнение продолжится в подпрограмме sub1. Наконец, из сте&
ка будет извлечено значение 000C, и управление вернется в программу main.

Листинг 6.4. Вложенные вызовы процедур


main PROC
000A CALL subl
000C MOV AX,...
.
main ENDP
sub1 PROC
.
CALL sub2
0050 RET
sub1 ENDP
sub2 PROC
.
CALL sub3
0060 RET
sub2 ENDP
sub3 PROC
.
.
RET
sub3 ENDP

Глава 6. Процедуры и прерывания 173

Стр. 173
Параметры процедур
Передача аргументов в регистры
В языке ассемблера наиболее общим способом для передачи аргументов в подпро&
граммы является размещение аргументов в регистрах. Этот метод эффективен, так как
вызванная подпрограмма может непосредственно использовать переданные значения,
и при этом обращение к регистрам выполняется значительно быстрее обращения к памя&
ти. Например, если некоторая процедура writeint требует, чтобы регистр EAX содержал
32&разрядное целое число, а EBX включал основание системы счисления, то вызов дол&
жен производиться так, как показано в примере ниже.
.DATA
aNumber DWORD ?
.CODE
MOV EAX,aNumber
MOV EBX,10
CALL writeint
Процедура должна отвечать за защиту значений, размещенных в регистрах, от слу&
чайного изменения. Это гарантирует, что вызывающая программа не столкнется с не&
ожиданным изменением состояния регистров. Всегда необходимо соблюдать следующее
важное правило: при написании процедур следует сохранять и восстанавливать все реги&
стры, которые могут изменяться в процедуре. Не пытайтесь игнорировать это правило
в интересах повышения скорости работы программы из&за того, что в подпрограмме нет
явной необходимости использовать некоторые регистры &&&& при необходимости модифи&
кации подпрограммы придется использовать дополнительные регистры, которые могут
непредвиденно изменяться в процедуре.
При написании процедур следует сохранять и восстанавливать все регистры, которые мо&
гут изменяться в процедуре.

Исключение из правила сохранения регистров может возникнуть тогда, когда проце&


дура использует один из регистров для возвращения значения. Этот регистр нельзя по&
мещать в стек и извлекать из стека. Например, процедура SUMOf возвращает сумму ре&
гистров EAX, EBX и ECX. Значение суммы находится в регистре EAX. Если сначала
поместить значение регистра в стек, а затем извлечь его из стека, то возвращаемое значе&
ние будет потеряно.
SUMOf PROC
PUSH EAX
ADD EAX,EBX
ADD EAX,ECX
POP EAX ; Ошибка: в AX теряется значение суммы.
RET
SUMOf ENDP

Прерывания
Обобщающий термин прерывание используется для двух случаев: аппаратное прерыва%
ние &&&& это сигнал от любого устройства системы для процессора, который по этому сиг&
налу должен обслужить данное устройство, и программное прерывание, которое создается
программами BIOS или DOS для вызова сервисных подпрограмм.

174 Глава 6. Процедуры и прерывания

Стр. 174
отсутствует. 175

При программировании для 32&разрядного режима нет необходимости непосредственно


использовать прерывания и все обработки прерываний берут на себя процедуры Windows.
Программное прерывание вырабатывается специальной микросхемой &&&& контролле&
ром прерываний, который посылает сигнал процессору на приостановку выполнения те&
кущей программы и переход к выполнению программы прерывания.
Например, нажатие любой клавиши заставляет процессор приостановить выполняе&
мую программу, чтобы переключиться на выполнение подпрограммы на уровне BIOS,
которая считывает значение с порта клавиатуры и сохраняет его в буфере памяти. Затем
процессор возобновляет выполнение прерванной программы.
Аппаратные прерывания необходимы для обнаружения важных событий, происхо&
дящих в аппаратном окружении процессора, и их следует обработать еще до того, как бу&
дут потеряны входные данные. Например, введенный с клавиатуры символ может быть
потерян, если его вовремя не заметить и не сохранить в памяти. Так же могут быть про&
пущены символы из последовательного порта ввода, если не будет вовремя вызвана под&
программа сохранения их в буфере.
Время от времени программы должны запрещать аппаратные прерывания, когда вы&
полняют критичные ко времени операции. Команда CLI запрещает прерывания, а ко&
манда STI их разрешает.
Следует отметить, что программные прерывания не являются прерываниями как та&
ковыми. Вероятно, их назвали так из&за того, что они во многом повторяют действия ап&
паратных прерываний. Программные прерывания включают необходимые сервисные
программы для проведения операций ввода&вывода.
Команда INT требует обслуживания со стороны операционной системы, обычно для
операций ввода&вывода. Небольшие обслуживающие программы размещаются непо&
средственно в операционной системе.

Команда INT
Команда прерывания INT используется только при программировании в 16&разрядном
режиме и вызывает подпрограммы операционной системы. В 32&разрядном режиме необ&
ходимо использовать соответствующие процедуры и функции операционной системы, ко&
торые и обращаются напрямую к отдельным прерываниям. Эти прерывания имеют но&
мера в диапазоне от 0 до FFh. Перед тем как вызвать команду INT, в регистр обычно
помещают номер функции, который определяет необходимую подпрограмму. Другие
регистры тоже могут использоваться в прерывании. Синтаксис прерывания такой:
INT номер.
Обычно команды INT используются для консольного ввода&вывода, управления фай&
лами и выводом видеоизображения, а также во многих других случаях.

Таблица векторов прерываний


Процессор выполняет команду прерывания, используя таблицу векторов прерываний.
Таблица занимает самые нижние 1024 байт памяти. Каждый элемент таблицы &&&& это 32&
разрядный адрес ‘‘сегмент&смещение’’, который указывает на подпрограмму операцион&
ной системы. В разных компьютерах эти адреса могут различаться. На рис. 6.2 показаны
действия, которые выполняет процессор, когда происходит вызов команды INT.
1. Номер, следующий за мнемокодом INT, сообщает процессору местонахождение
вектора в таблице векторов прерываний. В приведенном примере показано, что
INT 10 требует видеообслуживания.

Глава 6. Процедуры и прерывания 175

Стр. 175
Вызывающая Память Обработчик
программа прерывания

STI

CLD
MOV ...
PUSH ES
INT 10 F000:F065

ADD ...


IRET

Возврат в вызывающую программу

Рис. 6.2. Обработка вектора прерываний

2. Процессор переходит на адрес, указанный в таблице (F000:F065).


3. Обработчик прерываний  подпрограмма DOS, расположенная по адресу
F000:F065, начинает работу и передает управление назад в вызывающую про
грамму, когда команда IRET будет достигнута.
4. Команда возврата из прерывания IRET отдает управление вызывающей програм
ме, которая продолжает работу со следующей за прерыванием командой.
Программные прерывания вызывают подпрограммы обслуживания прерываний в BIOS
или в DOS. Рассмотрим наиболее часто используемые прерывания:
INT 10h (видеосервис). Подпрограммы для устройства отображения, которые кон
тролируют положение курсора, прокрутку экрана, размещение графических изо
бражений.
INT 16h (обслуживание клавиатуры). Подпрограммы для обработки нажатия кла
виш и контроля состояния клавиатуры.
INT 17h (обслуживание принтера). Подпрограммы инициализации, печати и полу
чения статуса принтера.
INT 1Ah (время дня). Подпрограмма возвращает количество временных интервалов
с момента включения компьютера или установки счетчика.
INT 1Ch (отсчет таймера). Подпрограмма, которая выполняется 18,2 раза в секунду.
INT 21h (подпрограммы DOS). Подпрограммы для вводавывода, управления фай
лами и управления памятью, называемые вызовами функций DOS.

Ввод на уровне BIOS


Для непосредственного ввода с клавиатуры используют прерывание INT 16h. Код
функции помещается в регистр AH перед вызовом прерывания INT 16h. В табл. 6.1 при
ведены наиболее часто используемые функции.

176 Глава 6. Процедуры и прерывания

Стр. 176
отсутствует. 177

Таблица 6.1. Функции клавиатуры для INT 16h BIOS


AH Описание
03h Задается скорость повторения символов. Необходимо установить AH=3, AL=5, BH  задержка
повторения, BL  скорость повторения. Значение задержки в регистре BH: 0 (250 мс);
1 (500 мс); 2 (750 мс); 3 (1000 мс). Скорость повторения в BL изменяется от 0 (быстро)
до 1Fh (медленно)
05h Помещает код символа и соответствующий сканкод в буфер клавиатуры. Перед вызовом
устанавливается AH=5, CH= сканкод и CL= код символа. Если буфер клавиатуры заполнен,
то после выполнения флаг переноса устанавливается и AL=1
10h Ожидание нажатия. Если произошло нажатие клавиши, сканкод помещается в AH, а код
символа  в AL. В противном случае подпрограмма остается в цикле, ожидая нажатия
(функция 00h дублирует эту функцию для старых клавиатур)
11h Проверяет буфер клавиатуры на наличие символа; если в буфере есть символ, то сканкод
помещается в AH, код символа  в AL и сбрасывается флаг нуля. В противном случае ZF=1.
Символ остается в буфере (функция 01h дублирует эту функцию для старых клавиатур)
12h Показывает байт состояния клавиатуры (см. рис. 6.3 (Функция 02h дублирует эту функцию
для старых клавиатур.)

Клавиша Insert

Клавиша CapsLock

Клавиша NumLock

Клавиша ScrollLock

Клавиша Alt

Клавиша Ctrl

Клавиша Shift (левая)

Клавиша Shift (правая)

7 6 5 4 3 2 1 0

Рис. 6.3. Байт состояния клавиатуры

В следующем примере показано, как сделать ожидание нажатия клавиш с помощью


прерываний INT 16h. Предположим, нажата клавиша F1 (сканкод 3Bh). После вызова
INT 16h в регистре AH содержится сканкод, а в AL помещается значение кода ASCII (0).
MOV AH,10h ; Запрос BIOS на ввод с клавиатуры.
INT 16h ; AH = 3Bh, AL = 0.
Иногда возникает необходимость смоделировать нажатие клавиши в программе, поместив
для этого символ в буфер клавиатуры. Следующие команды помещают символ ‘A’ в буфер.
MOV AH,5 ; Функция BIOS: нажатие клавиши.
MOV CH,1Eh ; Скан-код для ‘A’.
MOV CL,41h ; Код ASCII для ‘A’.
INT 16h

Управляющие коды ASCII


Набор символов в кодировке ASCII содержит несколько управляющих символов в диа
пазоне от 0 до 32, которые воспринимаются системой, но не отображаются на экране.

Глава 6. Процедуры и прерывания 177

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

Таблица 6.2. Управляющие символы ASCII


Шестнадцатеричные Десятичные Описание

08h 08 Возврат
09h 09 Горизонтальная табуляция
0Ah 10 Пропуск строки
0Ch 12 Подача страницы (для принтера)
0Dh 13 Возврат каретки (клавиша <Enter>)
1Bh 27 Переход

Символы 0Dh и 0Ah должны находиться в конце каждой строки текстового файла, так
как они управляют возвратом каретки и пропуском строки. При выводе на экран символ
возврата каретки перемещает курсор в левую сторону экрана, а символ пропуска строки пе&
ремещает курсор на одну строку вниз. Этот символ необходим, когда происходит последо&
вательный вывод строк (при его отсутствии строки будут накладываться друг на друга).

Видеоконтроль на уровне BIOS


Когда в прикладной программе необходимо вывести символ на экран, то в 16&разряд&
ном режиме можно либо произвести непосредственно запись в видеопамять, либо вос&
пользоваться прерываниями для получения необходимых возможностей от операцион&
ной системы. Можно сделать выбор между тремя уровнями доступа.
Прямой видеодоступ. Символы помещаются непосредственно в видеобуфер памя&
ти. Адрес буфера зависит от типа дисплея, а иногда и от производителя компьюте&
ра. Преимуществом является высокая скорость. Прямой видеовыход не может
быть перенаправлен, так как не используются резидентные программы управле&
ния доступом к видеобуферу.
Доступ на уровне BIOS. Символы выводятся с использованием функций прерыва&
ния INT 10h &&&& это обслуживающие программы BIOS. Характеризуется средней
скоростью, высокой совместимостью. Не могут быть перенаправлены, пока не из&
менены сервисные подпрограммы.
Доступ на уровне DOS. Использование функций DOS гарантирует корректную, но
не очень быструю работу на всех компьютерах. Позиции курсора и цвета экрана
устанавливаются с помощью драйверов ansi.sys &&&& программ, поставляемых
вместе с DOS. Ввод&вывод может быть легко перенаправлен на другие устройства,
такие как принтер или диск.
В 32&разрядном режиме необходимо использовать соответствующие функции опера&
ционной системы.

Дисплеи, режимы, атрибуты


Первое время компьютеры поставлялись с монохромным адаптером дисплея (MDA),
разработанным IBM, и могли отображать только текст на экранах черного, зеленого либо
янтарного цвета. Цветные графические адаптеры (CGA) и улучшенные графические
адаптеры (EGA) могли воспроизводить как текст, так и графику, хотя их возможности

178 Глава 6. Процедуры и прерывания

Стр. 178
отсутствует. 179

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

Видеорежимы
В основном используются два типа режимов: текстовый и графический. Текстовый ре&
жим применяется при работе в DOS, компьютер может отображать только символы в соот&
ветствии с расширенным набором символов. При этом можно использовать специальные
графические символы, которые позволяют изображать простейшие графические образы.
В режиме графики компьютер может управлять отдельными пикселями (точками), позво&
ляя рисовать линии, окружности и отображать сложные графические образы.

Видеоатрибуты
При работе в консольном режиме каждая позиция экрана может содержать отдельный
символ с собственными атрибутами. Для хранения атрибутов используется отдельный байт,
называемый байтом атрибутов. В прерывании 10h есть функция, которая позволяет ус&
тановить атрибуты (например, атрибут цвета, негативного отображения, мигания, под&
черкивания и яркости).
Видеорежим 7 отображает монохромный текст. Доступные атрибуты режима 7 пока&
заны в табл. 6.3. Например, обычное мигание записывается как 87h, яркое мигание &&&&
как 8Fh, негативное изображение &&&& как 0F0h и т.д. В то же время сам символ можно
сделать ярким, установив в третьем бите единицу.

Таблица 6.3. Атрибуты режима 7


Значение Атрибут

07 Нормальный
87 Мигание
0F Яркий (подсветка)
70 Негатив
01 Подчеркивание
09 Яркое подчеркивание

Режим цветного текста


Видеоконтроллер может использовать цветной текстовый режим 3, при котором отобра&
жаются все символы из стандартного набора с соответствующими атрибутами цвета. В этом
режиме не разрешено подчеркивание символов, но мигание и негативное отображение допус&
тимы. Цвета разделены на две категории: цвет фона (background) и цвет символа (foreground).
Для каждого символа имеется собственный атрибут, поэтому можно создать строку текста,
в которой каждый символ будет иметь свой цвет и фон, отличный от других. Цвет фона оп&
ределяют биты 4 и 5 в байте атрибутов, а цвет символа определяется битами 0, 1 и 2. Бит 3
определяет яркость, а бит 7 &&&& мигание. На рис. 6.4 показаны все биты установки цвета.
Для установки байта видеоатрибутов используется оператор SHL, который сдвигает
биты на четыре позиции влево. Например, следующее утверждение создает символ бе&
лого текста на голубом фоне (00010111) в регистре BH.
BLUE = 1
WHITE = 111b
MOV BH,(BLUE shl 4) + WHITE

Глава 6. Процедуры и прерывания 179

Стр. 179
0 0 0 1 0 1 1 1

Мигание Фон Символ

Рис. 6.4. Структура байта видеоатрибутов

В следующем фрагменте программы создается яркий белый символ на красном фоне


(01001111).
BRIGHT_WHITE = 1111b
RED = 100b
MOV BH,(RED shl 4) + BRIGHT_WHITE
В следующих строках получается мигающий желтый символ на коричневом фоне, это
значение 11101110.
BLINK = 10000000b
YELLOW = 1110b
BROWN = 110b
MOV BH,(BROWN shl 4) + YELLOW + BLINK
Перечень цветов фона и перечень цветов символа приведены в табл. 6.4 и 6.5.
В дополнение к этим цветам видеоконтроллер может переключаться между различ#
ными цветовыми палитрами, тем самым расширяя цветовую гамму.

Таблица 6.4. Цвета символов


Двоичное число Шестнадцатеричное число Цвет

000 00 черный
001 01 синий
010 02 зеленый
011 03 голубой
100 04 красный
101 05 вишневый
110 06 коричневый
111 07 белый

Таблица 6.5. Цвета фона


Двоичное Шестнадцате- Двоичное Шестнадца-
Цвет Цвет
число ричное число число теричное число

0000 00 черный 1000 08 серый


0001 01 синий 1001 09 ярко#синий
0010 02 зеленый 1010 0A ярко#зеленый
0011 03 голубой 1011 0B ярко#голубой
0100 04 красный 1100 0C ярко#красный
0101 05 вишневый 1101 0D ярко#вишневый
0110 06 коричневый 1110 0E желтый
0111 07 белый 1111 0F ярко#белый

180 Глава 6. Процедуры и прерывания

Стр. 180
отсутствует. 181

Видеостраницы
Все графические адаптеры с использованием цвета могут сохранять в памяти несколько
изображений экрана, называемых страницами. Монохромные адаптеры могут отобра&
жать только одну страницу. Графический адаптер с использованием цвета может про&
изводить запись в одну страницу, в то время как на экран выводится другая страница,
и мгновенно переключаться между страницами. Страницы имеют номера от 0 до 7, и ко&
личество страниц зависит от текущего видеорежима (табл. 6.6).

Таблица 6.6. Видеостраницы и режимы


Доступные страницы Режим Видеоадаптер

0 07h Mono
0&&7 00h, 01h CGA
0&&3 02h, 03h CGA, EGA, VGA
0&&7 02h, 03h CGA, EGA, VGA
0&&7 0Dh EGA, VGA
0&&3 0Eh EGA, VGA
0&&1 0Fh, 10h EGA, VGA

Видеофункции INT 10h


Функции, доступные с прерыванием INT 10h, устанавливают режим изображения
с помощью подпрограмм ROM BIOS. Они сохраняют только регистры BX, CX, DX и реги&
стры сегментов. Для сохранения других регистров необходимо поместить их в стек до вы&
зова команды прерывания INT 10h.
00h (установка видеорежима). Для получения на экране конкретного видеорежима
необходимо поместить 0 в регистр AH и установить в регистре AL номер видеорежима.
Экран будет очищен автоматически, если старший бит регистра AL не установлен. В сле&
дующем примере устанавливается видеорежим 80×25 строк цветного текста.
MOV AH,0 ; Установить видеорежим.
MOV AL,3 ; Выбрать режим 3.
INT 10h ; Вызвать BIOS.
Чтобы не очищать экран, можно установить в AL значение 83h. Если в системе два ви&
деоадаптера (монохромный и цветной), то необходимо выбрать адаптер. Для того чтобы уз&
нать, какой видеорежим установлен, используется функция 0Fh (получение видеорежима).
Следующая подпрограмма устанавливает графический видеорежим со средним разре&
шением, ожидает нажатия клавиши и затем устанавливается видеорежим цветного текста.
MOV AH,0 ; Установить видеорежим.
MOV AL,6 ; Цветной графический режим 640*200.
INT 10h
MOV AH,1 ; Нажатие клавиши.
INT 21h
MOV AH,0 ; Установить видеорежим.
MOV AL,3 ; Режим цветного текста.
INT 10h
Перечень всех функций команды INT 10h приведен в табл. 6.7.

Глава 6. Процедуры и прерывания 181

Стр. 181
Таблица 6.7. Список функций прерывания INT 10h
Номер функции (AH) Описание

0 Установить монохромный, текстовый, графический или цветной режим


1 Установить начальную и конечную линии для отображения курсора
2 Установить позицию курсора на экране
3 Получить позицию и размер курсора
4 Считать позицию и статус светового пера
5 Выбрать страницу для отображения на экране
6 Прокрутить текущую видеостраницу вверх, заменяя строки пустым
пространством
7 Прокрутить текущую видеостраницу вниз, заменяя строки пустым пространством
8 Считать символ и его атрибуты в текущей позиции курсора
9 Записать символ и его атрибуты в текущей позиции курсора
0Ah Записать символ без атрибутов в текущей позиции курсора
0Bh Выбрать группу доступных цветов для видеоадаптера
0Ch Записать пиксель в графическом видеорежиме
0Dh Считать пиксель в графическом видеорежиме
0Eh Вывести символ на экран и переместить курсор
0Fh Получить текущий видеорежим
11h В текстовом режиме загрузить один из трех шрифтов для EGA& или VGA&дисплеев

01h (установить линии курсора). Курсор отображается с помощью нескольких линий,


что позволяет изменять его размеры. В прикладных программах это свойство можно ис&
пользовать для отображения статуса состояния. Например, в текстовом редакторе увели&
ченные размеры курсора говорят о подключении дополнительной цифровой клавиатуры.
При нажатии клавиши <NumLock> происходит возврат в прежнее состояние.
В монохромном дисплее используется 12 линий для курсора, а все другие дисплеи ис&
пользуют только 8 линий. Курсор можно изобразить с помощью горизонтальных линий,
начиная с линии 0. По умолчанию курсор начинается с линии 6 и заканчивается линией 7.
В монохромном дисплее курсор начинается с линии 0Bh и заканчивается линией 0Ch
Для вызова функции, определяющей вид курсора, установите в CH и CL значения
верхней и нижней линий курсора и поместите в AH единицу (AH=1). Следующий набор
команд устанавливает для монохромного дисплея максимальный размер курсора.
MOV AH,1 ; Установить размеры курсора.
MOV CH,0 ; Начальная линия (верх).
MOV CL,0Ch ; Конечная линия (низ).
02h (установить позицию курсора). Функция помещает курсор в определенную строку
и в определенный столбец. Для этого в регистр AH помещается значение 2, в регистре DH
устанавливается нужная строка, а в DL &&&& столбец. В регистр BH устанавливается номер
текущей видеостраницы (обычно 0). В следующем фрагменте курсор помещается в стро&
ку 10 и столбец 20.
MOV AH,2 ; Установить позицию курсора.
MOV DH,10 ; Строка 10.
MOV DL,20 ; Столбец 20.
MOV BH,0 ; Видеостраница 0.
INT 10h ; Вызов BIOS.

182 Глава 6. Процедуры и прерывания

Стр. 182
отсутствует. 183

03h (получить позицию курсора). Функция возвращает номер строки и номер столбца
курсора на заданной видеостранице. Это же касается начальной и конечной линий, оп&
ределяющих размер курсора. Необходимо установить 3 в регистре AH, а номер видеостра&
ницы в регистре BH. Возвращаемые значения приведены в табл. 6.8.

Таблица 6.8. Возвращаемые значения для функции 03h


Регистр Значение
CH Начальная линия
CL Конечная линия
DH Номер строки
DL Номер столбца

Следующая подпрограмма получает и сохраняет информацию о курсоре.


MOV AH,3 ; Получить позицию курсора.
MOV BH,0 ; Видеостраница 0.
INT 10h ; Вызов BIOS.
MOV savecursor,CX ; Сохранение линий курсора.
MOV current_row,DH ; Сохранение строки.
MOV current_col,DL ; Сохранение столбца.
Эта функция необходима в тех программах, где происходит перемещение курсора по
позициям меню. В зависимости от позиции курсора определяется пункт меню.
05h (установить видеостраницу). Функция полезна в текстовом режиме при поддерж&
ке множества страниц. Текст, написанный на одной из страниц, будет сохранять все ат&
рибуты при отображении на другой странице. Для вызова функции необходимо устано&
вить значение 5 в регистр AH и необходимый номер страницы в регистр AL.
MOV AH,5 ; Установить страницу для отображения.
MOV AL,1 ; Выбрана страница 1.
INT 10h ; Вызов BIOS.
Приведенный ниже листинг программы выполняет отображение текста на видеостра&
ницах 0 и 1 и переключение между ними. После трансляции программы запустите ее не&
сколько раз, и вы будете видеть текст, записанный ранее на странице 1. Это происходит по&
тому, что большинство программ (включая DOS) выполняют запись только на страницу 0.
TITLE Переключение видеостраниц.
; Эта программа производит переключение между
; видеостраницами 0 и 1 на цветном дисплее.
.MODEL small
.STACK 100h
.DATA
page0 DB 'Это видеостраница 0.$'
page1 DB ' Это видеостраница 1.$'
.CODE
main PROC
MOV AX,@DATA ; Инициализировать регистр DS.
MOV DS,AX
MOV AH,9 ; Отобразить сообщение.
MOV DX,OFFSET page0
INT 21h
MOV AH,1 ; Нажатие клавиши.
INT 21h
to_page_l:

Глава 6. Процедуры и прерывания 183

Стр. 183
MOV AH,5 ; Установить видеостраницу.
MOV AL,1 ; К странице 1.
INT 10h
MOV AH,9 ; Отобразить сообщение.
MOV DX,OFFSET page1
INT 21h
MOV AH,1 ; Нажатие клавиши.
INT 21h
to_page_0:
MOV AH,5 ; Установить видеостраницу.
MOV AL,0 ; К странице 0.
INT 10h
MOV AX,4C00h ; Возврат в DOS.
INT 21h
main ENDP
END main
06h, 07h (прокрутка окна вверх и вниз). С помощью этой функции можно прокручивать
окно экрана. Термин прокрутка окна означает перемещение данных на дисплее вверх или
вниз. Если, например, изображение на дисплее прокручивается вверх, то нижние линии
заменяются пустым пространством.
Размер окна зависит от используемого количества строк и столбцов. Строки опреде&
ляются номерами 0-24 от верхней границы, а столбцы определяются номерами 0-79 от
левой границы. Координаты окна, покрывающего весь экран, будут иметь значения для
верхнего левого угла (0, 0) и нижнего правого (24, 79). При прокрутке одной или не&
скольких строк перемещаются их номера. Если прокрутить все строки, экран будет чис&
тым. Строки, вышедшие за пределы окна, не восстанавливаются. Входные параметры
для вызова функций 6 и 7 показаны в табл. 6.9.

Таблица 6.9. Входные параметры для функций 6 и 7


Регистр Значение
AH 6 &&&& для прокрутки вверх, 7 &&&& для прокрутки вниз
AL Количество строк (при 0 прокручиваются все строки)
CH, CL Номера строки и столбца верхнего левого угла окна
DH, DL Номера строки и столбца нижнего правого угла окна
BH Видеоатрибуты, присваиваемые каждой свободной строке

08h (чтение символа и атрибутов). Функция возвращает символ и его атрибуты в те&
кущей позиции курсора на выбранной видеостранице. Для вызова этой функции в ре&
гистр AH помещается значение 8, а в BH &&&& номер видеостраницы. Следующие команды
помещают курсор в строку 8 и столбец 1, после чего считывают символ.
locate:
MOV AH,2 ; Установить курсор
MOV BH,0 ; на видеостранице 0
MOV DX,0501h ; в позицию 5,1.
INT 10h
getchar:
MOV AH,8 ; Считать атрибуты/символ
MOV BH,0 ; на видеостранице 0.
INT 10h
MOV CHAR,AL ; Сохранить символ.
MOV attrib,AH ; Сохранить атрибуты.

184 Глава 6. Процедуры и прерывания

Стр. 184
отсутствует. 185

09h (записать символ и атрибуты). Функция используется для записи одного или не&
скольких символов с текущей позиции курсора. Эта функция может отобразить любой
символ в кодировке ASCII, включая специальные графические символы для кодов 1-31.
Ни один из этих символов не интерпретируется как управляющий код, что характерно
для прерывания INT 21h в DOS. Входные параметры приведены в табл. 6.10.

Таблица 6.10. Входные параметры для функции 09h


Регистр Значение
AH Код функции (9)
AL Символ для записи
BH Номер видеостраницы
BL Атрибуты
CX Фактор повторения

Фактор повторения определяет, сколько раз символ должен быть повторен. (Символ
не должен выходить за границу текущей строки экрана.) После записи символа необхо&
димо вызвать функцию 02h для перемещения курсора.
В следующих командах 32 раза записывается графический символ, определяемый ко&
дом 0Ah с атрибутами мигания. Символ 0Ah в кодировке ASCII определяет пропуск
строки, но в данном случае он отображается как белая точка на черном фоне.
MOV AH,9 ; Запись символа и атрибутов.
MOV AL,0Ah ; Символ 0Ah в кодировке ASCII.
MOV BH,0 ; Видеостраница 0.
MOV BL,87h ; Атрибуты мигания.
MOV CX,32 ; Отобразить 32 раза.
INT 10h
OAh (записать символ). Функция 0Ah выводит символ на экран в текущей позиции курсора
без изменения текущих атрибутов экрана. Эта функция подобна функции 09h, но без уста&
новки атрибутов. Следующие команды записывают символ A в текущую позицию курсора.
MOV AH,0Ah ; Записать только символ.
MOV AL,'A' ; Символ 'A'.
MOV BH,0 ; Видеостраница 0.
MOV CX,1 ; Отобразить один раз.
INT 10h
OCh (записать графический пиксель). Функция 0Ch рисует один пиксель на экране,
присваивая пикселю значение, номер видеостраницы, строку и столбец.
MOV AH,0Ch
MOV AL,pixelValue
MOV BH,videoPage
MOV EX,column ; Координата X.
MOV DX,row ; Координата Y.
INT 10h
ODh (читать графический пиксель). Функция 0Dh считывает графический пиксель
с экрана в определенной позиции строки и столбца, после чего возвращает его значение
в регистр AL.
MOV AH,0Dh
MOV BH,videoPage
MOV EX,column

Глава 6. Процедуры и прерывания 185

Стр. 185
MOV DX,row
INT 10h
MOV pixelValue,AL
OFh (получить видеорежим). Функция 0Fh возвращает число столбцов в регистр AH,
текущий режим дисплея &&&& в AL, активную страницу &&&& в BH. (Список общих видеоре&
жимов приведен в подразделе ‘‘Видеорежимы’’.) Эта функция должна вызываться перед
выполнением любых графических функций или специфических операций с цветом. В сле&
дующих командах определяется текущий видеорежим и активная страница.
MOV AH,0fh ; Получить видеорежим.
INT 10h
MOV vmode,AL ; Сохранить режим.
MOV page,BH ; Сохранить страницу.
11h (загрузка шрифтов по умолчанию). Усовершенствованные EGA& и VGA&дисплеи,
позволяют загружать и отображать различные шрифты в текстовом режиме. С дисплеем
VGA можно использовать 25 строк с символами размером 8×16 и 28 строк с символами
размером 8×14, а также 50 строк с символами 8×8.
Для загрузки шрифтов используется функция 11h из прерывания INT 10h и под&
функции 11h, 12h или 14h для установки шрифта. Подфункция шрифтов загружается
в регистр AL.
eightByFourteen EQU 11h ; 28 строк/экран.
eightByEight EQU 12h ; 50 строк/экран.
eightBySixteen EQU 14h ; 25 строк/экран.

MOV AH,0 ; Установить видеорежим


MOV AL,3 ; цветного текста.
INT 10h
MOV AH,11h ; Функция: загрузить шрифт.
MOV AL,eightByEight ; Подфункция: тип шрифта.
MOV BL,0 ; Номер блока = 0.
INT 10h

Прямая запись в видеопамять


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

Резюме
В данной главе рассмотрены принципы построения программ с использованием проце&
дур и прерываний. Основное внимание уделено 16&разрядному режиму, так как только
в этом режиме можно непосредственно обращаться к ресурсам компьютера и лучше понять
работу прерываний и графики. При программировании для 32&разрядного режима обраще&
ние к прерываниям выполняется через процедуры Windows. Необходимо уметь грамотно
разбивать программу на отдельные функциональные блоки и научиться разрабатывать
программы по частям, отлаживая отдельные процедуры и используя их в дальнейшем. На&
личие достаточного количества отлаженных процедур позволит быстро и без ошибок пи&
сать довольно сложные программы. В главе также кратко рассмотрены функции работы
с консолью, с помощью которых можно не только выполнять ввод с клавиатуры или вывод
на дисплей, но и производить графическое оформление интерфейса пользователя.

186 Глава 6. Процедуры и прерывания

Стр. 186
отсутствует. 187

Контрольные вопросы
1. Для чего нужны подпрограммы сервисных прерываний?
2. На какую ячейку указывает указатель стека?
3. Чем отличается программное прерывание от аппаратного?
4. Какими могут быть наименьший и наибольший номера прерываний?
5. На что указывают значения, помещенные в таблицу векторов прерываний?
6. Почему прикладные программы непосредственно не управляют аппаратными
устройствами?
7. Какое прерывание обслуживает клавиатуру? Какие возможности при этом пре&
доставляются?
8. Что такое скан&код клавиатуры? Чем он отличается от кода ASCII?
9. Какой управляющий символ в кодировке ASCII перемещает курсор в левую сто&
рону экрана?
10. Назовите, по крайней мере, четыре атрибута, которые можно использовать с
адаптером для цветного дисплея.
11. Какая функция из прерывания INT 10h отображает символ на экране без изме&
нения атрибутов экрана?
12. Какая функция прерывания INT 10h устанавливает видеорежим?
13. Когда значение помещается в стек, размер стека в памяти увеличивается. В сто&
рону каких адресов увеличивается размер стека: старших или младших?
14. Почему структура стека называется LIFO?

Глава 6. Процедуры и прерывания 187

Стр. 187
Глава 7

Логические вычисления
В этой главе...

Команды логических вычислений и сравнения


Логические структуры
Циклы с условием
Директивы для логических вычислений
Резюме
Контрольные вопросы

Команды логических вычислений и сравнения


Язык ассемблера отлично справляется с двоичными данными и операции с отдель&
ными битами выполняются в этом языке очень эффективно. Такие операторы, как AND,
OR, NOT или XOR, используются в тех командах языка ассемблера, где необходимо произ&
водить логические операции с отдельными битами.
Булевы команды основаны на операциях булевой алгебры. Эти операции разрешают
модификацию отдельных бит в двоичных числах, как показано в табл. 7.1.

Таблица 7.1. Логические операции


Операция Описание
AND Результат равен 1, только если оба бита равны 1
OR Результат равен 1, когда хотя бы один бит равен 1
XOR Результат равен 1, только когда биты разные (исключающее ИЛИ)
NOT Результатом является противоположное значение (другими словами,
1 становится 0, а 0 становится 1)
NEG Получает двоичное дополнение числа
TEST Выполняется команда AND без записи результата, устанавливаются только флаги
BT, BTR, BTC, BTS Копирует бит n из исходного операнда во флаг переноса и
переключает/сбрасывает/устанавливает тот же бит в исходном операнде
CMP Сравниваются два операнда, устанавливаются соответствующие флаги

Стр. 188
отсутствует. 189

Операторы AND, OR, NOT и XOR выполняют поразрядные операции для целых чисел во
время трансляции. Операнд может быть целой константой или символом. Например, до&
пустимы следующие выражения:
X = 1101101b
Y = X AND 00001111b
Z = X OR 11110000b
Q = NOT Z
W = Y XOR 0FFFFh

Команда AND
Команда AND выполняет логическое умножение (И) для каждого разряда двух операн&
дов и помещает результат в первый операнд. Разрешены следующие комбинации.
AND регистр, регистр
AND регистр, память
AND регистр, значение
AND память, регистр
AND память, значение
Регистр, память или значение могут иметь 8, 16 или 32 разряда, при этом операторы
должны иметь одинаковый размер. Для каждых соответствующих битов в двух операндах
применимо следующее правило: если оба бита равны 1, то результирующий бит равен 1,
в противном случае он равен 0. Используются следующие флаги: переполнения, знака,
нуля, паритета, переноса и дополнительного переноса. В следующем примере показан
результат операции AND для 4&разрядных чисел.

0 0 1 1
AND 0 1 0 1
Результат: 0 0 0 1

С помощью команды AND можно очищать отдельные биты в операнде, при этом за&
щищая или маскируя оставшиеся биты. В следующем примере значение 00001111 явля&
ется битовой маской.
MOV AL,00111011b
AND AL,00001111b ; AL = 00001011b
Операцию AND можно использовать для установки байта состояния клавиатуры, для DOS
который может размещаться по адресу 0040:0017. Установленный в единицу бит 5 оп&
ределяет состояние клавиши <NumLock> как включенное. Можно выключить клавишу
<NumLock>, просто сбросив бит 5 (установив в нуль):
MOV BX,17h ; Указать на байт клавиатуры.
AND BYTE PTR [BX],11011111b ; Выключить NumLock.
Здесь требуется оператор BYTE PTR, без которого ассемблер не может определить раз&
мер операнда памяти.

Команда OR
Команда OR выполняет операцию логического сложения (ИЛИ) между соответствую&
щими битами двух операндов и помещает результат в первый операнд. Следующие ком&
бинации операндов разрешены.

Глава 7. Логические вычисления 189

Стр. 189
OR регистр, регистр
OR регистр, память
OR регистр, значение
OR память, регистр
OR память, значение
Регистр, память или значение могут иметь 8, 16 или 32 разряда, кроме того, операторы
должны иметь одинаковый размер. Для каждых соответствующих битов в двух операндах
применимо следующее правило: если оба бита равны 0, то и результирующий бит равен 0,
в противном случае результат равен 1. Используются флаги переполнения, знака, нуля, па&
ритета, переноса и дополнительного переноса. В следующем примере показан результат
операции OR для 4&разрядных чисел.
0 0 1 1
OR 0 1 0 1
Результат: 0 1 1 1

Например, применим команду OR к значениям 3Bh и 0Fh. Нижние четыре бита уста&
новятся, а старшие биты не изменятся.
MOV AL,00111011b ; 3Bh.
OR AL,00001111b ; AL = 3Fh.
Такой способ можно использовать для преобразования десятичного числа в кодиров&
ку ASCII (табл. 7.2.), устанавливая биты 4 и 5. Например, AL=05h можно преобразовать
в символ 5 согласно кодировке ASCII (35h) с помощью команды OR с числом 30h.

Таблица 7.2. Преобразование десятичного числа в кодировку ASCII


Двоичное значение Шестнадцатеричное значение
00000101 05h
OR 00110000 30h
Результат: 00110101 35h ('5')

Последовательность команд для преобразования будет следующей.


MOV DL,5 ; Двоичное значение.
OR DL,30h ; Преобразование в ASCII.
Можно использовать команду OR для проверки знака числа или равенства нулю. Ко&
манда OR не изменит значения числа, только установит соответствующие флаги.
OR AL,AL
Если флаг нуля ZF = 1, то значение в AL должно быть равно 0, если флаг знака SF = 1,
значение в AL отрицательное, и если SF = 0, значение больше нуля.
Подобно тому, как ранее команда AND использовалась для изменения состояния кла&
виши <NumLock> в байте статуса клавиатуры, можно устанавливать и состояние клави&
ши <CapsLock> изменением бита 6. Следующая подпрограмма может быть полезна при
необходимости установить регистр клавиатуры.
PUSH DS ; Сохранить DS.
MOV AX,40h ; Установить в DS адрес BIOS.
MOV DS,AX
MOV BX,17h ; Байт флагов клавиатуры.
OR BYTE PTR [BX],01000000b ; Включить CapsLock.
POP DS ; Восстановить DS.

190 Глава 7. Логические вычисления

Стр. 190
отсутствует. 191

Команда XOR
Команда XOR (исключающее ИЛИ) выполняет логические операции между соответст&
вующими битами двух операндов. Следующие комбинации операндов разрешены.
XOR регистр, регистр
XOR регистр, память
XOR регистр, значение
XOR память, регистр
XOR память, значение
Первый операнд содержит результат операции. Только один операнд может быть опе&
рандом памяти. Для каждых соответствующих битов в двух операндах применимо следую&
щее правило: если оба бита одинаковы, то и результирующий бит равен 0, в противном слу&
чае результат будет равен 1. Используются флаги переполнения, знака, нуля, паритета,
переноса и дополнительного переноса.
Ниже показан результат операции XOR для отдельных битов. Команда XOR устанавли&
вает бит в единицу, только если исходные биты различны:
0 0 1 1
XOR 0 1 0 1
Результат: 0 1 1 0

Инверсия бит
Отличительной особенностью команды XOR является то, что она может инвертиро&
вать саму себя, когда применяется дважды. Предположим, к числу x применяется коман&
да XOR с числом y, в результате получается число z. Затем, если применить операцию XOR
к числам z и y, получим x. Например:
1 1 0 1 0 1 0 1 x
XOR 1 1 1 1 0 0 0 0 y
Равно: 0 0 1 0 0 1 0 1 z = x XOR y
XOR 1 1 1 1 0 0 0 0 y
Равно: 1 1 0 1 0 1 0 1 x = z XOR y

Инверсия флагов клавиатуры


Применим операцию XOR к уже хорошо знакомым флагам состояния клавиатуры. Пе&
реключение клавиши <CapsLock> достигается изменением бита 6 в байте флагов. Ника&
кие другие флаги не должны быть изменены.
PUSH DS ; Сохраняем DS.
MOV AX,40h ; Помещаем в DS адрес BIOS.
MOV DS,AX
MOV BX,17h ; Байт флагов клавиатуры.
XOR BYTE PTR [BX],01000000b ; Переключаем клавишу CapsLock.
POP DS ; Восстанавливаем DS.

Команда NOT
Команда NOT инвертирует все биты исходного операнда, изменяя нули на единицы
и наоборот. Результат называется дополнение до единицы. Следующие операнды разрешены.
NOT регистр
NOT память

Глава 7. Логические вычисления 191

Стр. 191
Например, дополнение до единицы числа F0h будет равняться 0Fh.
MOV AL,11110000b
NOT AL ; AL = 00001111b.

Команда NEG
Команда NEG инвертирует знак числа, преобразовывая число в его дополнение до двух.
Следующие команды разрешены.
NEG регистр
NEG память
Подсчет дополнения до двух от числа может быть выполнен путем инверсии всех его
битов и суммированием с единицей. Используются следующие флаги: переполнения, ну&
ля, знака, дополнительного переноса и переноса.
После выполнения команды NEG проверьте флаг переполнения для определения пра&
вильности результата. Например, если поместить в регистр AL отрицательное число -128,
то в результате операции получим -128 (неправильный результат) и флаг 0F = 1:
MOV al,-128 ; AL = 10000000b
NEG al ; AL = 10000000b, 0F = 1
С другой стороны, если инвертировать +127, то результат будет правильный и 0F = 0:
MOV al,+127 ; AL = 01111111b
NEG al ; AL = 10000001b, 0F = 0

Команда TEST
Команда TEST выполняет команду AND для двух операндов, но не сохраняет резуль&
тат, а только устанавливает флаги. Это особенно удобно для контроля отдельных битов.
Ни один из операндов не изменяется. Следующие команды разрешены.
TEST регистр, регистр
TEST регистр, память
TEST регистр, значение
TEST память, регистр
TEST память, значение
Если любые соответствующие биты установлены в обоих операндах, ZF = 0. Использу&
ются следующие флаги: переполнения, нуля, знака, дополнительного переноса и переноса.
Например, проверить состояние принтера в системе DOS можно с помощью преры&
вания INT 17h, которое проверяет состояние принтера и возвращает байт состояния в ре&
гистр AL. Если пятый бит равен единице, то в принтере нет бумаги. Следующие команды
выполняют проверку и сбрасывают флаг нуля, если установлен бит 5:
MOV AH,2 ; Функция: чтение состояния принтера.
INT 17h ; Вызов BIOS.
TEST AL,00100000b ; ZF = 0, нет бумаги.

Команды BT, BTC, BTR, BTS


Команды BT, BTC, BTR и BTS относятся к группе команд процессора 80386 и более старших
моделей, используемых для проверки наличия данных. В табл 7.3 приведено краткое опи&
сание синтаксиса команд.

192 Глава 7. Логические вычисления

Стр. 192
отсутствует. 193

Таблица 7.3. Команды BT, BTC, BTR и BTS


Команда Описание
BT op,n Копировать бит n из операнда op во флаг переноса
BTC op,n Копировать бит n из операнда op во флаг переноса и переключить
(инвертировать) бит в операнде
BTR op,n Копировать бит n из операнда op во флаг переноса и сбросить бит в операнде
BTS op,n Копировать бит n из операнда op во флаг переноса и установить бит в операнде

Разрешены следующие типы операндов:


op &&&& регистр (16 или 32 разряда), память (16 или 32 разряда), значение (8 разрядов);
n &&&& регистр (16 или 32 разряда), значение (8 разрядов).
В следующих командах показано значение флага переноса и регистра AX после каждой
операции.
MOV AX,8AB6h
BT AX,15 ; CF = 1, AX не изменился.
BTC AX,15 ; CF = 1, AX = 0AB6h.
BTS AX,0 ; CF = 0, AX = 0AB7h.
BTR AX,0 ; CF = 1, AX = 0AB6h.

Команды BSF и BSR


Команды BSF и BSR для процессора 80386 сканируют исходный операнд для поиска
первого установленного бита. Если бит найден, флаг нуля сбрасывается и операнду&
получателю присваивается номер позиции обнаруженного бита. Команда BSF сканирует от
бита 0 к старшим битам, а BSR сканирует от старшего бита. Синтаксис команд BSF и BSR:
BSF получатель, отправитель
BSR получатель, отправитель
Разрешены следующие типы операндов, при этом подразумевается, что они должны
иметь одинаковый размер:
отправитель &&&& регистр (16 или 32 разряда), память (16 или 32 разряда);
получатель &&&& регистр (16 или 32 разряда).
Например, BSF и BSR помещают значения 1 и 4 в регистр CX.
MOV AX,000100101
BSF CX,AX ; CX = 1
BSR CX,AX ; CX = 4

Команда CMP
Команда сравнения CMP выполняет вычитание операнда&отправителя из операнда&
получателя, но при этом ни один из операндов не модифицируется. Результат отражается
в состоянии регистра флагов. Разрешены следующие комбинации операндов, где первый
операнд всегда является операндом&получателем, а второй &&&& операндом&отправителем.
CMP регистр, регистр
CMP регистр, память
CMP регистр, значение
CMP память, регистр
CMP память, значение

Глава 7. Логические вычисления 193

Стр. 193
Регистры сегментов не могут быть использованы. Применяются следующие флаги:
переполнения, нуля, знака, дополнительного переноса и переноса.
Когда сравниваются значения без знака, флаги нуля и переноса устанавливаются ко&
мандой CMP в соответствии с табл 7.4.

Таблица 7.4. Использование команды CMP для операндов без знака


Результат CMP CF ZF

получатель < отправитель 1 0


получатель = отправитель 0 1
получатель > отправитель 0 0

Когда сравниваются значения со знаком, флаги нуля, знака и переполнения устанав&


ливаются командой CMP в соответствии с табл. 7.5.

Таблица 7.5. Использование команды CMP для операндов со знаком


Результат CMP ZF SF, OF

получатель < отправитель ? SF <> OF


получатель = отправитель 1 ?
получатель > отправитель 0 SF = OF

Команда CMP является базовой в большинстве логических структур. Когда за командой


CMP следует переход по условию, то это эквивалентно использованию условия ЕСЛИ.
В листинге 7.1 приведено три фрагмента использования команды CMP. После Label1
происходит сравнение чисел 10 и 5. Устанавливается флаг переноса, так как вычитание
10 из 5 требует, чтобы был произведен заем единицы. После Label2 сравнение регистра
AX с 1000 устанавливает флаг нуля, так как это результат вычитания 1000 из AX. После
Label3 вычитание 0 из 105 не требует заема, и результат не является нулевым, поэтому
флаги нуля и переноса сброшены.

Листинг 7.1. Использование команды CMP


Label1:
MOV AL,5
CMP AL,10 ; CF = 1
Label2:
MOV AX,1000
MOV EX,1000
CMP EX,AX ; ZF = 1
Label3:
MOV SI,105
CMP SI,0 ; ZF = 0 AND CF = 0

Команда CMPXCHG
Команда сравнения и изменения CMPXCHG для процессора 80486 сравнивает оператор&
получатель с аккумулятором (AL, AX или EAX). Если значения равны, значение оператора&
отправителя копируется в оператор&получатель, в противном случае оператор&получатель
копируется в аккумулятор. Используются все флаги состояния. Формат команды:
CMPXCHG получатель, отправитель

194 Глава 7. Логические вычисления

Стр. 194
отсутствует. 195

Разрешены следующие комбинации:


CMPXCHG память, регистр
CMPXCHG регистр, регистр
Для примера поместим значение 0 в listw, так как значение в памяти по адресу
listw равно значению регистра AX &&&& 1234h:
.DATA
listw DW 1234h,3333h
.CODE
MOV DX,0
MOV AX,1234h
CMPXCHG listw,DX ; listw = 0
А в следующем случае значение 3333h помещается в AX, так как значение памяти по ад&
ресу listw+2 не равно значению 1234h в регистре AX:
MOV DX,0
MOV AX,1234h
CMPXCHG listw+2,DX ; AX = 3333h

Логические структуры
Речь идет не о логических структурах высокого уровня, хотя с помощью системы ко&
манд ассемблера можно смоделировать любую логическую структуру, используя команды
сравнений и переходов. Логическая структура &&&& это лишь несколько логических ко&
манд, работающих совместно. Например, структура WHILE использует условие IF для
оценки условия, как показано в табл. 7.6.

Таблица 7.6. Пример цикла DO*WHILE


Высокий уровень Низкий уровень
do L1: if (a >= b) then
<утверждение_l> jump to L2
<утверждение_2> endif
... <утверждение_l>
... <утверждение_2>
while (a < b) ...
(продолжение здесь) jump to L1
L2:(продолжение здесь)

При использовании условия IF выполняются два шага. Сначала выполняется ариф&


метическая команда или команда сравнения для установки одного или нескольких фла&
гов, затем команда условного перехода заставляет процессор перейти к выполнению с но&
вого адреса. Эти команды делятся на две группы.
1. Арифметические команды и команды сравнения, по результату которых процес&
сор устанавливает определенные флаги.
2. Команды условного перехода, с помощью которых процессор принимает решение
на основе состояния флагов.
Команды из разных групп работают совместно: команды из группы 1 устанавливают
состояние флагов, тогда как команды условного перехода из группы 2 выполняются на

Глава 7. Логические вычисления 195

Стр. 195
основе состояния флагов. В следующем примере команда JZ переходит на метку next,
если AL = 0.
CMP AL,0
JZ next ; Переход, если ZF = 1.
...
next:

Команда Jcond
Команда перехода по условию передает контроль по указанному адресу, когда при&
знак условия установлен. Синтаксис этой команды следующий:
Jcond метка
Суффикс cond определяет признак условия, идентифицируя состояние одного или
нескольких флагов. Например:
JC ; Переход, если флаг переноса установлен.
JNC ; Переход, если флаг переноса сброшен.
JZ ; Переход, если флаг нуля установлен.
JNZ ; Переход, если флаг нуля сброшен.
При каждом условном переходе проверяются один или несколько флагов, возвращая
результат (истина или ложь). Если результатом является истина, то переход совершается,
в противном случае программа продолжает исполнение со следующей команды.
Предположим, необходимо совершить переход на метку equal, когда значения реги&
стров AX и BX равны. В следующем примере CMP устанавливает флаг нуля в единицу, если
AX = BX. Команда JE делает переход, если ZF = 1.
1: CMP AX,BX ; Сравнение AX и BX.
2: JE equal
3: NOT_equal: ; Продолжение здесь, если AX <> BX.
4: ...
5: ...
6: JMP exit
7: equal: ; Переход, если AX = BX.
8: ...
9: exit: ; Окончание всегда здесь.
Для того чтобы лучше понять эту программу, используем различные значения для AX и BX.
Если AX = 5 и BX = 5, то команда CMP (строка 1) устанавливает флаг нуля, так как AX
и BX равны. Команда JE (строка 2) делает переход на метку equal в строке 7. Все команды
с этого места точки выполняются последовательно.
Если AX = 5 и BX = –6, то команда CMP сбрасывает флаг нуля, так как AX и BX не рав&
ны. Команда JE не выполняется, поэтому выполнение продолжается со строки 3, проходя
последовательно все команды до строки 6, а потом происходит переход на метку exit.

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


Существует три типа команд условного перехода: первый тип основан на общем срав&
нении операндов со знаком и без него; второй тип использует только сравнение операн&
дов без знака; третий тип использует сравнение операндов со знаком.
В табл. 7.7 приведен список условных переходов, основанных на общем сравнении.
Процессор делает переход на основе анализа флагов нуля, переноса, паритета или реги&
стров CX и ECX.

196 Глава 7. Логические вычисления

Стр. 196
отсутствует. 197

Таблица 7.7. Переходы, основанные на общем сравнении


Мнемокод Описание Флаги/регистры
JZ Переход, если нуль ZF=1
JE Переход, если равны ZF=1
JNZ Переход, если не нуль ZF=0
JNE Переход, если не равны ZF=0
JC Переход, если флаг переноса установлен CF=1
JNC Переход, если флаг переноса сброшен CF=0
JCXZ Переход, если CX=0 CX=0
JECXZ Переход, если ECX=0 ECX=0
JP Переход, если флаг четности установлен PF=1
JNP Переход, если флаг четности сброшен PF=0

Сравнение чисел без знака


Переходы, основанные на сравнении целых чисел без знака, показаны в табл. 7.8. Этот
тип перехода может быть полезен, когда сравниваются такие числа, как 7FFFh и 8000h,
и предполагается, что первое число меньше последующих.

Таблица 7.8. Переходы на основе сравнения чисел без знака


Мнемокод Описание Флаги
JA Переход, если больше ( op1>op2) CF=0 и ZF=0
JNBE Переход, если не больше или равно ( op1 NOT<=op2) CF=0 и ZF=0
JAE Переход, если больше или равно (op1>=op2) CF=0
JNB Переход, если не больше ( op1 NOT<op2) CF=0
JB Переход, если меньше ( op1<op2) CF=1
JNAE Переход, если не больше или равно ( op1 NOT>=op2) CF=1
JBE Переход, если меньше или равно ( op1<=op2) CF=1 или ZF=1
JNA Переход, если не больше (op1 NOT>op2) CF=1 или ZF=1

Сравнение чисел со знаком


Переходы, основанные на сравнении целых чисел со знаком, показаны в табл. 7.9.
Этот тип перехода может быть полезен, когда сравниваются такие числа, как 80h (-128)
и 7Fh (+127). Предполагается, что первое число меньше последующих. Обратите внимание,
как процессор использует флаг переполнения в этом типе перехода.

Таблица 7.9. Переходы, основанные на сравнении чисел со знаком


Мнемокод Описание Флаги
JG Переход, если больше (op1>op2) SF=OF и ZF=0
JNLE Переход, если не меньше или равно (op1 NOT<=op2) SF=OF и ZF=0
JGE Переход, если больше или равно (op1>=op2) SF=OF
JNL Переход, если не меньше (op1 NOT< op2) SF=OF

Глава 7. Логические вычисления 197

Стр. 197
Окончание табл. 7.9
Мнемокод Описание Флаги
JL Переход, если меньше (op1<op2) SF<>OF
JNGE Переход, если не больше или равно (op1 NOT>=op2) SF<>OF
JLE Переход, если меньше или равно (if op1<=op2) ZF=1 или SF<>OF
JNG Переход, если не больше (op1 NOT>op2) ZF=1 или SF<>OF
JS Переход, если отрицательное (op1 отрицательное) SF=1
JNS Переход, если положительное (op1 положительное) SF=0
JO Переход, если переполнение ОF= 1
JNO Переход, если нет переполнения ОF = 0

Генерация кодов для условных переходов


Оператор SHORT можно использовать для улучшения генерируемых ассемблером ко&
дов при переходе вперед с использованием процессоров 386 или более поздних его моде&
лей. Рассмотрим следующий простой пример, в котором регистр AX сравнивается сам
с собой с последующей командой перехода JNE на метку L2.
CMP AX,AX
JNE L2
MOV BX,2
L2:
Ассемблер создаст следующий машинный код. Заметьте, так как переход совершается
вперед, два пустых байта вставляются со смещения 000B из&за того, что ассемблер не
может знать при первом прохождении, на какое количество байтов совершается переход
и что в данном случае необходимо лишь два байта.
0007 3BCO CMP AX,AX
0009 7505 JNE #TEST#12 (0010)
000B 90 nop
000C 90 nop
000D BB0200 MOV BX,0002
0010 (L2:)

Предотвратить создание ассемблером пустых байтов в объектном коде для данного


примера можно, если использовать оператор SHORT.
CMP A X , AX
JNE SHORT L2
MOV BX,2
L2:
Ассемблер сгенерирует более компактный код:
0007 3BCO CMP AX,AX
0009 7503 JNE #TEST#12 (000E)
000B BB0200 MOV BX,0002
000E (L2:)

198 Глава 7. Логические вычисления

Стр. 198
отсутствует. 199

Примеры условных переходов


В следующих кодах сравниваются два числа без знака в регистрах AX и BX, и большее
число помещается в регистр DX.
MOV DX,AX ; Положим AX больше.
CMP AX,BX ; Если AX >= BX, тогда
JAE L1 ; переход на L1,
MOV DX,BX ; иначе помещаем BX в DX.
L1:
Можно также сравнить значения без знака в регистрах AL, BL и CL и присвоить наи&
меньшее значение переменной.
MOV small,AL ; Положим в AL наименьшее.
CMP small,BL ; Если small<=BL, тогда
JBE L1 ; переход на L1,
MOV small,BL ; иначе помещаем BL в small.
L1: CMP small,CL ; Если small<=CL, тогда
JBE L2 ; переход на L2,
MOV small,CL ; иначе помещаем CL в small.
L2:

Проверка ввода букв


В программе иногда необходимо проверить, что входные символы находятся в диапа&
зоне 'a'...'z' или 'A'...'Z'. Задачу можно упростить, если сбрасывать бит 5, полу&
чая только прописные символы. Например, в AL находится исходный символ:
PUSH AX ; Сохраняем AX.
AND AL,11011111b ; Превращаем в прописной.
CMP AL,'A' ; Проверяем диапазон 'A'...'Z'.
JB B1
CMP AL,'Z'
JA B1
TEST AX,0 ; Устанавливаем ZF=1.
B1:POP AX ; Восстанавливаем AX.
В листинге 7.2 приводится программа isAlpha, проверяющая входные символы.

Листинг 7.2. Проверка входных символов


Title Проверка входных символов.
; Программа читает и отображает символы,
; пока не будет введен небуквенный символ.
.386
INCLUDE Study32.inc
.DATA
chr BYTE ?
.CODE
main PROC
L1:
INVOKE ReadChar
CALL isAlpha ; Проверка значения в AL.
JNZ ext ; Выход, если не буква.
INVOKE WriteChar
JMP L1 ; Продолжение цикла.
ext:
main ENDP

Глава 7. Логические вычисления 199

Стр. 199
; Процедура isAlpha устанавливает ZF = 1,
; если символ в AL является буквой.
isAlpha PROC
PUSH AX ; Сохранить AX.
AND AL,11011111b ; Преобразование в прописной символ.
CMP AL,'A' ; Проверка диапазона'A'...'Z'.
JB B1
CMP AL,'Z'
JA B1
TEST AX,0 ; ZF = 1.
B1:
POP AX ; Восстановление AX.
RET
isAlpha ENDP
END main

Наибольшее и наименьшее значения в массиве


Допустим, необходимо найти наибольшее и наименьшее значения в массиве целых
16&разрядных чисел. Для этого сначала инициализируются две переменные smallest
и largest, а затем каждое значение массива сравнивается с этими переменными, произ&
водя при необходимости нужные изменения.
В программе из листинга 7.4 используется команда JGE. Поскольку массив представлен
числами со знаком, то команда JAE не будет работать правильно. Например, если исполь&
зовать JAE для сравнения -1 и 0, то будет считаться, что используются числа без знака,
и число -1 будет представлено как FFFFh. Оно же будет считаться наибольшим числом.
Команда JGE корректно интерпретирует числа и сделает вывод, что 0 больше, чем -1.

Листинг 7.3. Наибольшее и наименьшее числа со знаком


Title Поиск наибольшего и наименьшего значений
.386
INCLUDE Study32.inc
.DATA
largest DWORD ?
smallest DWORD ?
array DWORD -1,2000,-421,32767,500,0,-26,-4000
arrayCount = 8
.CODE
main PROC
MOV EDI,OFFSET array
MOV EAX,[EDI] ; Получить первый элемент.
MOV largest,EAX ; Инициализировать наибольший.
MOV smallest,EAX ; Инициализировать наименьший.
MOV ECX,arrayCount ; Счетчик циклов.
L1:
MOV EAX,[EDI] ; Получить значение массива
CMP EAX,smallest ; [DI] >= smallest?
JGE L2 ; Да: пропуск,
MOV smallest,EAX ; нет: поместить [DI] в наименьшее.
L2:
CMP EAX,largest ; [DI] <= largest?
JLE L3 ; Да: пропуск,
MOV largest,EAX ; нет: поместить [DI] в наибольшее.

200 Глава 7. Логические вычисления

Стр. 200
отсутствует. 201

L3:
ADD EDI,4 ; Указать следующий номер.
LOOP L1 ; Повторять, пока CX не равно 0.

MOV EAX,largest
INVOKE WriteInt ; Вывод значения largest.
INVOKE Crlf
INVOKE WaitMsg
EXIT
main ENDP
END main

Команда SETcond
Команда SETcond для процессоров 80386 устанавливает в байт операнда 1, если усло&
вие истинно, и 0 &&&& если ложно. Синтаксис:
SETcond регистр8
SETcond память8
Слово cond здесь условно обозначает флаг, который процессор должен проверить.
Параметр регистр8 может быть любым 8&разрядным регистром, а память8 &&&& любой 8&
разрядный операнд памяти. Возможные значения для cond включают суффиксы команд
условных переходов, как показано в табл. 7.10 и 7.11.

Таблица 7.10. Суффиксы команд условных переходов SETcond


Суффикс Значение Суффикс Значение
E равно C перенос
A выше NC нет переноса
B ниже L меньше
AE выше или равно GE больше или равно
O переполнение NO нет переполнения

Таблица 7.11. Использование команд SETcond


Команда Описание
SETZ AL устанавливает AL в 1, если ZF=1
SETE AL устанавливает AL в 1, если ZF=1
SETNC myFlag устанавливает myFlag в 1, если CF=0
SETAE myFlag устанавливает myFlag в 1, если CF=0
SETGE BYTE PTR [SI] устанавливает [SI] в 1, если SF=OF
SETNO BYTE PTR [SI] устанавливает [SI] в 1, если SF=OF

В следующем примере регистр AL=1 после команды SETB, так как команда CMP уста&
навливает флаг переноса.
MOV BX,20h
CMP BX,30h
SETB AL ; AL = 1

Глава 7. Логические вычисления 201

Стр. 201
С другой стороны, AL=0 после SETE, так как команда CMP не устанавливает флаг нуля.
MOV BX,20h
CMP BX,30h
SETE AL ; AL = 0

Циклы с условием
Команды LOOPZ и LOOPE
Команды LOOPZ и LOOPE (повторять пока установлен флаг нуля и счетчик больше ну&
ля) разрешают повторение цикла, пока ZF=1 и CX>0. Синтаксис этих команд следующий.
LOOPZ метка
LOOPE метка
Сначала декрементируется CX. Затем, если CX>0 и ZF=1, процессор совершает переход
на метку, в противном случае начинает выполняться следующая команда.
В листинге 7.4 команда LOOPZ просматривает массив целых чисел, пока не встретится
ненулевое значение. Поскольку команда CMP сравнивает каждое значение с 0, то флаг нуля
будет установлен или сброшен. Если встречается ненулевое значение, то команда LOOPZ
не делает переход на метку next.

Листинг 7.4. Поиск нулевого значения


.DATA
intarray DW 0,0,0,0,1,20,35,-12,66,4,0
ArraySize = ($-intarray) / 2
.CODE
MOV BX,OFFSET intarray ; Указывает на массив.
SUB BX,2 ; Отступить на одну позицию.
MOV EX,ArraySize ; Повторить ArraySize раз.
next:
ADD BX,2 ; Перейти к следующему значению.
CMP WORD PTR [BX],0 ; Сравнить значение с нулем.
LOOPZ next ; Повторять, пока ZF = 1 и CX > 0.

Использование команд, изменяющих флаги между командой CMP и последующей коман&


дой циклического перехода, приводит к ошибкам.

Команды LOOPNZ и LOOPNE


Команды LOOPNZ и LOOPNE (повторять, пока сброшен флаг нуля и счетчик больше
нуля) эквивалентны LOOPZ и LOOPE. Цикл продолжается, пока CX>0 и ZF=0.
Следующие команды сканируют каждый элемент 8&разрядного массива, пока не най&
дется положительное число (бит знака станет равен нулю).

Листинг 7.5. Поиск положительного числа


.DATA
byteArray DB -3,-6,-1,-10,10,30,40,4
arraySize = ($-byteArray)

202 Глава 7. Логические вычисления

Стр. 202
отсутствует. 203

.CODE
MOV SI,OFFSET byteArray-1
MOV EX,arraySize
next:
INC SI
TEST BYTE PTR [SI],10000000b
LOOPNZ next

Обратите внимание, что команда INC перед командой TEST не изменяет флаг нуля меж
ду командами TEST и LOOPNZ.

Директивы для логических вычислений


Директива .IF
В ассемблере Microsoft (MASM) определена директива .IF, которая позволяет запи
сывать логические выражения более наглядно и понятно, подобно тому, как это делается
в языках высокого уровня. Синтаксис условного выражения с использованием директи
вы .IF будет следующий:
.IF условие1
утверждения
[.ELSEIF условие2
утверждения ]
[.ELSE
утверждения]
.ENDIF
Выражения, заключенные в круглые скобки, не являются обязательными, а директи
вы .IF и .ENDIF должны присутствовать всегда. Под словами ‘‘условие1’’ и ‘‘условие2’’
скрываются булевы выражения, использующие такие операторы, как <, >, == или !=. Бу
левы выражения будут вычисляться во время выполнения программы. Ниже приведено
несколько примеров допустимых условных выражений.
EAX > 1000h
val1 <= 100
val2 == EAX
val3 != EBX
Можно использовать и составные условные выражения, как показано ниже.
(EAX > 0) && (EAX < 1000h)
(val1 <= 100) || (val2 <= 100)
(val2 != EBX) && !CARRY?
Полный список операторов отношений приведен в табл. 7.12.

Таблица 7.12. Операторы отношений и логические операторы


Оператор Описание
выраж1 == выраж2 Возвращает TRUE, если выражения равны
выраж1 != выраж2 Возвращает TRUE, если выражения не равны
выраж1 > выраж2 Возвращает TRUE, если выраж1 больше выраж2
выраж1 >= выраж2 Возвращает TRUE, если выраж1 больше или равно выраж2

Глава 7. Логические вычисления 203

Стр. 203
Окончание табл. 7.12
Оператор Описание
выраж1 < выраж2 Возвращает TRUE, если выраж1 меньше выраж2
выраж1 <= выраж2 Возвращает TRUE, если выраж1 меньше или равно выраж2
!выраж Возвращает TRUE, если выражение ложно
выраж1 && выраж2 Возвращает логическое AND между выраж1 и выраж2
выраж1 || выраж2 Возвращает логическое OR между выраж1 и выраж2
выраж1 & выраж2 Возвращает поразрядное AND между выраж1 и выраж2
CARRY? Возвращает TRUE, если установлен флаг переноса
OWERFLOW? Возвращает TRUE, если установлен флаг переполнения
PARITY? Возвращает TRUE, если установлен флаг четности
SIGN? Возвращает TRUE, если установлен флаг знака
ZERO? Возвращает TRUE, если установлен флаг нуля

Рассмотрим, как ассемблер преобразовывает директиву .IF. Например, в следую


щих строках используются 32разрядные беззнаковые целочисленные переменные val1
и result.
MOV EAX,6
.IF EAX > val1
MOV result,1
.ENDIF
Ассемблер преобразует эти строки в следующие
MOV EAX,6
CMP EAX,val1
JBE @C0001
MOV result,1
@C0001
Метка @C0001 будет создана компилятором. Ее несколько необычное написание га
рантирует, что она будет уникальна среди других меток.
Необходимо отметить, что компилятор автоматически определяет тип сравниваемых
операндов и в соответствии с этим подставляет необходимый код. Если предположить,
что в предыдущем примере сравниваются знаковые переменные, то преобразование бу
дет сделано следующим образом:
MOV EAX,6
CMP EAX,val1
JLE @C0001
MOV result,1
@C0001
Таким образом, в третьей строке будет использоваться оператор для сравнения пере
менных со знаком.
Но что произойдет, если сравнивать два регистра? Ведь в этом случае ассемблер не
сможет определить тип значений, помещенных в регистры. Поэтому в такой ситуации
всегда используется команда JBE.
Рассмотрим небольшой, но достаточно полезный пример с использованием директи
вы .IF. Напишем процедуру установки курсора в нужное место, в которой до установки
курсора производится проверка диапазона допустимых значений.

204 Глава 7. Логические вычисления

Стр. 204
отсутствует. 205

Листинг 7.6. Процедура установки курсора


SetCursorPosition PROC
; Устанавливает курсор по заданным координатам.
; Принимает: DL = координата X, DH = координата Y
; Возвращает: ничего
.data
BadXCoordMsg BYTE "X out of range!",0Dh,0Ah,0
BadYCoordMsg BYTE "Y out of range!",0Dh,0Ah,0
.code
.IF (DL < 0) || (DL > 79)
mov edx,OFFSET BadXCoordMsg
call WriteString
jmp quit
.ENDIF
.IF (DH < 0) || (DH > 24)
mov edx,OFFSET BadYCoordMsg
call WriteString
jmp quit
.ENDIF
call Gotoxy
quit:
ret
SetCursorPosition ENDP

Директивы .REPEAT и .WHILE


Директивы .REPEAT и .WHILE удобно использовать для написания циклов, для кото&
рых используются команды CMP и команды условного перехода.
Директива .REPEAT повторяет тело цикла до тех пор, пока результат выполнения ус&
ловного выражения для директивы .UNTIL не примет значение TRUE.
.REPEAT
утверждения
.UNTIL условие
В директиве .WHILE проверка выполнения условия производится до того, как начнет
выполняться очередная итерация.
.WHILE условие
утверждения
.ENDW
Например, в следующих строках отображаются значения от 1 до 10 с помощью дирек&
тивы .WHILE.
MOV EAX,0
.WHILE EAX < 10
INC EAX
CALL WriteDec
CALL Crlf
.ENDW
А в этих строках отображаются числа от 1 до 10 с помощью директивы .REPEAT.
MOV EAX,0
.REPEAT
INC EAX

Глава 7. Логические вычисления 205

Стр. 205
CALL WriteDec
CALL Crlf
.UNTIL EAX ==10
Можно использовать и вложения выражений, которые используют директивы для ус&
ловных выражений. Например, в приведенной ниже конструкции на языке высокого
уровня используется вложение условных выражений IF в цикл.
while( op1 < op2 )
{
opl++;
if( op2 == op3 )
X = 2;
else
X=3
}
Аналогичную конструкцию можно написать и на языке ассемблера. В данном случае
переменные opl, op2, и op3 помещаются в регистры, чтобы не использовать в одной
команде два операнда памяти, что в ассемблере запрещено.
.data
opl DWORD 2
op2 DWORD 4
op3 DWORD 5
.code
mov eax,opl
mov ebx,op2
mov ecx,op3
.WHILE eax < ebx
inc eax
.IF ebx == ecx
mov X,2
.ELSE
mov X,3
.ENDIF
.ENDW

Резюме
В данной главе продолжено изучение основных команд процессоров с архитектурой х86.
Рассмотрены команды логических вычислений, используемых при создании логических
структур программы и обработке различных условий, на основе которых происходит пе&
реход на отдельные ветви программы. С помощью этих команд можно разработать логи&
ческие структуры, соответствующие операторам языков высокого уровня, таким как IF,
WHILE, FOR, CASE, и в дальнейшем использовать их в своих разработках.

Контрольные вопросы
1. Какие логические операторы используются в языке ассемблера?
2. Могут ли логические операторы выполняться во время трансляции?
3. Объясните различие между командами JMP и JZ.
4. Какой флаг используется при сравнении чисел без знака?

206 Глава 7. Логические вычисления

Стр. 206
отсутствует. 207

5. Какой флаг используется при сравнении чисел со знаком?


6. Какой условный переход используется для проверки содержимого регистра общего
назначения?
7. Какое различие между командами JL и JNE?
8. Какое различие между командами JA и JAE?
9. Можно ли при переходе по условию делать переход на метку в любом месте сегмента?
10. После выполнения команды JB какой флаг должен быть установлен?
11. Какие значения будут в регистрах AL и BL после выполнения следующих команд?
.DATA
val1 DB 6Bh
val2 DB 3Fh
.CODE
MOV AL,val1
MOV BL,val2
AND AX,0B6h
CMP AL,BL
JA label1
MOV AL,BL
JMP exit
label1:
MOV BL,AL
exit:
12. Какие команды используются для создания циклов с условием?
13. Напишите пример с использованием директив для логических выражений.

Глава 7. Логические вычисления 207

Стр. 207
Глава 8

Арифметика целых чисел


В этой главе...

Команды сдвига
Простые прикладные программы
Сложение и вычитание больших чисел
Умножение и деление
Кодировка ASCII и арифметика упакованных чисел
Резюме
Контрольные вопросы

Команды сдвига
Эта глава посвящена выполнению арифметических операций с целыми числами на
языке ассемблера. Команды сдвига и циклического сдвига используются при умножении
и делении. Такие команды в языках высокого уровня отсутствуют, поэтому при разра)
ботке приложений на этих языках для достижения высокой производительности необхо)
димо использовать язык ассемблера. В главе рассмотрены основные арифметические
операции: сложение, вычитание, умножение и деление с использованием команд сдвига.
В конце кратко будут затронуты вопросы арифметики с числами в кодировке ASCII
(одна цифра на байт) и арифметика упакованных десятичных чисел. В главе описаны
только основные команды и приемы работы.
Команды сдвига используют флаги переполнения и переноса.

Команда SHL
Команда SHL (сдвиг влево) перемещает каждый бит в операнде влево, заполняя
младшие биты нулями. Старший бит помещается во флаг переноса, а бит, который до
этого находился во флаге переноса, теряется (рис. 8.1).

Флаг CF 0

7 6 5 4 3 2 1 0

Рис. 8.1. Сдвиг влево

Стр. 208
отсутствует. 209

Синтаксис команды следующий:


SHL операнд,1
SHL операнд,CL
SHL операнд,значение8 (для 80286 и выше)
Операнд может содержать 8, 16 или 32 разряда. Регистр CL не изменяется во время
выполнения команды. Аналогичные форматы используются и для команд SHR, SAL, SAR,
ROR, ROL, RCR и RCL.
В приведенных форматах для сдвига влево в первом случае сдвигается или регистр,
или операнд памяти на 1 бит влево. Во втором случае используется счетчик сдвига в регистре
CL для указания количества битов, на которое должен быть произведен сдвиг. И в треть&
ем случае, разрешенном для процессора 80286 и более поздних моделей, количество би&
тов для сдвига указывается непосредственно 8&разрядным значением.
Ниже приведено несколько примеров использования регистров и операндов памяти
с командой SHL.
SHL BL,1 ; Сдвигает регистр BL на 1 бит влево.
SHL wordval,1 ; 16-разрядный операнд памяти.
SHL BYTE PTR[SI],1 ; Косвенный 8-разрядный операнд.
SHL AL,CL ; Сдвиг AL влево со счетчиком.
SHL BX,5 ; Для 80286.
SHL EBX,16 ; Для 80386.

Быстрое умножение
Команду SHL удобно использовать для быстрого умножения. Стандартные команды
умножения работают значительно медленнее, чем с использованием этого способа. Ко&
личество битов, на которое необходимо сделать сдвиг, должно равняться двоичному ло&
1
гарифму множителя. Например, умножение на 2 (2 ) &&&& это сдвиг на один бит, умноже&
2
ние на 4 (2 ) &&&& сдвиг на 2 бита и т.д. В следующем примере показано десятичное
значение регистра DL после каждого сдвига.
MOV DL,1 ; DL = 1d.
SHL DL,1 ; DL = 2d.
SHL DL,1 ; DL = 4d.
SHL DL,1 ; DL = 8d.
Для умножения на число, которое не является степенью 2, можно использовать раз&
ложение по формуле. Например, умножение числа на десять можно разложить на два
3
этапа: сначала умножить на 8 (2 ) и затем сложить с числом, умноженным на два, т.е. ис&
3
пользовать формулу 10=2 +2.

Команды SHLD и SHRD


Команда сдвига влево двойного слова SHLD для процессора 80386 и более поздних мо&
делей сдвигает значение в операнде&приемнике на указанное количество бит. Освобож&
дающиеся справа биты заполняются старшими битами операнда&отправителя, причем
значение операнда&отправителя не изменяется. Используются флаги знака, нуля, пари&
тета, переноса и дополнительного переноса.
Команда сдвига вправо двойного слова SHRD для процессора 80386 сдвигает значение
в операнде&получателе вправо на указанное число битов. Освобождающиеся слева биты
заполняются младшими битами операнда&отправителя. Операнд&отправитель не изменя&
ется. Синтаксис команды следующий:

Глава 8. Арифметика целых чисел 209

Стр. 209
SHLD регистр16, регистр16, счетчик
SHLD память16, регистр16, счетчик
SHLD регистр32, регистр32, счетчик
SHLD память32, регистр32, счетчик
Например, в следующих командах происходит сдвиг влево на 4 бита переменной wval
и вставляются 4 старших бита из регистра AX в младшие позиции wval.
.DATA
wval DW 9BA6h
.CODE
MOV AX,0AC36h
SHLD wval,AX,4 ; wval = BA6Ah.
В примере ниже регистр AX сдвигается вправо на 4 бита, и младшие 4 бита регистра DX
помещаются на место старших битов регистра AX.
MOV AX,234Bh
MOV DX,7654h
shrd AX,DX,4 ; AX = 4234h.
В следующем примере на 1 бит влево сдвигается регистр EBX, а старший бит пере
менной dval помещается в младшую позицию EBX:
.DATA
dval DD 8124365Ah
.CODE
MOV EBX,00000006h
SHLD EBX,dval,1 ; EBX = 0000000Dh.
Эти команды широко используются в прикладных графических программах, где
необходимо распределять битовые маски изображений для вывода на экран. Они также
находят применение при шифровании данных, где алгоритм шифрования использует
сдвиги битов.

Команда SHR
Команда сдвига вправо SHR сдвигает каждый бит вправо, заменяя старший бит нулем.
Младший бит копируется во флаг переноса, а бит, находившийся ранее во флаге перено
са, теряется (рис. 8.2).

0 Флаг CF

7 6 5 4 3 2 1 0

Рис. 8.2. Сдвиг вправо

Синтаксис команды SHR такой же, как и для команды SHL. В следующем примере 0 из
младшего бита регистра AL копируется во флаг переноса, а старший бит AL сбрасывается:
MOV AL,0D0h ; AL = 11010000b.
SHR AL,1 ; AL = 01101000b, CF = 0.
Команда SHR может использоваться для деления чисел без знака на 2. Например,
осуществив сдвиг числа 32 на 1 бит вправо, получим 16.
MOV DL,32 ; 00100000b
SHR DL,1 ; 00010000b (DL = 16)

210 Глава 8. Арифметика целых чисел

Стр. 210
отсутствует. 211

Производя сдвиг на несколько битов, можно выполнять быстрое деление на делитель,


являющийся степенью числа 2. В следующем примере 64 делится на 8.
MOV AL,01000000b ; AL = 64.
SHR AL,3 ; Деление на 8, AL = 0000l000b.
Если необходимо производить деление над числами со знаком, лучше использовать
команду SAR, так как она сохраняет бит знака.

Команды SAL и SAR


Команды арифметического сдвига влево SAL и арифметического сдвига вправо SAR
применяются для сдвига чисел со знаком. Команда SAL подобна команде SHL, она вклю
чена в систему команд для получения логической стройности. Команда SAR сдвигает ка
ждый бит вправо и копирует бит знака (рис. 8.3).

Флаг CF

7 6 5 4 3 2 1 0

Рис. 8.3. Арифметический сдвиг вправо

Команда SAR копирует младший бит операндаполучателя во флаг переноса, сдвигает


операнд вправо на одну позицию и дублирует бит знака. Можно использовать сдвиг на
несколько битов в соответствии со счетчиком в регистре CL. Синтаксис команд SAR и SHR
идентичен синтаксису SHL и SHR.
В следующем примере показано, как SAR дублирует бит знака. В регистр AL помеща
ется отрицательное число перед сдвигом вправо:
MOV AL,0F0h ; AL = 11110000b (-16).
SAR AL,1 ; AL = 11111000b (-8) CF = 0.
В следующем примере число 32768 сдвигается вправо пять раз, что подобно делению
5
на число 32 (2 ). Результат будет 1024.
MOV DX,8000h ; DX = 1000000000000000b.
SAR DX,5 ; DX = 1111110000000000b.

Команда ROL
Команда циклического сдвига влево ROL сдвигает каждый бит влево. Старший бит ко
пируется во флаг переноса и в младший бит. Синтаксис команды такой же, как и для
команды SHL (рис. 8.4).

Флаг CF

7 6 5 4 3 2 1 0

Рис. 8.4. Циклический сдвиг влево

Отличие команд циклического сдвига от сдвига в том, что биты никогда не теряются.
Бит, который “выталкивается” с одного конца, появляется на другом конце.

Глава 8. Арифметика целых чисел 211

Стр. 211
В следующем примере старший бит копируется и во флаг переноса и в младший бит
с номером 0.
MOV AL,40h ; AL = 01000000b.
ROL AL,1 ; AL = 10000000b, CF = 0.
ROL AL,1 ; AL = 00000001b, CF = 1.
ROL AL,1 ; AL = 00000010b, CF = 0.
Команда ROL удобна для обмена старшей и младшей половинок операнда, как пока
зано в листинге 8.1.

Листинг 8.1. Использование команды ROL для обмена значениями


.286
.DATA
byteval DB 0Fh
wordval DW 1234h
.CODE
MOV AL,26h
ROL AL,4 ; AL = 62h
ROL byteval,4 ; byteval = F0h
MOV AX,0203h
ROL AX,8 ; AX = 0302h
ROL wordval,8 ; wordval = 3412h

Команда ROR
Команда циклического сдвига вправо ROR сдвигает каждый бит вправо. Младший бит
копируется во флаг переноса и одновременно в старший бит. Синтаксис команды здесь
такой же, как для команды SHL (рис. 8.5).

Флаг CF

7 6 5 4 3 2 1 0

Рис. 8.5. Циклический сдвиг вправо

В следующем примере младший бит копируется во флаг переноса и одновременно


в старший бит результата:
MOV AL,01h ; AL = 00000001b.
ROR AL,1 ; AL = 10000000b, CF = 1.
ROR AL,1 ; AL = 01000000b, CF = 0.

Команды RCL и RCR


Команда циклического сдвига влево с копированием RCL сдвигает каждый бит вле
во и копирует старший бит во флаг переноса, который копируется в младший бит ре
зультата (рис. 8.6).
В следующем примере команда CLC сбрасывает флаг переноса. Первая команда RCL
перемещает старший бит регистра BL во флаг переноса и сдвигает все остальные биты

212 Глава 8. Арифметика целых чисел

Стр. 212
отсутствует. 213

Флаг CF

7 6 5 4 3 2 1 0

Рис. 8.6. Циклический сдвиг влево с копированием

влево. Вторая команда RCL перемещает флаг переноса в младший бит и смещает все ос
тальные биты влево.
CLC ; CF = 0.
MOV BL,88h ; BL = 10001000b.
RCL BL,1 ; BL = 00010000b, CF = 1.
RCL BL,1 ; BL = 00100001b, CF = 0.

Команда RCR
Команда циклического сдвига вправо с копированием RCR сдвигает каждый бит впра
во и копирует младший бит во флаг переноса. Флаг переноса копируется в старший бит
результата (рис. 8.7).

Флаг CF
7 6 5 4 3 2 1 0

Рис. 8.7. Циклический сдвиг вправо с копированием

В следующем примере команда STC устанавливает флаг переноса перед выполнением


циклического сдвига.
STC ; CF = 1
MOV AH,10h ; AH = 00010000b, CF = 1
RCR AH,1 ; AH = 10001000b, CF = 0

Простые прикладные программы


Сдвиг нескольких байтов в процессоре 8086
Иногда в программе необходимо сдвинуть все биты массива, например, при переме
щении битовой матрицы графического изображения. В примере показан сдвиг трех бай
тов. Сдвиг начинается с левого байта с сохранением младшего бита во флаге переноса.
Результат сдвига показан в табл. 8.1.

Таблица 8.1. Сдвиг последовательности битов


byte1 byte2 byte3

Перед: 00111011 01000110 11111111


После: 00011101 10100011 01111111

Глава 8. Арифметика целых чисел 213

Стр. 213
После сдвига байт 1 принимает значение 00011101, и устанавливается флаг переноса
(CF=1). Затем используется команда RCR для циклического сдвига байта 2 вправо, когда ко&
пируется содержание флага переноса в старший бит байта 2. После сдвига байт 2 принимает
значение 10100011. Наконец, байт 3 сдвигается вправо, результатом будет 01111111. Эти
три шага повторяются каждый раз для сдвига на несколько битов. В листинге 8.2 сдвига&
ются все биты для трех байтов вправо на четыре бита.

Листинг 8.2. Сдвиг нескольких байтов с помощью команды RCR


.DATA
bytel DB 3Bh ; После сдвига: 03h.
byte2 DB 46h ; После сдвига: B4h.
byte3 DB 0FFh ; После сдвига: 6Fh.
.CODE
MOV CX,4 ; Повторить сдвиг четыре раза.
L1:SHR byte1 ; Старший байт.
RCR byte2,1 ; Средний байт, включая флаг переноса.
RCR byte3,1 ; Младший байт, включая флаг переноса.
LOOP L1

Быстрое умножение и деление


Как уже было показано ранее, команды SHL и SHR эффективно выполняют умноже&
ние и деление, если один операнд является степенью числа 2 (например, 2, 4, 8, 16, 32, . . . ).
Если ни один из операндов не является степенью числа 2, то их можно попытаться раз&
ложить на числа, представляющие собой степень 2. Умножение регистра BX на 36, на&
пример, может быть более эффективным, если последовательно произвести умножение
на 32 и 4, а результаты сложить: BX*36=(BX*32)+(BX*4).
В следующем примере показано, как 16&разрядная переменная с именем intval ум&
ножается на 36. Результат является суммой двух величин, 320 и 40.
.DATA
intval DW 0Ah ; Результат = 0168h (360d).
product DW ?
.CODE
MOV BX,intval
MOV CL,5 ; Умножение на 32.
SHL BX,CL ; BX = 0140h (320d).
MOV product,BX ; Сохранить первый результат.
MOV BX,intval ; Загрузить значение вновь.
SHL BX,1 ; Умножение на 4.
SHL BX,1 ; BX = 0028h (40d).
ADD product,BX ; Сложить результаты.

Строка битов
Часто байт или слово содержат больше одного поля, поэтому приходится выде&
лять короткую последовательность битов, называемую битовой строкой, или строкой
битов. Например, функция 7 DOS возвращает дату файла в регистр DX (дата послед&
него обновления файла). Биты 0&&4 представляют день (от 1 до 31), биты 5&&8 представ&
ляют месяц, а биты 9&&15 &&&& год. Например, предположим, что файл был модифициро&
ван 10 марта 1999 года. Тогда дата будет представлена в регистре DX следующим

214 Глава 8. Арифметика целых чисел

Стр. 214
отсутствует. 215

значением (год считается от 1980 года), как это пока DH DL


зано на рис. 8.8.
Для извлечения отдельного поля необходимо сдви
нуть составляющие его биты в нижнюю часть регистра
0010011001101010
DX, используя команду SHR, а затем выполнить опера
цию AND с соответствующей маской. Для демонстрации Год (915) Месяц (58) День (04)

этого правила выделим день месяца. Сначала сделаем Рис. 8.8. Формат даты файла
копию регистра DL и маску. В регистре AL будет резуль
тирующее значение.
MOV AL,DL ; Делаем копию DL.
AND AL,00011111b ; Сбрасываем биты 5-7.
MOV day,AL ; Сохраняем день.
Для получения номера месяца необходимо сдвинуть биты 58 в правый конец регист
ра AL перед маскированием остальных битов.
MOV AX,DX ; Сделать копию DX.
MOV CL,5 ; Счетчик сдвигов.
SHR AX,CL ; Сдвинуть вправо на 5 бит.
AND AL,00001111b ; Сбросить биты 4-7.
MOV month,AL ; Сохранить месяц.
Номер года (биты 915) занимает почти весь регистр DH. Необходимо переместить его
в регистр AL и сдвинуть на 1 бит вправо.
MOV AL,DH ; Сделать копию DH.
SHR AL,1 ; Сдвинуть вправо на одну позицию.
MOV AH,0 ; Сбросить AH.
ADD AX,1980 ; Счет от 1980 года.
MOV year,AX ; Сохранить год.

Директива RECORD
Директива RECORD (запись) в языке ассемблера описывает группу битов в байте или
слове. Ее используют, чтобы сделать описание битовой маски и сдвиги проще. Сначала
для записи должно быть определено имя и ширина каждого поля. Синтаксис директи
вы следующий:
имя записи RECORD поле1[, поле2] ...

Синтаксис поля:
имя поля: ширина[= выражение]
Имя поля  это имя отдельного поля в записи, причем первое поле располагается
в старших битах байта или слова. Выражение представляет собой целочисленное кон
стантное выражение.
Используем директиву RECORD для определения рас
положения битов в 16разрядной дате для файла. Распо
ложение битов включает 7 битов для номера года, 4 бита 0010011001101010
для месяца и 5 битов для дня месяца (рис. 8.9).
Обычное использование директивы RECORD для да Год (915) Месяц (58) День (04)
ты следующее:
Рис. 8.9. Расположение битов
date_record RECORD year:7, month:4, day:5
в слове, представляющем дату

Глава 8. Арифметика целых чисел 215

Стр. 215
За каждым именем поля следует его ширина, выраженная количеством битов.
Можно присваивать полям значения по умолчанию. Например, инициализация записи
date_record для 1 января 1980 года будет иметь такой вид (значение 0 обозначает 1980 год):
date_record RECORD year:7=0, month:4=1, day:5=l
Если определить запись в восемь битов или меньше, запись автоматически будет ссы&
латься на байт, в противном случае &&&& на слово. Если используются не все позиции, про&
исходит автоматическое выравнивание вправо. Например, следующая запись определяет
только 12 битов. Ассемблер устанавливает младшие биты, а в старшие четыре бита запи&
сывается нуль.
bitrec RECORD fieldl:6=llllllb, field2:6=llllllb
Значение всех 16 битов будет равно 0000111111111111.
Если запись определена, ее можно использовать для создания переменной записи.
Синтаксис:
[имя] имя записи < [значение[,значение]] ...>
Можно присвоить начальное значение любому полю. Если присваиваемое значение
превышает допустимое, ассемблер выводит сообщение об ошибке.
Создадим имя записи, используя ранее определенную запись date_record. Сле&
дующее объявление содержит значения (0,1,1) для 1 января 1980 года.
date_record RECORD year:7=0, month:4=1, day:5=l
.
birthDate date_record <>
Можно инициализировать запись birthDate значением 30 мая 1999 года.
birthDate date_record <19,5,30>

Оператор MASK
Поскольку мы использовали директиву RECORD для определения записи date_record,
можно улучшить предыдущие утверждения. Оператор MASK создает битовую маску &&&& все
биты, соответствующие данному полю, устанавливаются, остальные сбрасываются.
MOV AX,file_date ; Получить дату.
AND AX,MASK month ; Сбросить неиспользуемые биты.
MOV CL,month
SHR AX,CL ; Сдвинуть вправо на 5 бит.

Оператор WIDTH
Оператор ширины поля WIDTH возвращает число выделенных для поля битов в запи&
си. В следующем примере ширина каждого поля в записи date_record определяется
по&разному, как и ширина всей записи. Эти значения определяются во время трансля&
ции, как показано в листинге 8.3.

Листинг 8.3. Использование оператора WIDTH


date_record RECORD year:7=0, month:4=1, day:5=1
.DATA
birthDate date RECORD <>
size_of_year = WIDTH year ; Ширина = 7.
size_of_month = WIDTH month ; Ширина = 4.

216 Глава 8. Арифметика целых чисел

Стр. 216
отсутствует. 217

.CODE
MOV AX,width day ; Ширина = 5.
IF (WIDTH date_record) GT 8 ; Запись 16 разрядов?
MOV AX,birthDate ; Поместить в AX.
ELSE ; Запись 8 разрядов?
MOV AL,birthDate ; Поместить AL.
ENDIF

Сложение и вычитание больших чисел


Команда ADC
Команда сложения с переносом ADC выполняет сложение операндов размером в не&
сколько байтов или несколько слов. Операнд&отправитель и флаг переноса добавляются
к операнду&получателю. Синтаксис:
ADC получатель,отправитель
Операнд&отправитель и операнд&получатель могут быть 8&, 16& или 32&разрядными
регистрами или операндами памяти. Операнд&отправитель может быть непосредствен&
ным значением.
Процедура Multi32_Add из листинга 8.4 суммирует два больших целых числа и со&
храняет результат в памяти. Перед вызовом процедуры определяются указатели операн&
дов и указатель места размещения суммы. Подсчитывается количество суммируемых
двойных слов. В процедуре предполагается, что каждое значение сохраняется вместе
с соответствующим двойным словом.

Листинг 8.4. Программа сложения учетверенных слов


TITLE Сложение учетверенных слов.
.386 ; Допустимы 32-разрядные регистры.
INCLUDE Study32.inc
.DATA
op1 QWORD 0A2B2A40674981234h
op2 QWORD 08010870000234502h
result DWORD 3 dup(?)
.CODE
main PROC
MOV ESI,OFFSET op1
MOV EDI,OFFSET op2
MOV EBX,OFFSET result
MOV ECX,2 ; Счетчик.
CALL Multi32_Add
MOV ESI, OFFSET result
MOV EBX,4
MOV ECX,3
CALL DumpMem
CALL WaitMsg
Exit
main ENDP

; Multi32_Add
; Складывает два целых числа, содержащих несколько двойных слов.
; Входные параметры: ESI и EDI указывают на операнды, BX указывает на

Глава 8. Арифметика целых чисел 217

Стр. 217
; операнд-получатель, и ECX содержит число суммируемых двойных слов.

.CODE
Multi32_Add PROC
PUSHAD
CLC ; Сбрасывает флаг переноса.
L1:MOV EAX,[ESI] ; Получает первый операнд.
ADC EAX,[EDI] ; Суммирует второй операнд.
PUSHFD ; Сохраняет флаг переноса.
MOV [EBX],EAX ; Сохраняет результат.
ADD ESI,4 ; Продвигает все 3 указателя.
ADD EDI,4
ADD EBX,4
POPFD ; Восстанавливает флаг переноса.
LOOP L1 ; Повторение цикла.
ADC WORD PTR [EBX],0 ; Сложить остаток.
POPAD
RET
Multi32_Add ENDP
END main

После выполнения этой программы на экране консоли получаем следующее:


Dump of offset 00403010
-------------------------------
74BB5736 22C32B06 00000001
Press [Enter] to continue...
Поскольку процедура DumpMem отображает ячейки памяти в прямом порядке (от млад&
шего с старшему), а запись в память производится в обратном порядке, то для получения
правильного результата необходимо переставить ячейки. Правильный результат будет
0000000122C32B0674BB5736.

Команда SBB
Команда вычитания с заемом SBB используется для вычитания больших операндов.
Синтаксис этой команды следующий:
SBB получатель,отправитель
Операнд&отправитель и операнд&получатель могут быть 8& или 16&разрядными значе&
ниями. Сначала операнд&отправитель вычитается из операнда&получателя, затем флаг
переноса вычитается из результата.
В листинге 8.5 одно учетверенное слово вычитается из другого. Команда SBB вычита&
ет флаг переноса и содержимое операнда op2 из регистра AL. Директива DQ сохраняет
байты в памяти и изменяет порядок, поэтому используется регистр SI.
В этом примере оператор op1 больше, чем op2, поэтому результат положительный.
Если результат будет отрицательный, флаг переноса будет установлен после последнего
цикла, причем результат будет сохранен в форме двоичного дополнения.
Команда очистки флага переноса CLC сбрасывает флаг переноса. Это необходимо делать
перед командами ADC или SBB только в начале. И напротив, последнее значение флага оп&
ределяет результат. Команда установки флага переноса STC помещает во флаг значение 1.

218 Глава 8. Арифметика целых чисел

Стр. 218
отсутствует. 219

Листинг 8.5. Вычитание учетверенных слов


.DATA
val1 QWORD 20403004362047Alh
val2 QWORD 055210304A2630B2h
result QWORD 0
.CODE
MOV CX,8 ; Счетчик циклов.
MOV ESI,val1 ; Установить индексы в начальное состояние.
MOV EDI,val2
CLC ; Сбросить флаг переноса.
L1:
MOV AL,BYTE PTR val1[ESI] ; Первое число.
SBB AL,BYTE PTR val2[EDI] ; Вычесть второе.
MOV BYTE PTR result[ESI],AL ; Сохранить результат.
DEC ESI
DEC EDI
LOOP L1

Умножение и деление
В системе команд Intel есть операции, которые выполняют умножение и деление 8&,
16& и 32&разрядных целых чисел. Все операнды должны быть двоичными числами, по&
этому все десятичные и двоично&десятичные числа должны быть приведены к необходи&
мому формату. Операции с вещественными числами должны выполняться в блоке обра&
ботки вещественных чисел или должны эмулироваться программно (программы
эмуляции дополнительно поставляются с ассемблером).
Команда умножения MUL и команда деления DIV используют двоичные числа. Ко&
манды целочисленного умножения IMUL и целочисленного деления IDIV применяются
для двоичных чисел со знаком.
Когда процессор выполняет арифметические операции, он рассматривает все операн&
ды как двоичные числа без знака. Если используются числа со знаком, то необходимо
контролировать флаги во избежание переполнения операнда&приемника. Хотя процес&
сор корректно выполняет все арифметические операции с числами без знака, операции
с числами со знаком тоже будут выполняться правильно. Например, сложим FFFFh
и 3000h. Результатом будет 2FFFh.
MOV CX,3000h ; Начальное значение в CX.
ADD CX,0FFFFh ; CX = 2FFF, 0F = 0, CF = 1.
Сумма чисел без знака 3000h и 0FFFFh будет равна 12FFFh, что вызывает переполнение
регистра CX и устанавливает флаг переноса. Сумма чисел со знаком 3000h и FFFFh (–1)
будет равна 2FFFh, что является правильным результатом.
Флаг переполнения указывает, было ли переполнение в результате операций над чис&
лами со знаком. Например, в регистре AL содержится число 7Fh (+127). Добавляем к AL
единицу. Результат 80h (–128) явно неправильный.
После такой операции будет установлен флаг переполнения. Для операций с 8&раз&
рядными числами флаг переноса OF равен c7 XOR c6, где c7 определяет перенос из
бита 7, а c6 &&&& перенос из бита 6. В следующем примере в шестой позиции генерирует&
ся перенос, в отличие от седьмой позиции. Таким образом, OF=(0 XOR 1), что равня&
ется единице.

Глава 8. Арифметика целых чисел 219

Стр. 219
01111111 (+127)
+ 00000001 (+1)
10000000 (-128, OF = 1, CF = 0)

Тот же самый принцип используется при работе с 16&разрядными операндами, когда


выбираются биты 14 и 15, а при работе с 32&разрядными операндами &&&& биты 30 и 31.

Команда MUL
Команда MUL выполняет умножение 8&, 16& или 32&разрядных операндов с помощью
регистров AL, AX или EAX. Синтаксис команды следующий:
MUL множитель
IMUL множитель
Множитель может быть или регистром, или операндом памяти, но не может быть не&
посредственным значением. В табл. 8.2 показаны все регистры, которые используются
при каждом типе операции в зависимости от размера умножаемых чисел.

Таблица 8.2. Регистры команды MUL


Множимое Множитель Результат
AL операнд 8 AX
AX операнд 16 DX:AX
EAX операнд 32 EDX:EAX

В следующих командах 5h умножается на 10h, и результат 0050h помещается в ре&


гистр AX.
MOV AL,5h
MOV BL,10h
MUL BL ; AX = 0050h.
В следующем фрагменте 2000h умножается на 100h, а результат 00200000h поме&
щается в регистры DX:AX.
.DATA
val1 DW 2000h
val2 DW 0100h
.CODE
MOV AX,val1
MUL val2 ; DX = 0020h, AX = 0000h.
В этом фрагменте перемножаются числа 12345678h и 1000h, а результат
123456780000h помещается в регистры EDX:EAX.
MOV EAX,12345678h
MOV EBX,10000h
MUL EBX ; EDX = 00001234h, EAX = 56780000h.
При перемножении двух операндов памяти необходимо поместить один из них в ре&
гистр AL, AX или EAX и перемножить с другим операндом.
MOV AX,integer1
MUL integer2

220 Глава 8. Арифметика целых чисел

Стр. 220
отсутствует. 221

Команда IMUL
Команда IMUL служит для перемножения двоичных чисел со знаком. Результатом
также будет число со знаком. При операции с 8&разрядными числами знак регистра AL
переносится в AH. При 16&разрядных операндах знак в AX переносится в DX, и при 32&раз&
рядных операндах знак EAX переносится в EDX.
При перемножении 8&разрядных операндов IMUL устанавливает флаги переноса и пе&
реполнения, если регистр результата AH не получает знак AL. Это значит, что регистры
знака AH и AL имеют разные значения.
Как для команды MUL, так и для команды IMUL, флаги CF и OF устанавливаются, если
старший байт результата содержит значение, в противном случае они сбрасываются.
Следующие примеры демонстрируют это.
Операция с 8&разрядными числами: 48*4=192.
MOV AL,48
MOV BL,4
IMUL BL ; AX = 00C0h (+192), CF = 1, OF = 1
Результирующее число в AX содержит значение 00C0h (0000000011000000). Здесь
нет расширения знака на регистр AH, поэтому CF=1 и OF=1. Значение результата при
этом больше, чем 7 битов.
Операция с 8&разрядными числами: -4*4=-16.
MOV AL,-4
MOV BL,4
IMUL BL ; AX = FFF0h (-16), CF = 0, OF = 0
Результирующим значением в AX является число FFF0h (&&16), и регистр AH получает
знаковое расширение регистра AL (заполняется знаком AL), поэтому CF=0 и OF=0.
Операция с 16&разрядными числами: 48*4=192.
MOV AX,48
MOV BX,4
IMUL BX ; DX = 0000, AX = 00C0h(+192), CF = 0, OF = 0.
Результат в DX:AX представляет 000000C0h. Знаки DX и AX являются одинаковыми
(положительными), поэтому CF=0 и OF=0.

Команда DIV
Команда DIV выполняет деление 8&, 16& и 32&разрядных чисел без знака. Указывается
один операнд (регистр иди операнд памяти), который является делителем. Синтаксис
этой команды следующий:
DIV делитель
Если делитель &&&& 8&разрядное значение, делимое находится в регистре AX. В AL на&
ходится целая часть результата, а в AH &&&& остаток. Если делитель является 16&разрядным
числом, делимое находится в DX:AX. В AX помещается целая часть результата, а в DX &&&&
остаток (табл. 8.3).

Глава 8. Арифметика целых чисел 221

Стр. 221
Таблица 8.3. Регистры команды DIV
Делимое Делитель Частное Остаток
AX операнд 8 AL AH
DX:AX операнд 16 AX DX
EDX:EAX операнд 32 EAX EDX

Деление 8&разрядных операндов можно выполнить так:


MOV AX,0083h ; Делимое.
MOV BL,2 ; Делитель.
DIV BL ; AL = 41h, AH = 01h.
При делении 16&разрядных операндов DX включает старшую часть делимого, поэтому
его необходимо очистить перед делением. После деления частное помещается в AX, а ос&
таток &&&& в DX.
MOV DX,0 ; Очистка регистра.
MOV AX,8003h ; Делимое.
MOV EX,100h ; Делитель.
DIV EX ; AX = 0080h, DX = 0003h.
При делении 32&разрядных операндов с использованием операнда памяти как делите&
ля, делимое сохраняется в обратном порядке.
.DATA
dividend DQ 0000000800300020h
divisor DD 00000100h
.CODE
MOV EDX,DWORD PTR dividend+4 ; Старшая часть двойного слова.
MOV EAX,DWORD PTR dividend ; Младшая часть двойного слова.
DIV divisor ; EAX = 08003000h, EDX = 00000020h.
Флаги состояния (переполнение, знак, нуль, перенос) после команд DIV или IDIV
не определены.

Команда IDIV
Команда IDIV имеет такой же синтаксис, как и команда DIV с теми же операндами.
Различие заключается в выполнении операций над числами со знаком. Для 8&разрядных
операторов делимое находится в регистре AX, поэтому знак определяется по биту 15. На&
пример, если &&48 разделить на 5, то AL=-9, а AH=-3.
MOV AX,-48 ; AX = FFD0h.
MOV BL,5
IDIV BL ; AX = FDF7h (частное = -9, остаток = -3).
Общая ошибка заключается в том, что перед делением помещают 8&разрядное дели&
мое в регистр AL. В следующем примере команда IDIV выполняется некорректно, так
как делимое получается равным +208 (00D0h), и частное будет неправильным.
MOV AH,0
MOV AL,-48 ; AX = 00D0h (+208).
MOV BL,5
IDIV BL ; AX = 0329h (частное = 41, остаток = 3).

Команды CBW, CWD, CDQ и CWDE


В системе команд есть четыре команды для перевода небольших форматов операндов
со знаком в большие форматы (табл. 8.4). Команды CBW и CWD рассчитаны на процессо&
ры 8086/8088, а CWDE и CDQ &&&& на процессоры 80386.

222 Глава 8. Арифметика целых чисел

Стр. 222
отсутствует. 223

Таблица 8.4. Команды перевода форматов


Команда Описание
CBW Преобразует байт в слово, расширяя AL на AX
CWD Преобразует слово в двойное слово, расширяя AX на DX:AX
CWDE Преобразует слово в расширенное двойное слово, распространяя AX на EAX
CDQ Преобразует двойное слово в учетверенное слово, расширяя EAX на EDX:EAX

Например, для рассмотренного выше примера деления –48 команда CBW обеспечит
правильный результат.
MOV AH,0
MOV AL,-48 ; AX = 00D0h (+208).
CBW ; AX = FFD0h (-48).
MOV BL,5
IDIV BL ; AX = FDF7h (частное=–9, остаток=-3).

В следующих командах AX расширяется на DX перед делением с 16&разрядным опе&


рандом.
MOV AX,-5000 ; DX:AX = ????EC78h
CWD ; DX:AX = FFFFEC78h
MOV BX,256
IDIV BX ; AX = FFEDh, (-19) частное.
; DX = FF78h, (-136) остаток.
Здесь можно использовать команду MOVSX для процессора 80386, которая распро&
страняет знак на старшие значения. Можно также сократить количество команд при де&
лении с 8&разрядными операндами, как показано в примере.
MOVSX AL,-128 ; AX = FF80h
MOV BL,10
IDIV ; AL = -12, AH = –8

Кодировка ASCII и арифметика упакованных чисел


Методы арифметики целых чисел, о которых уже говорилось в этой главе, были про&
демонстрированы для двоичных чисел. Однако процессор должен выполнять арифмети&
ческие действия с числами, которые, например, вводятся с консоли. Эти числа вводятся
в кодировке ASCII и называются строкой цифр в кодировке ASCII. Допустим, требуется
ввести два числа с консоли и сложить их. Для подсчета суммы следует сложить два числа
в кодировке ASCII, после чего необходимо откорректировать результат. Для подобных
операций в языке ассемблера можно использовать четыре команды: команду корректи&
ровки сложения AAA, команду корректировки вычитания AAS, команду корректировки
и команду корректировки деления AAD.
Когда выполняется сложение или вычитание для чисел в кодировке ASCII, операнды
могут быть либо в формате ASCII, либо в неупакованном десятичном формате. Старшие
четыре бита неупакованного десятичного числа всегда равны нулю, в то время как у чи&
сел в кодировке ASCII старшие четыре бита равны 0011b. На рис. 8.10 показано, как
число 3402 будет храниться в обоих форматах.
Неупакованный десятичный формат используется для умножения и деления, поэтому
старшие четыре бита каждого числа должны быть сброшены. Понятно, что арифметика

Глава 8. Арифметика целых чисел 223

Стр. 223
Рис. 8.10. Число 3402 в разных форматах

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


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

Команда AAA
Команда AAA корректирует двоичный результат команд ADD или ADC. Это делает ре&
зультат в регистре AL совместимым с представлением чисел в кодировке ASCII. В сле&
дующем примере показано, как получается корректный результат при сложении двух чи&
сел (8 и 12) в кодировке ASCII с помощью команды AAA. Необходимо очистить регистр
AH перед выполнением сложения. Последняя команда превращает значения в регистрах
AH и AL в числа в кодировке ASCII.
MOV AH,0
MOV AL,'S' ; AX = 0038h.
ADD AL,'2' ; AX = 006Ah.
AAA ; AX = 0100h (ASCII корректировка результата).
OR AX,3030h ; AX = 3130h = '10' (преобразование в ASCII).

Команда AAS
Команда AAS корректирует двоичный результат команд SUB или SBB. Это делает ре&
зультат в регистре AL совместимым с представлением чисел в кодировке ASCII. Данная
корректировка необходима, когда при вычитании получается отрицательный результат.
В следующем примере вычитается число 9 в формате ASCII из числа 8. После команды
SUB регистр AX получает значение 00FFh (&&1). Команда AAS преобразует AX в FF09h, де&
сятичное дополнение до &&1.
.DATA
val1 DB '8'
val2 DB '9'
.CODE
MOV AH,0
MOV AL,val1 ; AX = 0038h.
sub AL,val2 ; AX = 00FFh.
AAS ; AX = FF09h.
PUSHF ; Сохранить флаг переноса.
OR AL,30h ; AX = FF39h.
POPF ; Восстановить флаг переноса.

Команда AAM
Команда AAM корректирует двоичный результат команды умножения MUL. Умноже&
ние может выполняться с неупакованными десятичными числами, но не может выпол&
няться с числами в формате ASCII, пока старшие четыре бита каждого числа не будут
сброшены. В следующем примере перемножаются числа 5 и 6, а результат корректирует&
ся в регистре AX. После корректировки в AX появится число 0300h, которое является не&
упакованным представлением десятичного числа 30.

224 Глава 8. Арифметика целых чисел

Стр. 224
отсутствует. 225

.DATA
ascVal DB 05h, 06h
.CODE
MOV BL,ascVal ; Первый операнд.
MOV AL,ascVal+l ; Второй операнд.
MUL BL ; AX = 001Eh.
AAM ; AX = 0300h.

Команда AAD
Команда AAD корректирует неупакованное десятичное делимое в регистре AX перед
операцией деления. В следующем примере число 35 в кодировке ASCII делится на 5.
Сначала команда AAD превращает 0307h в 0025h. Затем команда DIV получает частное
07h из AL и остаток 02h из AH.
.DATA
quotient DB ?
remainder DB ?
.CODE
MOV AX,0307h ; Делимое.
AAD ; AX = 0025h.
MOV BL,5 ; Делитель.
DIV BL ; AX = 0207h.
MOV quotient,AL
MOV remainder,AH

Команды DAA и DAS


Упакованные десятичные числа содержат две десятичные цифры в одном байте. Каж&
дое десятичное число определяется 4 битами, и число 2405 записывается так:
packedBCD DD 2405h
Упакованный десятичный формат имеет, по крайней мере, три преимущества.
Число может содержать большое количество значащих цифр, что позволяет вы&
полнять вычисления с большой точностью.
Преобразование упакованных десятичных чисел в формат ASCII (и обратно) про&
изводится довольно быстро.
Десятичная точка может использоваться для указания позиции в отдельных пере&
менных.
Две команды, корректировка после сложения DAA и корректировка после вычитания DAS,
проверяют результат сложения или вычитания упакованных десятичных чисел. К сожа&
лению, таких команд нет для операций умножения и деления. В этих случаях числа
должны быть распакованы перед умножением или делением, а затем упакованы снова.
Команда DAA преобразовывает двоичный результат исполнения команд ADD и ADC в реги&
стре AL в упакованный десятичный формат. Например, суммируются два упакованных числа
35 и 48. Младшая цифра результата (7Dh) больше 9, и она преобразовывается. Старшая
цифра, значение которой после первой корректировки будет равно 8, не преобразовывается.
MOV AL,35h
ADD AL,48h ; AL = 7Dh.
DAA ; AL = 83h (скорректированный результат).

Глава 8. Арифметика целых чисел 225

Стр. 225
Команда DAS преобразовывает двоичный результат команд SUB или SBB в регистре AL
в упакованный десятичный формат. Например, в следующих командах вычитается упа&
кованное десятичное число 48 из 85, а результат корректируется:
MOV BL,48h
MOV AL,85h
SUB AL,BL ; AL = 3Dh.
DAS ; AL = 37h (скорректированный результат).

Резюме
В данной главе рассмотрены команды выполнения операций с целыми числами. Это
наиболее часто используемые в программах команды и поэтому необходимо хорошо
знать их работу и возможные ограничения и ошибки. Начинающие программисты обыч&
но не обращают внимания на величины чисел и во время отладки программа может рабо&
тать отлично. Однако при работе с большими числами может возникнуть переполнение
и расчеты будут выполняться неправильно. В языках высокого уровня такие ошибки
фиксируются компилятором или возникает исключительная ситуация, но в языке ас&
семблера контроль диапазона возможных значений &&&& задача самого программиста.

Контрольные вопросы
1. Предложите возможные пути сложения следующих двух десятичных чисел в фор&
мате ASCII:
2.1234
300.5
2. Какая команда передвигает каждый бит операнда влево и копирует старший бит во
флаг переноса и в позицию младшего бита?
3. Какая команда передвигает каждый бит вправо, копирует младший бит во флаг
переноса, а флаг переноса копирует в позицию старшего бита?
4. Какая команда сдвигает каждый бит вправо и дублирует бит знака?
5. Какая команда передвигает каждый бит влево, копирует флаг переноса в позицию
младшего бита и копирует старший бит во флаг переноса?
6. Что случится с содержимым флага переноса после выполнения команды SHR?
7. Как с помощью команды сдвига можно умножить содержимое регистра AX на 16?
8. Как с помощью команды сдвига можно разделить содержимое регистра BX на 4?
9. Напишите программу с использованием команды сдвига для умножения регистра
AL на 12.
10. Напишите одну команду циклического сдвига, которая меняет местами старшую
и младшую половинки регистра DL.
11. Напишите команду, которая перемещает старший бит регистра DL в младший бит
регистра DH.
12. Какое значение будет в регистре DX после выполнения следующих команд?
MOV DX,5
STC
MOV AX,10h
ADC DX,AX

226 Глава 8. Арифметика целых чисел

Стр. 226
Глава 9

Структуры
и макроопределения
В этой главе...

Структуры
Введение в макроопределение
Дополнительные макроопределения и директивы
Резюме
Контрольные вопросы

Структуры
Директива STRUC определяет структуру, которая может быть отображена в памяти. Струк&
тура может инициализироваться в программе значениями по умолчанию. Отдельные час&
ти структуры называются полями.
Структура должна быть объявлена до того, как она будет использоваться в программе.
Например, можно описать структуру с полями, определяющими отдельного сотрудника.
Следующее описание структуры должно быть помещено в исходный файл перед сегмен&
том данных.
EMPNUMBER_SIZE = 7
LASTNAME_SIZE = 20
typEmployee STRUC
IdNum DB EMPNUMBER_SIZE+1 DUP(0)
Lastname DB LASTNAME_SIZE+1 DUP(0)
Credits DW ?
Status DB ?
typEmployee ENDS
В сегменте данных можно объявить переменную типа ‘‘структура’’.
.DATA
sRec typEmployee <>
Угловые скобки (<>) означают, что ассемблер должен зарезервировать поля в соответ&
ствии с описанием структуры.
При объявлении переменной типа ‘‘структура’’ можно изменить значения полей, уста&
новленных по умолчанию. Новые значения должны разделяться запятой, причем любое

Стр. 227
значение может быть пропущено, но оставлена разделяющая запятая. В следующем при&
мере определяются поля в переменной типа ‘‘структура’’ typEmployee.
sRec typEmployee <"1234","Gennady",32,0> ; Изменить все поля.
myRec typEmployee <,,50,0> ; Изменить последние два поля.
yourRec typEmployee <,"Govorov"> ; Изменить только второе поле.
Как только структура определена, ссылка на отдельные поля может производиться по
именам переменной и поля, разделенных точкой.
MOV DL,sRec.IdNum
MOV AX,sRec.Credits
Ассемблер добавляет к структуре смещение поля от начала структуры для получения
эффективного адреса. В структуре typEmployee смещение поля IdNum равно 0, так как
это первое поле в структуре. Смещение поля LastName равно 8.
Если регистр базы или регистр индекса содержат смещение структуры, то точка раз&
деляет косвенный оператор от имени поля. При использовании ассемблера Microsoft не&
обходимо поставить оператор PTR.
MOV BX,OFFSET sRec
MOV AX,(typEmployee PTR [BX]).Credits
MOV DL,(typEmployee PTR [BX]).Status
Можно использовать и другие модели адресации при ссылке на переменные струк&
турного типа.
MOV AX,sRec[SI].Credits
MOV DL,[BX+SI].Status
Для объявления массива переменных структурного типа можно использовать опера&
тор DUP.
allEmployee typEmployee 100 DUP(<>)
В следующих командах используется регистр ESI для указания на элементы массива
allEmployee. Оператор SIZE возвращает значение 32 &&&& число байт для размещения
одного элемента массива (структура typEmployee).
MOV ESI,OFFSET allEmployee
MOV ECX,Employee_COUNT
L1:CALL InputEmployee
ADD ESI,SIZE typEmployee
LOOP L1

Отображение системного времени


В операционной системе Windows есть функции для установки положения курсора и полу&
чения системного времени. При этом необходимо создать структуры COORD и SYSTEMTIME:
COORD STRUCT
X WORD ?
Y WORD ?
COORD ENDS

SYSTEMTIME STRUCT
wYear WORD ?
wMonth WORD ?
wDayOfWeek WORD ?
wDay WORD ?

228 Глава 9. Структуры и макроопределения

Стр. 228
отсутствует. 229

wHour WORD?
wMinute WORD ?
wSecond WORD ?
wMilliseconds WORD ?
SYSTEMTIME ENDS
Обе структуры описаны в подключаемом файле, листинг которого приведен в при&
ложении.
Для получения системного времени, установленного с учетом временной зоны, вызы&
вается функция GetLocalTime, в которую передается адрес структуры SYSTEMTIME.
.DATA
sysTime SYSTEMTIME <>
.CODE
INVOKE GetLocalTime, ADDR sysTime
Затем извлекаются соответствующие значения из структуры SYSTEMTIME. Например:
MOVZX EAX,sysTime.wYear
CALL WriteDec
Перед тем как произвести вывод данных на экран, вызывается функция Windows
GetStdHandle для получения стандартного дескриптора выхода консоли.
.DATA
consoleHandle DWORD ?
.CODE
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV consoleHandle,EAX
Константа STD_OUTPUT_HANDLE определена в заголовочном файле.
Для установления позиции курсора вызывается функция SetConsoleCursorPosition,
в которую передается дескриптор консоли и переменная типа COORD, содержащая коор&
динаты X, Y в символах.
.DATA
XYPos COORD <10,5>
.CODE
INVOKE SetConsoleCursorPosition, consoleHandle, XYPos
Полностью программа показана в листинге 9.1. Она может быть запущена только в за&
щищенном режиме.

Листинг 9.1. Программа получения времени


TITLE Structures (Time.ASM)
.386
.MODEL flat, stdCALL
INCLUDE E:\MASM615\INCLUDE\Study32.inc
INCLUDE E:\MASM615\INCLUDE\Macros.inc
.DATA
sysTime SYSTEMTIME <>
XYPos COORD <10,5>
consoleHandle DWORD ?
colonStr BYTE ":",0
.CODE
main PROC
; Получить дескриптор стандартного выхода для консоли Win32.
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
MOV consoleHandle,EAX
; Установить положение курсора и получить системное время.

Глава 9. Структуры и макроопределения 229

Стр. 229
INVOKE SetConsoleCursorPosition, consoleHandle, XYPos
INVOKE GetLocalTime, ADDR sysTime
; Отобразить системное время (hh:mm:ss).
MOVZX EAX,sysTime.wHour ; Часы.
CALL WriteDec
MOV EDX,offset colonStr ; ":".
CALL WriteString
MOVZX EAX,sysTime.wMinute ; Минуты
CALL WriteDec
MOV EDX,offset colonStr ; ":".
CALL WriteString
MOVZX EAX,sysTime.wSecond ; Секунды
CALL WriteDec
CALL Crlf
CALL Crlf
CALL WaitMsg ; "Press Enter..."
exit
main ENDP
END main

Введение в макроопределение
Макроопределение &&&& это одно или несколько утверждений языка ассемблера, кото&
рым присвоено символическое имя и которые можно вызывать по этому имени. Однажды
описанное, оно может вызываться сколь угодно много раз в программе. Когда происхо&
дит вызов макроопределения, копия команд, представляющих данное макроопределе&
ние, вставляется непосредственно в программу. И хотя внешне операции перехода по
ссылке и вызов макроопределения похожи, но в техническом плане вызов макроопреде&
ления &&&& это не то же самое, что вызов подпрограммы командой CALL.
Макроопределение выполняется быстрее, чем процедура, имеющая те же самые ко&
манды. Это происходит потому, что для вызова процедуры необходимы дополнительные
команды CALL и RET, что заставляет процессор организовывать переход на другую ветвь
и делать возврат из нее в исходную точку. Это одно из преимуществ применения макрооп&
ределений, но их использование увеличивает объем программного кода, так как каждый
вызов макроопределения вставляет в программу код самого макроопределения.
Например, команда CALL Crlf производит перемещение курсора в начало следую&
щей строки. Эта команда должна размещаться в каждой точке программы, где необходи&
мо сделать вывод на дисплей с новой строки, но лучше создать макроопределение с понят&
ным именем NewLine:
NewLine MACRO
CALL Crlf
ENDM
После того как макроопределение будет описано, его можно вызывать из любого мес&
та программы. При вызове макроопределения все команды, входящие в состав макрооп&
ределения, помещаются в то место программы, где произошел вызов. Например:
.CODE
NewLine
В каждой точке программы, где появляется имя макроопределения, вставляются команды
из тела макроопределения, в данном случае это CALL Crlf. Вставка кодов происходит

230 Глава 9. Структуры и макроопределения

Стр. 230
отсутствует. 231

при первом прохождении ассемблером исходного файла, и они будут присутствовать в лис&
тинге, сгенерированном ассемблером.
Очень важной особенностью макроопределения является возможность включать па&
раметры.
Макроопределение вызывается при размещении его имени в исходном коде програм&
мы со всеми необходимыми аргументами. Синтаксис макроопределения следующий:
имя макроопределения argument_1, argument_2, ...
Каждый аргумент является значением, передаваемым в макроопределение. Это зна&
чение заменяет соответствующие параметры в макроопределении при его вызове. Поря&
док аргументов должен соответствовать порядку параметров, но число аргументов не
обязательно равно числу этих параметров. Если аргументов слишком много, то ассемб&
лер выдаст предупреждение, но если аргументов мало, то оставшиеся параметры будут
пропущены. Директива условия IFB в ассемблере позволяет выполнять проверку пропу&
щенных аргументов.
Макроопределение может быть создано в любом месте программы с помощью дирек&
тив MACRO и ENDM. Синтаксис:
имя макроопределения MACRO parameter_1, parameter_2...
список команд
ENDM
Чтобы как&то выделить макроопределение для удобства чтения программы, в его
имени можно применять определенный префикс. В данном случае в качестве префикса
используется строчная буква ‘‘m’’.
При макроопределении может быть любое число параметров, которое можно размес&
тить на одной строке, разделяя их запятыми.
Для выделения комментариев в макроопределениях два раза ставится точка с запя&
той (;;). При этом комментарии появляются только в написанном макроопределении,
а не в тех фрагментах, которые вставляются в тело программы.
Используя спецификатор REQ, можно задать обязательную установку требуемых па&
раметров. Если макроопределение вызывается без необходимых аргументов, ассемблер
генерирует ошибку. Например:
mPutChar MACRO char:REQ ;; Необходимый параметр.
PUSH EAX
MOV AL, char
CALL WriteChar
ENDM
Если в макроопределении несколько необходимых параметров, каждый параметр
должен включать спецификатор REQ.

Примеры макроопределений
Макроопределение mWriteStr
Создадим макроопределение с именем mWriteStr для записи строки в стандартный
выход. Для данного макроопределения требуется один параметр, который назовем string
и который будет ссылаться на нужную строку.
mWriteStr MACRO string
PUSH EDX
MOV EDX,OFFSET string
CALL WriteString
POP EDX
ENDM

Глава 9. Структуры и макроопределения 231

Стр. 231
Параметр string замещается при каждом вызове макроопределения. Например, для
отображения трех строк макроопределение вызывается три раза, используя каждый раз
различные аргументы.
.DATA
msgl DB "Это сообщение l.",0Dh,0Ah,'$'
msg2 DB "Это сообщение 2.",0Dh,0Ah,'$'
msg3 DB "Это сообщение 3.",0Dh,0Ah,'$'
.CODE
mWriteStr msg1
mWriteStr msg2
mWriteStr msg3
Таким образом, параметры позволяют использовать макроопределение mWriteStr
с наибольшей выгодой. Макроопределения могут вызывать сами себя, как будет показа&
но в дальнейшем. Однако ценой всех этих удобств будет заметное увеличение размера
выполняемого кода при частом вызове макроопределений, так как будут сгенерированы
следующие команды:
; mWriteStr msgl
PUSH EDX
MOV EDX,OFFSET msgl
CALL WriteString
POP EDX
; mWriteStr msg2
PUSH EDX
MOV EDX,OFFSET msg2
CALL WriteString
POP EDX
; mWriteStr msg3
PUSH EDX
MOV EDX,OFFSET msg3
CALL WriteString
POP EDX

Макроопределение mReadStr
Макроопределение mReadStr содержит вызов библиотечной процедуры ReadString.
Она принимает имя массива символов.
mReadStr MACRO varName
PUSH ECX
PUSH EDX
MOV EDX,OFFSET varName
MOV ECX,(SIZEOF varName) - 1
CALL ReadString
POP EDX
POP ECX
ENDM
Ниже показан пример вызова макроопределения mReadStr.
.DATA
firstName BYTE 30 DUP(?)
.CODE
mReadStr firstName

Макроопределение mGotoxy
Это макроопределение размещает курсор на определенной строке и в заданном столбце
экрана. Используя квалификатор REQ, можно указать параметры как необходимые. Если

232 Глава 9. Структуры и макроопределения

Стр. 232
отсутствует. 233

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


то ассемблер генерирует ошибку.
mGotoxy MACRO X:REQ, Y:REQ
PUSH EDX
MOV DH,Y ;; Строка.
MOV DL,X ;; Столбец.
CALL Gotoxy
POP EDX
ENDM
Макроопределение можно вызывать с непосредственными значениями, операндами па&
мяти, значениями регистров, если они представляют 8&разрядное целочисленное значение.
mGotoxy 10,20 ; Непосредственное значение.
mGotoxy row,col ; Операнд памяти.
mGotoxy CH,CL ; Значение регистра.
Необходимо удостовериться, что значения регистров не конфликтуют с регистрами,
используемыми внутри макроопределения. Если при вызове макроопределения mGotoxy
некорректно использовать регистры DH и DL, то макроопределение будет работать непра&
вильно. Рассмотрим код, после которого эти параметры будут неправильными.
1 PUSH EDX
2 MOV DH,DL ;; Строка.
3 MOV DL,DH ;; Столбец.
4 CALL Gotoxy
5 POP EDX
Предположим, что регистру DL необходимо передать значение Y, а регистру DH &&&& зна&
чение X. В строке 2 значение DH заменяется и ему не будет присвоено правильное значение.
При использовании в качестве аргументов регистров необходимо убедиться, что регистры,
которые передаются, не конфликтуют с регистрами, используемыми внутри макроопреде&
ления.

Макроопределение mDumpMem
Рассматривая вызов различных процедур, можно заметить, что иногда это выглядит
довольно неуклюже. Например, для процедуры DumpMem, описанной ранее, требуется
передача адреса в регистре ESI, количество отображаемых модулей в ECX и размер модуля
памяти в EBX. В следующем примере отображается восемь двойных слов, принадлежащих мас&
сиву.
PUSH EBX ; Сохранение регистров.
PUSH ECX
PUSH ESI
MOV ESI,OFFSET array ; Адрес массива.
MOV ECX,8 ; Счетчик.
MOV EBX,TYPE array ; Отобразить двойное слово.
CALL DumpMem
POP ESI ; Восстановить регистры.
POP ECX
POP EBX
Необходимо сохранять в стеке регистры ESI, EBX и ECX (которые могут содержать
важные данные) до того, как использовать команду CALL.
Будет удобнее, если написать макроопределение, в котором вызов процедуры проис&
ходит внутри макроопределения. В теле макроопределения можно сохранить регистры,

Глава 9. Структуры и макроопределения 233

Стр. 233
разместить необходимые аргументы, сделать вызов процедуры и восстановить все значе&
ния. Напишем макроопределение mDumpMem.
mDumpMem MACRO address, ;; Адрес переменной.
itemCount, ;; Количество элементов.
componentSize ;; Размер каждого элемента.
PUSH EBX ;; Сохранение регистров.
PUSH ECX
PUSH ESI
MOV ESI,address ;; Инициализация аргументов.
MOV ECX,itemCount
MOV EBX,componentSize
CALL DumpMem ;; Вызов процедуры.
POP ESI ;; Восстановление регистров.
POP ECX
POP EBX
ENDM
Выход этого макроопределения можно произвести так:
mDumpMem OFFSET array, 8, 4
Альтернативный формат вызова макроопределений позволяет использовать символ
продолжения строки (\) в конце первой и второй строк.
mDumpMem OFFSET array, \ ; Смещение массива.
LENGTHOF array, \ ; Количество модулей.
TYPE array ; Размер каждого модуля.

Макроопределения, содержащие коды и данные


В дополнение к кодам макроопределение может также содержать и данные. Напри&
мер, приведенное ниже макроопределение отображает строку на консоли.
mWrite MACRO text
LOCAL string ;; Локальное имя.
.DATA
string BYTE text,0 ;; Определение строки.
.CODE
PUSH EDX
MOV EDX,OFFSET string
CALL Writestring
POP EDX
ENDM
Обратите внимание на новшества, используемые здесь. Директива LOCAL предписы&
вает препроцессору создавать уникальное имя метки каждый раз, когда макроопределе&
ние mWrite расширяется, чтобы избежать конфликта имен. Например, в следующих ут&
верждениях mWrite вызывается дважды, передавая различные строковые литералы.
mWrite "Введите имя"
mWrite "Введите фамилию"
А так будет выглядеть листинг с рабочим кодом:
; mWrite " Введите имя "
.DATA
??0000 BYTE " Введите имя ",0
.CODE
PUSH EDX
MOV EDX,OFFSET ??0000

234 Глава 9. Структуры и макроопределения

Стр. 234
отсутствует. 235

CALL Writestring
POP EDX
; mWrite " Введите фамилию "
.DATA
??0001 BYTE " Введите фамилию ",0
.CODE
PUSH EDX
MOV EDX,OFFSET ??0001
CALL Writestring
POP EDX
Имена, генерируемые ассемблером, имеют вид ??nnnn, где nnnn &&&& уникальное це&
лое число. Директива LOCAL обязательно должна использоваться для имен внутри кода
макроопределения, тогда макроопределение может вызываться несколько раз.

Вложенные макроопределения
Быстро написать комплексное макроопределение можно с помощью компоновки суще&
ствующих макроопределений. Макроопределение, включенное в другое макроопределение,
называется вложенным. Создадим макроопределение с именем mDisplayRowCol, которое
отображает строки в заданном месте экрана. В нем будут вызываться макроопределения
mGotoRowCol и mDisplayStr, получающие в качестве аргументов значения, которые пе&
редаются макроопределению mDisplayRowCol в соответствии с его параметрами.
mDisplayRowCol MACRO row,col,string
mGotoRowCol row,col ; Вызов mGotoRowCol.
mDisplayStr string ; Отображение строки.
ENDM

Процедуры вызова макроопределений


Ранее уже упоминалось об основном недостатке при использовании макроопределе&
ний. Они увеличивают размер кода, генерируемого ассемблером, так как в программу
вставляются копии макроопределений в местах вызова. Для получения более компакт&
ного кода лучше использовать процедуры. С другой стороны, процедуры очень неудобны
при передаче аргументов с помощью регистров. Хорошим компромиссом будет исполь&
зование макроопределений как ‘‘упаковщиков’’ для вызова процедуры. Макроопределе&
ние может поместить каждый аргумент процедуры в нужном регистре перед вызовом са&
мой процедуры. Эта технология используется для вызова процедуры Writeint, которая
записывает целое число без знака в стандартный выход.
Рассмотрим макроопределение с именем mWriteint (см. листинг ниже), которое вы&
зывает процедуру Writeint. Два параметра, value и radix, загружаются в регистры AX
и BX. Перед вызовом процедуры регистры сохраняются и затем восстанавливаются с це&
лью минимизировать влияние на окружающий код.
mWriteint MACRO value, radix
PUSH AX ;; Сохранение регистров.
PUSH BX
MOV AX,value ;; Значение для отображения.
MOV BX,radix ;; Основание системы счисления.
CALL Writeint
POP BX
POP AX
ENDM

Глава 9. Структуры и макроопределения 235

Стр. 235
Аргументы, передаваемые в макроопределение, могут быть регистрами, переменными
или константами для удобства вызова процедуры.
.DATA
wordval DW 1000h
.CODE
mWriteint 2000h,10 ; Непосредственное десятичное значение.
mWriteint DX,16 ; Шестнадцатеричное значение регистра.
mWriteint wordval,2 ; Двоичный операнд памяти.

Директивы условия ассемблера


Несколько различных директив условия может быть использовано в макроопределе&
ниях для получения более широких возможностей. Общий синтаксис таков:
IF условие
утверждения
[ELSE
утверждения]
ENDIF
В табл. 9.1 перечислены все директивы условия. Когда говорится, что директива разре%
шает трансляцию, то это означает, что будет транслироваться вся последовательность
команд до директивы ENDIF.

Таблица 9.1. Директивы условия


IF выражение Разрешает трансляцию, если значение выражения истинно (не нуль).
Допустимые операторы отношения LT, GT, EQ, NE, LE и GE
IFB <аргумент> Разрешает трансляцию, если аргумент пропущен. Имя параметра
должно быть заключено в угловые скобки (<>)
IFNB <аргумент> Разрешает трансляцию, если аргумент не пропущен. Имя параметра
должно быть заключено в угловые скобки (<>)
IFIDN <арг1>,<арг2> Разрешает трансляцию, если оба аргумента равны. Сравнение можно сделать
чувствительным к регистру, если используется директива IFIDNI
IFDIF <арг1>,<арг2> Разрешает трансляцию, если аргументы не равны. Сравнение можно сделать
чувствительным к регистру, если используется директива IFIDNI
IFDEF имя Разрешает трансляцию, если имя определено
IFNDEF имя Разрешает трансляцию, если имя не определено
ENDIF Конец блока, который начинался с директивы условия
ELSE Транслировать все команды до ENDIF, если условие предыдущей
директивы IF ложно
EXITM Немедленный выход из макроопределения

Проверка пропущенного аргумента


Макроопределение может получать один или несколько аргументов, поэтому необхо&
димо проверять, все ли аргументы присутствуют. Если пропущен аргумент, который дей&
ствительно используется в макроопределении, то появится неправильная команда.
Директива IFB возвращает значение истина, если пропущен аргумент, а директива IFNB
возвращает значение истина, если нет пропущенных аргументов. Например, в следующем

236 Глава 9. Структуры и макроопределения

Стр. 236
отсутствует. 237

макроопределении с именем mymac проверяется значение для параметра parm1. Если mymac
вызывается без аргументов, директива EXITM прекратит генерацию кодов.
mymac MACRO parm1
IFB <parm1> ; Аргумент пропущен?
EXITM ; Выходим из макроопределения
ENDIF ; и не генерируем код.
...
.CODE
mymac ; Код не сгенерирован.
mymac val1 ; Код получен.

Аргументы по умолчанию
Другой путь для решения проблемы с пропуском параметров заключается в установке
значений по умолчанию для параметров.
Например, макроопределение mWriteLn может использовать строку, содержащую
один пробел как аргумент по умолчанию. Если произвести вызов без аргументов, то будет
напечатан пробел с концом строки.
mWriteLn MACRO text:=<" ">
mWrite text
CALL Crlf
ENDM
Если в качестве аргумента по умолчанию использовать пустую строку (“”), то ассемблер
зафиксирует ошибку.
Директивы условия, такие как IF, должны включать выражение, которое может возвратить
значения истина или ложь во время трансляции. Нельзя использовать значения в регист&
рах или памяти, так как они могут быть определены только во время работы программы.

Булевы выражения

В ассемблере разрешены следующие операторы отношений, которые могут использо&


ваться в булевых выражениях.
LT &&&& Меньше.
GT &&&& Больше.
EQ &&&& Равно.
NE &&&& Не равно.
LE &&&& Меньше или равно.
GE &&&& Больше или равно.

Директивы IF, ELSE и ENDIF


Директива IF должна использоваться с булевым выражением. Выражение может со&
держать целочисленные константы, символические константы или константы аргумен&
тов макроопределения, но не может содержать регистры или имена переменных. Синтак&
сис для директив IF и ENDIF следующий:
IF выражение
утверждения
ENDIF

Глава 9. Структуры и макроопределения 237

Стр. 237
Можно использовать такой формат для IF, ELSE и ENDIF:
IF выражение
утверждения
ELSE
утверждения
ENDIF
В качестве примера рассмотрим макроопределение mGotoxyConst (листинг 9.2), в ко&
тором использованы операторы LT и GT для проверки допустимого диапазона аргументов.
Аргументы X и Y должны быть константами. Еще одна константа с символическим именем
ERRS содержит количество найденных ошибок. Если ошибочен аргумент X, то значение ERRS
становится равным 1, если ошибочен аргумент Y, то к значению ERRS добавляется 1. На&
конец, если ERRS больше, чем 1, то происходит выход из макроопределения.

Листинг 9.2. Макроопределение mGotoxyConst


mGotoxyConst MACRO X:REQ, Y:REQ
; Установить курсор в заданное место.
LOCAL ERRS ;; Локальная константа.
ERRS = 0
IF (X LT 0) OR (X GT 79)
ECHO Warning: Первый аргумент выходит за диапазон.
ERRS = 1
ENDIF
IF (Y LT 0) OR (Y GT 24)
ECHO Warning: Второй аргумент выходит за диапазон.
ERRS = ERRS + 1
ENDIF
IF ERRS GT 0 ;; Если есть ошибка,
EXITM ;; выход из макроопределения.
ENDIF
PUSH EDX
MOV DH,Y
MOV DL,X
CALL Gotoxy
POP EDX
ENDM

Директивы IFIDN и IFIDNI


Директива IFIDNI сравнивает два идентификатора с учетом регистра, включая имена
параметров макроопределения. Директива IFIDN выполняет проверку с учетом регистра.
Эта директива особенно полезна, когда необходимо производить проверку на возмож&
ность конфликта регистров в вызываемом макроопределении и вызывающей программе.
Синтаксис директив следующий:
IFIDNI <идентификатор>,<идентификатор>
statements
ENDIF

IFIDN <идентификатор>,<идентификатор>
statements
ENDIF

238 Глава 9. Структуры и макроопределения

Стр. 238
отсутствует. 239

Например, в макроопределении mReadBuf вторым аргументом не может быть регистр


EDX, поскольку он будет переписан, когда в него будет переписываться смещение буфера.
В листинге 9.3 макроопределение отображает предупреждающее сообщение, если не бу&
дут соблюдаться необходимые условия.

Листинг 9.3. Отображение предупреждающих сообщений


mReadBuf MACRO bufferPtr, maxChars
; Считывание из стандартного входа в буфер.
; Второй аргумент не может быть EDX/EDX
IFIDNI <maxChars>,<EDX>
ECHO Внимание: Второй аргумент mReadBuf не может быть EDX
EXITM
ENDIF
push ECX
push EDX
MOV EDX,bufferPtr
MOV ecx,maxChars
CALL ReadString
POP EDX
POP ECX
ENDM

Следующее утверждение приведет к генерации предупреждающего сообщения:


mReadBuf OFFSET buffer,EDX

Операторы для макроопределений


Существует пять операторов для макроопределений, что позволяет более гибко исполь&
зовать макроопределения. Необходимо отметить, что выделение комментария (;;) не яв&
ляется оператором в обычном понимании, он служит только для выдачи на печать текста ком&
ментария при использовании макроопределений (табл. 9.2).

Таблица 9.2. Операторы для макроопределений


& Оператор замены
<> Оператор выделения текста
! Оператор выделения символа
% Оператор выражения
;; Комментарий макроопределения

Оператор замены
Оператор замены (&) разрешает заменять параметр значением, которое передается как
аргумент. Синтаксис оператора замены следующий:
&параметр
Этот оператор особенно полезен, когда в качестве аргумента используется текстовое
сообщение и этот аргумент передается строке или команде. С помощью оператора заме&
ны можно разрешать неоднозначные ссылки на имена параметров в макроопределении.
Например, макроопределение ShowRegister отображает имя и шестнадцатеричное со&
держимое 32&разрядного регистра.
.CODE
ShowRegister ECX

Глава 9. Структуры и макроопределения 239

Стр. 239
После выполнения этого кода будет выведена следующая строка:
ECX=00000101
Строковая переменная, содержащая имя регистра, может быть определена внутри
макроопределения.
ShowRegister MACRO regName
.DATA
tempStr BYTE " regName=",0
Однако препроцессор предположит, что regName &&&& часть строкового литерала, и не бу&
дет заменять его значением аргумента, передаваемого в макроопределение. Вместо этого
он добавит оператор & для вставки аргумента макроопределения, подобного ECX, в стро&
ковый литерал:
ShowRegister MACRO regName
.DATA
tempStr BYTE " &regName=",0
В следующем листинге содержится макроопределение ShowRegister, в котором ис&
пользуется процедура DumpRegs.
.DATA
tempStr BYTE " &regName=",0
.CODE
PUSH EAX
PUSH EDX
; Отобразить имя регистра.
MOV EDX,offset tempStr
CALL WriteString
; Отобразить содержимое регистра.
MOV EAX,regName
CALL WriteHex
POP EDX
POP EAX
ENDM

Оператор выражения
Оператор выражения (%) позволяет рассчитать результат выражения и передать его
как аргумент в макроопределение или преобразовать константное выражение в его тек&
стовое представление. Оператор указывает ассемблеру на то, что необходимо передать
результат расчета выражения, а не само выражение. При совместном использовании с ди&
рективой TEXTEQU выполняется вычисление выражения и преобразование его в целочис&
ленное значение.
В следующем примере вычисляется выражение (5+count) и возвращается значение
15 как символьный текст.
count = 10
sumVal TEXTEQU %(5 + count) ; = "15"
Если в макроопределении требуется целочисленный аргумент, оператор % предостав&
ляет различные возможности передачи целых чисел. Вычисляется выражение, после чего
целочисленный результат передается в макроопределение. Например, при вызове макро&
определения mGotoxyConst будут рассчитаны выражения:
mGotoxyConst %(5 * 10), %(3 + 4)

240 Глава 9. Структуры и макроопределения

Стр. 240
отсутствует. 241

Для чего препроцессор выполнит следующие команды:


PUSH EDX
MOV dh,7
MOV dl,50
CALL Gotoxy
POP EDX
Когда оператор выражения (%) &&&& первый символ в строке, то препроцессор должен
развернуть все тексты макроопределения и найти подробную строку. Предположим, на&
пример, что необходимо отобразить размер массива на экране во время ассемблирова&
ния. В приведенном ниже случае необходимого результата не получится.
.DATA
array DWORD 1,2,3,4,5,6,7,8
.CODE
ECHO Массив содержит (SIZEOF array) байт
ECHO Массив содержит %(SIZEOF array) байта
На экране появится следующее сообщение:
Массив содержит (SIZEOF array) байта
Массив содержит %(SIZEOF array) байта
Если же использовать TEXTEQU для создания макроопределения, то можно получить
требуемый результат.
TempStr TEXTEQU %(SIZEOF array)
% ECHO Массив содержит TempStr байта
И будет получено следующее сообщение:
Массив содержит 32 байта
Рассмотрим макроопределение Mul32, которое перемножает два первых аргумента
и возвращает результат в третьем аргументе. Макроопределение может работать с регист&
рами, операндами памяти и даже с непосредственными операндами.
MUL32 MACRO opl, op2, product
IFIDNI <op2>,<EAX>
LINENUM TEXTEQU %(@LINE)
% ECHO * Ошибка в строке LINENUM: регистр EAX не может быть вторым
ECHO * аргументом при вызове макроопределения MUL32.
EXITM
ENDIF
PUSH EAX
MOV EAX,opl
MUL op2
MOV product,EAX
POP EAX
ENDM
В макроопределении проверяется то, что регистр EAX не является вторым аргумен&
том. Для быстрого нахождения и исправления ошибки отображается номер линии, в ко&
торой было вызвано макроопределение.
Сначала вызывается макроопределение LINENUM, которое использует ссылку @LINE &&&&
предопределенный оператор ассемблера, возвращающий номер строки исходного кода:
LINENUM TEXTEQU %(@LINE)
Затем оператор выражения (%) в первом столбце строки, содержащей утверждение ECHO,
приводит к замене LINENUM.
% ECHO * Ошибка в строке LINENUM: регистр EAX не может быть вторым

Глава 9. Структуры и макроопределения 241

Стр. 241
Например, если макроопределение будет вызвано в строке 40
MUL32 val1,EAX,val3,
то будет отображено следующее сообщение:
Ошибка в строке 40: регистр EAX не может быть вторым
аргументом при вызове макроопределения MUL32.

Оператор выделения текста


Оператор выделения текста (<>) объединяет вместе строки символов. Это предотвра&
щает интерпретацию ассемблером списка как отдельных аргументов. Оператор выделе&
ний текста необходим в директивах условия, таких как IFB и IFIDN, он также использу&
ется, когда строка включает специальные символы (",", "%", "&" и ";"), которые сами
по себе могут быть интерпретированы как макрооператоры. В следующем примере напи&
сано макроопределение с именем mMessage.
mMessage MACRO text
DB "&text",0
ENDM
Например, макроопределение можно вызвать так:
mMessage <Efficiency is 50%, &falling;>
и ассемблер запишет:
DB "Efficiency is 50%, &falling;",0

Оператор выделения символа


Оператор выделения символа (!) используется для тех же целей, что и оператор выде&
ления текста. Он заставляет ассемблер интерпретировать отдельный символ только как
символ, а не как оператор. Используя опять макроопределение mMessage в качестве при&
мера, можно с помощью оператора выделения символа отобразить знак ‘‘>’’ как символ.
mMessage <Efficiency is !> 50 percent>
Ассемблер запишет строку:
DB "Efficiency is > 50 percent"
В следующем примере показано совместное использование операторов %, & и !. Пред&
положим, что определен идентификатор BadYValue. Создадим макроопределение
с именем ShowWarning, которое принимает текст в качестве аргумента, заключает его
в кавычки и передает в макроопределение mWrite. Сначала обратите внимание на ис&
пользование оператора &:
ShowWarning MACRO message
mWrite "&message"
ENDM
Затем вызывается процедура ShowWarning, которой передается выражение %BadYValue.
Оператор % выделяет BadYValue и создает эквивалентную строку.
.CODE
ShowWarning %BadYValue
При запуске программы отображается предупреждающее сообщение:
Warning: Y-coordinate is > 24

242 Глава 9. Структуры и макроопределения

Стр. 242
отсутствует. 243

Дополнительные макроопределения и директивы


Директива REPT
Одну или несколько команд можно повторить с использованием директив REPT, IRP
и IRPC. Это позволяет с помощью простого макроопределения создать большую структуру
данных. Директива REPT повторяет блок команд в соответствии со счетчиком. Синтак&
сис этой директивы следующий:
REPT выражение
блок команд
ENDM
Выражение определяет количество повторений и задается 16&разрядным числом без
знака. Допустим, необходимо использовать REPT для распределения таблицы, вклю&
чающей 100 записей о сотрудниках.
index LABEL byte ; Начало таблицы.
REPT 100 ; Начало цикла REPT.
DB 7 DUP(?) ; Номера сотрудников.
DB 20 DUP(' ') ; Фамилия.
DW ? ; Соответствие.
ENDM
Эту технику можно использовать для создания макроопределения, которое сдвигает
операнд влево на заданное число байт. В следующем примере счетчик count определяет
количество команд SHL, сгенерированных ассемблером:
mSHL MACRO dest,count
REPT count
SHL dest,1
ENDM
ENDM
Макроопределение можно вызвать так:
mSHL AX,1
mSHL BX,4
При этом сгенерированный код будет следующий:
SHL AX,1
SHL BX,1
SHL BX,1
SHL BX,1
SHL BX,1

Директива IRP
Директива IRP создает повторяющийся блок, где каждое повторение включает разные
значения. Синтаксис этой директивы таков:
IRP аргумент,< аргумент[, аргумент] ...>
команды
ENDM
Блок повторяется только для каждого аргумента. При каждом повторении очеред&
ной аргумент передается в качестве параметра. Эта директива полезна при инициали&
зации таблиц или блоков данных, где содержатся различные значения. Аргумент может
быть символическим именем, строкой или числовой константой. Используя следующее

Глава 9. Структуры и макроопределения 243

Стр. 243
написание макроопределения, проанализируем распределение данных, сгенерированных
ассемблером.
IRP parm,<10,20,30,40>
DW parm, parm * 2, parm * 3, parm * 4
ENDM
Сгенерировано.
DW 10, 10 * 2, 10 * 3, 10 * 4
DW 20, 20 * 2, 20 * 3, 20 * 4
DW 30, 30 * 2, 30 * 3, 30 * 4
DW 40, 40 * 2, 40 * 3, 40 * 4
С помощью директивы IRP можно создать таблицу смещений процедур. Это хороший
стиль программирования для выбора из нескольких ветвей по индексу.
MOV BX,indexvalue ; Выбор раздела таблицы.
CALL proctable[BX] ; Косвенный вызов.
В следующем фрагменте с использованием директивы IRP в качестве аргументов пере&
даются четыре имени процедур. Каждое имя последовательно передается в параметр
procname, в результате чего получается таблица смещений процедур.
proctable LABEL word
IRP procname,<MOVup,MOVdn,MOVlft,MOVrt>
DW procname
ENDM
Ассемблер сгенерирует следующие команды.
proctable LABEL word
DW MOVup
DW MOVdn
DW MOVlft
DW MOVrt

Совмещение сдвигов и повторений


Ранее использовалась директива REPT для создания макроопределения mSHL, которое
сдвигало операнд влево на заданное число бит. Для того чтобы такие же возможности
создать для всех других команд сдвига и циклического сдвига, необходимо написать во&
семь различных макроопределений. Однако есть гораздо лучший путь: можно скомбини&
ровать директиву IRP с написанием макроопределения, чтобы получить различные вари&
анты. Следующее макроопределение генерирует восемь различных макроопределений
с именами mSHL, mSHR, mSAL, mSAR, mROL, mROR, mRCL и mRCR.
IRP styp,<SHL,SHR,SAL,SAR,ROL,ROR,RCL,RCR>
m&styp MACRO dest,count
PUSH EX
MOV CL,count
styp dest,CL
POP CX
ENDM
ENDM
Параметр dest может быть 8&, 16& или 32&разрядным регистром или переменной,
кроме регистров CL, CX или ECX. Параметр count может быть непосредственным значе&
нием, регистром или операндом памяти размером 8 бит.

244 Глава 9. Структуры и макроопределения

Стр. 244
отсутствует. 245

Оператор замены (&) позволяет заменять строку аргументов при вызове макроопреде&
ления. В приведенном примере директива IRP произведет вызов макроопределения во&
семь раз, используя различные значения переменной styp при каждом вызове.

Директива IRPC
Директива IRPC подобна директиве IRP, за исключением того, что число символов в
аргументе строка определяет число повторений. Синтаксис этой директивы следующий:
IRPC параметр,строка
команды
ENDM
Строка может быть заключена в угловые скобки (<>), если она содержит пробелы, за&
пятые или другие специальные символы. В следующем примере создается пять перемен&
ных (value_A, value_B и т.д.), в качестве аргумента используется строка ABCDE.
IRPC parm,ABCDE
value_&parm DB '&parm'
ENDM
Сгенерированные команды:
value_A DB 'A'
value_B DB 'B'
value_C DB 'C'
value_D DB 'D'
value_E DB 'E'
Макроопределения предоставляют огромные возможности для расширения системы
команд &&&& вы ограничены только пределами вашей фантазии. Когда соберется достаточ&
но большая коллекция макроопределений, приходится каждый раз копировать их в соз&
даваемую программу. Вместо этого создайте отдельный файл с макроопределениями
и используйте директиву INCLUDE для подключения его при ассемблировании. Теперь
необходимые макроопределения станут частью окончательной программы. Если исполь&
зуется двухпроходной ассемблер, такой как Microsoft Assembler (MASM), хорошей идеей
будет ограничить INCLUDE директивами IF1 и ENDIF, которые заставят ассемблер вклю&
чать макроопределение только при первом проходе.
IF1 ; Если это первый проход,
include macros.inc ; подключить файл макроопределений.
ENDIF
Если не указан полный путь подключаемого файла, ассемблер будет искать его в те&
кущем каталоге. Можно дополнительно указать полный или относительный путь таким
образом:
include ..\..\library\macros.INC

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

Глава 9. Структуры и макроопределения 245

Стр. 245
повторяющиеся фрагменты, которые можно оформить в виде структур, процедур или мак&
роопределений, что в конечном итоге сделает вашу программу хорошо понимаемой
не только для вас.

Контрольные вопросы
1. Почему удобно использовать структуры? Напишите пример структуры myStruct
с двумя полями.
2. Напишите макроопределение с именем mPUSHData, которое помещает регистры
EAX, EBX, ECX и EDX в стек, и второе макроопределение, которое извлекает те же
самые регистры из стека.
3. В чем заключается преимущество использования макроопределений от вызова
процедур?
4. Напишите пример макроопределения с использованием данных.
5. Каково назначение директивы IFB?
6. Каково назначение директивы IFIDN?
7. Какая директива прерывает выполнение кода макроопределения?
8. Чем директива IFIDNI отличается от директивы IFIDN?
9. Каково назначение директивы IFDEF?
10. Какая директива отмечает конец блока условия?
11. Покажите пример параметра макроопределения, инициализированного по умол&
чанию.
12. Каково назначение оператора &?
13. Каково назначение оператора !?

246 Глава 9. Структуры и макроопределения

Стр. 246
Глава 10

Написание графических
приложений Windows
В этой главе...

Структуры для окон


Функция MessageBox
Процедура WinMain
Процедура WinProc
Процедура ErrorHandler
Листинг программы
Совместное использование языков высокого уровня и ассемблера
Резюме
Контрольные вопросы

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


Windows и написании графических приложений. В созданном программным способом окне
можно будет отображать сообщения и реагировать на события мыши. Краткие сведения,
приведенные в главе, могут послужить только основой для понимания принципов построе&
ния окон Windows и ни в коей мере не рассчитаны на полное описание всех возможностей
создания графических приложений. Однако усвоив основы построения графических при&
ложений, вы сможете понять и более развитые средства разработки приложений.
При разработке программ для Windows необходимо использовать следующие библио&
течные и подключаемые файлы:
kernel32.1ib &&&& библиотека функций Windows API;
user32.lib &&&& дополнительные функции Windows API;
подключаемый файл с объявлением структур, прототипов функций и констант
Windows API.
Командный файл для компиляции и компоновки программы должен быть следующим:
ML -c -coff %l.asm
LINK %l.obj kernel32.1ib user32.lib /SUBSYSTEM:WINDOWS

Стр. 247
Обратите внимание, что директива /SUBSYSTEM:WINDOWS поставлена вместо исполь&
зуемой ранее /SUBSYSTEM:CONSOLE. В программе будут функционировать стандартные
библиотеки Windows kernel32.lib и user32.lib.

Структуры для окон


Структура POINT определяет координаты X и Y точки экрана, измеренные в пикселях. Ее
можно использовать при работе с графическими объектами, окнами и при щелчках мыши.
POINT STRUCT
ptX DWORD ?
ptY DWORD ?
POINT ENDS
Структура RECT определяет границы прямоугольника. Член left содержит коор&
динату X левой стороны прямоугольника. Член top содержит координату Y верхней
грани прямоугольника. Соответствующие значения сохраняются в членах right (правый)
и bottom (нижний).
RECT STRUCT
left DWORD ?
top DWORD ?
right DWORD ?
bottom DWORD ?
RECT ENDS
Структура MSGStruct определяет данные, необходимые при передаче сообщения
Windows.
MSGStruct STRUCT
msgWnd DWORD ?
msgMessage DWORD ?
msgWparam DWORD ?
msgLparam DWORD ?
msgTime DWORD ?
msgPt POINT <>
MSGStruct ENDS
Структура WNDCLASS определяет класс окна. Каждое окно в программе должно при&
надлежать определенному классу и в каждой программе должен быть указан класс основ&
ного окна. Этот класс должен быть зарегистрирован в операционной системе перед тем,
как произойдет обращение к основному окну.
WNDCLASS STRUC
style DWORD ? ; Опции стиля окна.
IpfnWndProc DWORD ? ; Указатель на функцию WinProc.
cbClsExtra DWORD ? ; Совместно используемая память.
cbWndExtra DWORD ? ; Количество дополнительных байт.
hlnstance DWORD ? ; Дескриптор текущей программы.
hlcon DWORD ? ; Дескриптор пиктограммы.
hCursor DWORD ? ; Дескриптор курсора.
hbrBackground DWORD ? ; Дескриптор цвета фона.
IpszMenuName DWORD ? ; Указатель на имя меню.
IpszClassName DWORD ? ; Указатель на имя WinClass.
WNDCLASS ENDS
Рассмотрим эти параметры более подробно.

248 Глава 10. Написание графических приложений Windows

Стр. 248
отсутствует. 249

style &&&& комбинации различных опций стиля, таких как WS_CAPTION и WS_BORDER,
которые определяют поведение окна и его видимость.
IpfnWndProc &&&& указывает на функцию в программе, которая принимает и обра&
батывает сообщения о событиях, инициируемых пользователем.
cbClsExtra &&&& ссылается на память, совместно используемую всеми окнами,
принадлежащими данному классу. Может иметь нулевое значение.
cbWndExtra &&&& определяет количество дополнительных байт для размещения
следующего экземпляра окна.
hlnstance &&&& содержит дескриптор текущего экземпляра программы.
hlcon &&&& содержит дескриптор для ресурсов пиктограммы текущей программы.
hCursor &&&& содержит дескриптор для ресурсов курсора текущей программы.
hbrBackground &&&& содержит цвет фона.
IpszMenuName &&&& указывает на строку меню.
IpszClassName &&&& указывает на строку с нулевым окончанием, содержащую имя
класса окна.

Функция MessageBox
Простейшим способом отобразить текст в программе будет использование окна со&
общений, в котором указываются необходимые данные и которое высвечивается на
экране и сохраняется до тех пор, пока пользователь не щелкнет на кнопке. Функция
MessageBox из библиотеки Win32 API отображает простое окно сообщений, прототип
которого показан ниже.
MessageBox PROTO,
hWnd:DWORD, pText:PTR BYTE, pCaption:PTR BYTE, style:DWORD
Параметр hWnd определяет дескриптор текущего окна. Параметр pText указывает на
строку с нулевым окончанием, которая содержит необходимый текст для отображения. Па&
раметр pCaption указывает на строку с нулевым окончанием, которая появляется в заго&
ловке окна. Параметр style &&&& целочисленное значение, с помощью которого указываются
пиктограмма окна сообщений (дополнительно) и кнопки (обязательно). Кнопки определяют&
ся такими константами, как MB_OK и MB_YESNO. Пиктограммы определяются константами,
подобными MBJCONQUESTION. При отображении окна сообщений можно использовать со&
вместно константы как для пиктограмм, так и для кнопок, как показано в примере ниже.
INVOKE MessageBox, hWnd, ADDR QuestionText,
ADDR QuestionTitle, MB_OK + MB_ICONQUESTION

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

Глава 10. Написание графических приложений Windows 249

Стр. 249
создает основное окно;
отображает и обновляет основное окно;
начинает циклический процесс приема и диспетчеризации сообщений.

Процедура WinProc
Процедура WinProc принимает и обрабатывает все сообщения о событиях, относя&
щихся к окну. Большинство событий создает пользователь, щелкая мышкой и перетас&
кивая объекты, нажимая клавиши клавиатуры и т.д. Процедура декодирует каждое сооб&
щение о событии, и если сообщение распознано, то обращается к соответствующим
функциям для выполнения задач, связанных с окном. Прототип функции следующий:
WinProc PROC,
hWnd:DWORD, ; Дескриптор окна.
localMsg:DWORD, ; Идентификатор (ID) сообщения.
wParam:DWORD, ; Параметр 1 (изменяется).
lParam:DWORD ; Параметр 2 (изменяется).
Содержимое третьего и четвертого параметров будет отличаться в зависимости от со&
общения. Например, при щелчке мыши lParam содержит координаты X и Y точки, в ко&
торой произошел щелчок.
В демонстрационной программе, приведенной ниже, процедура WinProc обрабатыва&
ет три сообщения о событиях, которые создаются:
WM_LBUTTONDOWN &&&& при нажатии левой кнопки мыши;
WM_CREATE &&&& при появлении основного окна;
WM_CLOSE &&&& во время закрытия основного окна.
Например, следующие строки обрабатывают сообщение WM_LBUTTONDOWN, при этом
вызывается окно сообщений для отображения сообщения.
.IF EAX == WM_LBUTTONDOWN
INVOKE MessageBox, hWnd, ADDR PopupText,
ADDR PopupTitle, MB_OK
JMP WinProcExit
В результате нажатия левой кнопки мыши на экране появится следующее окно сооб&
щений (рис. 10.1).

Рис. 10.1. Окно сообщений появляется после нажатия


левой клавиши мыши

Все сообщения, для которых не существует соответствующего обработчика события,


передаются в процедуру DefWindowProc, которая является обработчиком событий по
умолчанию для Windows.

250 Глава 10. Написание графических приложений Windows

Стр. 250
отсутствует. 251

Процедура ErrorHandler
Процедура ErrorHandler будет вызываться, если возникнет сбой при регистрации
и создании основного окна программы. Например, функция RegisterClass должна
возвратить ненулевое значение при успешном создании и регистрации окна. Однако если
будет возвращен нуль, то при этом вызывается процедура ErrorHandler для отображе&
ния сообщения и выхода из программы.
INVOKE RegisterClass, ADDR MainWin
.IF EAX == 0
CALL ErrorHandler
JMP Exit_Program
.ENDIF
Процедура ErrorHandler выполняет несколько важных задач:
вызывает процедуру GetLastError для извлечения номера системной ошибки;
вызывает процедуру FormatMessage и извлекает подходящую строку форматиро&
вания для сообщения об ошибке;
вызывает процедуру MessageBox для получения и отображения окна сообщения,
содержащего необходимое сообщение об ошибке;
вызывает процедуру LocalFree для освобождения памяти, занимаемой строкой
сообщения об ошибке.

Листинг программы
Пусть вас не смущает размер листинга 10.1 &&&& большинство фрагментов данной про&
граммы используются в любом приложении для Windows.

Листинг 10.1. Демонстрационная программа


TITLE Графические примитивы Windows (WinApp.asm)
; Эта программа отображает окно с изменяемыми размерами и
; несколько окон сообщений.
.386
.model flat,STDCALL
INCLUDE E:\MASM615\INCLUDE\gWin.inc
.data
AppLoadMsgTitle BYTE "Приложение загружено.",0
AppLoadMsgText BYTE "Это окно отображается, когда получено "
BYTE "сообщение WM_CREATE.",0
PopupTitle BYTE "Всплывающее окно",0
PopupText BYTE "Это окно активизируется после приема "
BYTE "сообщения WM_LBUTTONDOWN.",0
GreetTitle BYTE "Основное окно активно.",0
GreetText BYTE "Это окно отображается немедленно после "
BYTE "вызова CreateWindow или UpdateWindow.",0
CloseMsg BYTE "Сообщение WM_CLOSE получено.",0
ErrorTitle BYTE "Ошибка",0
WindowName BYTE "Приложение на ассемблере для Windows",0
className BYTE "ASMWin",0
; Определение структуры класса окна приложения.
MainWin WNDCLASS <NULL,WinProc,NULL,NULL,NULL,NULL,NULL, \
COLOR_WINDOW,NULL,className>

Глава 10. Написание графических приложений Windows 251

Стр. 251
msg MSGStruct <>
winRect RECT <>
hMainWnd DWORD ?
hInstance DWORD ?
.code
WinMain PROC
; Получить дескриптор текущего процесса.
INVOKE GetModuleHandle, NULL
mov hInstance, eax
mov MainWin.hInstance, eax
; Загрузить пиктограммы и курсоры.
INVOKE LoadIcon, NULL, IDI_APPLICATION
mov MainWin.hIcon, eax
INVOKE LoadCursor,NULL,IDC_ARROW
mov MainWin.hCursor, eax
; Зарегистрировать класс окна.
INVOKE RegisterClass, ADDR MainWin
.IF eax == 0
call ErrorHandler
jmp Exit_Program
.ENDIF
; Создать основное окно приложения.
INVOKE CreateWindowEx,0,ADDR className,
ADDR WindowName,MAIN_WINDOW_STYLE,
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,NULL,NULL,hInstance,NULL
; Если окно не создано, то отобразить сообщение об ошибке и выйти.
.IF eax == 0
call ErrorHandler
jmp Exit_Program
.ENDIF
; Сохранить дескриптор окна, показать и отобразить окно.
mov hMainWnd,eax
INVOKE ShowWindow,hMainWnd, SW_SHOW
INVOKE UpdateWindow, hMainWnd
; Отобразить сообщение об ошибке.
INVOKE MessageBox,hMainWnd,ADDR GreetText,ADDR GreetTitle,MB_OK
; Начать циклическую обработку сообщений.
Message_Loop:
; Получить следующее сообщение из очереди.
INVOKE GetMessage, ADDR msg, NULL,NULL,NULL
; Выйти, если нет сообщений.
.IF eax == 0
jmp Exit_Program
.ENDIF
; Передать сообщение в программу WinProc.
INVOKE DispatchMessage, ADDR msg
jmp Message_Loop
Exit_Program:
INVOKE ExitProcess,0
WinMain ENDP

; В предыдущем цикле структура msg передается в функцию GetMessage.


; Структура заполняется и затем передается в функцию
; DispatchMessage Windows.

WinProc PROC,
hWnd:DWORD, localMsg:DWORD, wParam:DWORD, lParam:DWORD

252 Глава 10. Написание графических приложений Windows

Стр. 252
отсутствует. 253

; Обработчик сообщений приложения, который обрабатывает


; специфические сообщения приложения. Все другие сообщения
; перенаправляются в обработчик сообщений по умолчанию.
mov eax, localMsg
.IF eax == WM_LBUTTONDOWN ; Кнопка мыши?
INVOKE MessageBox,hWnd,ADDR PopupText,ADDR PopupTitle,MB_OK
jmp WinProcExit
.ELSEIF eax == WM_CREATE ; Создать окно?
INVOKE MessageBox,hWnd,ADDR AppLoadMsgText,
ADDR AppLoadMsgTitle,MB_OK
jmp WinProcExit
.ELSEIF eax == WM_CLOSE ; Закрыть окно?
INVOKE MessageBox,hWnd,ADDR CloseMsg,ADDR WindowName,MB_OK
INVOKE PostQuitMessage,0
jmp WinProcExit
.ELSE ; other message?
INVOKE DefWindowProc,hWnd,localMsg,wParam,lparam
jmp WinProcExit
.ENDIF
WinProcExit:
ret
WinProc ENDP

ErrorHandler PROC
; Отобразить нужное сообщение об ошибке.
.data
pErrorMsg DWORD ? ; Указатель на сообщение об ошибке.
messageID DWORD ?
.code
INVOKE GetLastError ; Возвратить идентификатор сообщения в EAX
mov messageID, eax
; Получить строку сообщения.
INVOKE FormatMessage, FORMAT_MESSAGE_ALLOCATE_BUFFER + \
FORMAT_MESSAGE_FROM_SYSTEM,NULL,messageID,NULL,
ADDR pErrorMsg,NULL,NULL
; Отобразить сообщение об ошибке.
INVOKE MessageBox,NULL, pErrorMsg, ADDR ErrorTitle,
MB_ICONERROR+MB_OK
; Освободить память.
INVOKE LocalFree, pErrorMsg
ret
ErrorHandler ENDP
END WinMain

Запуск программы
Когда программа запускается, то появляется следующее окно сообщений (рис. 10.2).

Рис. 10.2. Окно сообщений, появляющееся


после запуска программы

Глава 10. Написание графических приложений Windows 253

Стр. 253
При щелчке пользователя на кнопке OK первое окно сообщений закрывается и ото&
бражается окно сообщений, представленное на рис. 10.3.

Рис. 10.3. Окно сообщений, появляющееся после за%


крытия первого окна

После щелчка пользователя на кнопке OK закрывается и это окно и отображается ос&


новное окно приложения Windows (рис. 10.4).

Рис. 10.4. Основное окно программы

Если щелкнуть левой кнопкой мыши на поле окна, то появится окно сообщений, ко&
торое показано на рис. 10.5.
При закрытии основного окна Windows появляется следующее окно сообщений
(рис. 10.6).
После щелчка пользователя на кнопке OK программа для этого окна завершается.

Рис. 10.5. Окно сообщений, появляющееся по% Рис. 10.6. Окно сообщений, кото%
сле щелчка мыши на поле основного окна рое появляется при закрытии
основного окна

254 Глава 10. Написание графических приложений Windows

Стр. 254
отсутствует. 255

Совместное использование языков высокого


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

Согласование вызовов
В операционной системе MS&DOS вызываемая процедура могла находиться либо в том
же сегменте, что и команда вызова, тогда вызов назывался близким или внутрисегмент&
ным, либо в другом сегменте, тогда вызов назывался дальним или межсегментным. Раз&
ница заключалась в том, что адрес в первом случае формировался из двух байт, а во вто&
ром &&&& из четырех байт. Соответственно, возврат из процедуры мог быть либо близким,
т.е. адрес возврата формировался на основе двух байт, взятых из стека, либо дальним,
когда адрес формировался на основе четырех байт, взятых опять же из стека. Ясно, что
вызов и возврат должны быть согласованы друг с другом. В рамках единой программы
это, как правило, не вызывало больших проблем. Но вот когда необходимо было под&
ключить библиотеку или объектный модуль, то могли возникнуть трудности. Если в объ&
ектном модуле возврат осуществлялся ближний, необходимо было компоновать объект&
ные модули так, чтобы сегмент, где находится процедура, был объединен с сегментом,
откуда осуществляется вызов. Вызов в этом случае, разумеется, должен быть ближним.
Если же возврат из процедуры осуществлялся как дальний, то и вызов этой процедуры
должен быть дальним. Проблема согласования вызовов и возвратов усугублялась еще
и тем, что ошибки обнаруживались не при компоновке, а при исполнении программы.
С этим были связаны и так называемые модели памяти в языке C, что также было голов&
ной болью многих начинающих программистов. Сегментация памяти приводила в C еще
к одной проблеме &&&& проблеме указателей. В языке Pascal пошли по другому пути. Там
приняли, что в программе должен существовать один сегмент данных и несколько сег&
ментов кода. Если же одного сегмента для хранения данных не хватало, то предлагалось
использовать динамическую память. При переходе к Windows все программисты получи&
ли замечательный подарок в виде плоской модели памяти. Теперь все вызовы являются
ближними, т.е. осуществляются в пределах одного огромного сегмента. Тем самым была
снята проблема согласования вызовов.

Согласование имен
При программировании для Windows на согласование вызовов уже можно не обра&
щать внимания, но согласование имен со временем только усложнилось. Как уже упоми&
налось ранее, транслятор MASM добавляет в конце имени @n, где n &&&& количество пере&
даваемых в стек параметров. То же делает и компилятор Visual C++. Таким образом,
трудности возникают уже при согласовании двух ассемблерных модулей. В этом смысле
TASM является более гибким компилятором, так как при желании к любому имени мож&
но добавить @n, тем самым согласовав имена.
Другая проблема &&&& подчеркивание перед именем. Транслятор MASM генерирует под&
черкивание автоматически, если в начале программы устанавливается тип вызова stdcall
(Standard Call &&&& стандартный вызов). Транслятор TASM этого не делает, следовательно,

Глава 10. Написание графических приложений Windows 255

Стр. 255
при необходимости это нужно делать непосредственно в тексте программы. Между фир&
мами Borland и Microsoft здесь полное несоответствие.
Еще одна проблема &&&& согласование заглавных и прописных букв. При трансляции
с помощью TASM используется ключ /ml как раз для того, чтобы различать прописные
и заглавные буквы. Транслятор MASM делает это автоматически. Как известно, и в стан&
дарте языка C с самого начала предполагалось различие между заглавными и прописны&
ми буквами. В языке Pascal прописные и заглавные буквы не различаются.
В этом есть своя логика: Pascal и Delphi не создают стандартных объектных модулей,
зато могут подключать их. При создании же динамических библиотек имя помещается
в библиотеку так, как оно было указано в заголовке процедуры.
Наконец, последняя проблема, связанная с согласованием имен, &&&& это уточняющие
имена в C++. Дело в том, что в языке C++ возможна так называемая перегрузка. Это
значит, что одно и то же имя может относиться к разным функциям. В тексте программы
эти функции различаются по количеству и типу параметров, а также по типу возвращае&
мого значения. Поэтому компилятор C++ автоматически делает в конце имени добавку &&&&
так, чтобы разные по смыслу функции различались при компоновке. Разумеется, фирмы
Borland и Microsoft и тут не пожелали согласовать свои позиции и делают в конце имени
совершенно разные добавки. (Обойти эту проблему не так сложно, нужно использовать
модификатор extern "С".)

Согласование параметров
В табл. 10.1 представлены основные соглашения по передаче параметров в процедуру.
Ранее во всех ассемблерных программах указывался тип передачи параметров как stdcall.
Однако, по сути, это никак и нигде не использовалось &&&& передача и извлечение пара&
метров делалась явно, без помощи транслятора. Когда мы имеем дело с языками высо&
кого уровня, необходимо четко представлять себе, как работают те или иные соглашения.

Таблица 10.1. Соглашения о вызовах


Соглашение Параметры Очистка стека Регистры

Pascal Слева направо Процедура Нет


Register (быстрый, Слева направо Процедура Задействованы три регистра
или регистровый вызов) (ЕАХ, EDX, ЕСХ), а затем стек
Cdecl Справа налево Вызывающая программа Нет
Stdcall (стандартный вызов) Справа налево Процедура Нет

Необходимо отметить еще один важный момент &&&& тип возвращаемых функцией дан&
ных. С точки зрения ассемблера здесь все предельно просто: в регистре ЕАХ возвращается
значение, которое может быть либо числом, либо указателем на некую переменную или
структуру. Если возвращаемое число имеет тип WORD, то оно передается в младшем слове
регистра ЕАХ. Однако имея дело с языками высокого уровня, следует очень аккуратно
относиться к преобразованию типов.

Простой пример использования языка ассемблера


с языком Delphi
В этом разделе рассмотрена простейшая процедура перемножения чисел на языке
ассемблера. Преимущество такого построения программы состоит в том, что можно

256 Глава 10. Написание графических приложений Windows

Стр. 256
отсутствует. 257

совмещать широчайшие возможности Delphi по созданию графической оболочки с низ&


коуровневым программированием. Другими словами, критичные к скорости работы
программы фрагменты можно написать на языке ассемблера, а остальную часть про&
граммы, связанную с вводом&выводом и созданием интерфейса пользователя, быстро
и наглядно разработать на языке Delphi, что позволит значительно сэкономить время. Та&
ким же образом можно производить отладку отдельных фрагментов программы на языке
ассемблера, используя удобный и эффективный отладчик среды Delphi (листинг 10.2).

Листинг 10.2. Использование отладчика Delphi


unit FAsm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Label1: TLabel;
Button1: TButton;
procedure Button1Click(Sender: TObject);
public
{ Открытые объявления }
function LongMul(X, Y: Integer): Longint;
procedure Display(Z: Integer);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}

{ TForm1 }
procedure TForm1.Display(Z: Integer);
begin
label1.Caption := 'Произведение = ' + IntToStr(Z);
end;

function TForm1.LongMul(X, Y: Integer): Longint;


asm
MOV EAX,X
IMUL Y
end;

procedure TForm1.Button1Click(Sender: TObject);


begin
Display(LongMul(2,3));
end;
end.

Не надо хорошо знать Delphi, чтобы увидеть, что здесь объявлены и описаны три
процедуры:
Display &&&& отображение целочисленной переменной;
LongMul &&&& умножение двух целых чисел;
Button1Click &&&& событие нажатия клавиши для отображения числа.

Глава 10. Написание графических приложений Windows 257

Стр. 257
При этом тело процедуры LongMul написано на ассемблере. Все отлично работает
и на экране можно получить окно с результатом, как показано на рис. 10.7.

Рис. 10.7. Результат работы программы


с совместным использованием языков ас%
семблера и Delphi

Большое удобство заключается в том, что можно использовать все возможности Delphi
по интегрированной отладке программы, т.е. можно сделать останов, например, на ко&
манде MOV из процедуры LongMul, и пройти по шагам весь цикл выполнения програм&
мы, отслеживая состояние всех регистров, флагов и памяти, как показано на рис. 10.8.

Рис. 10.8. Отладка программы на языке ассемблера

Для более полного представления о том, что делает компилятор с кодом для процеду&
ры LongMul, рассмотрим последовательно весь процесс.
Во&первых, компилятор оптимизирует данный код. Нет никакого кода для копирова&
ния значений параметров в локальные переменные. Это может произойти со строковыми
и другими параметрами, размер которых отличается от 1, 2 или 4 байт. В нашем случае
параметры рассматриваются в качестве переменных.
Пока функция не возвращает строку, вариант или ссылку на интерфейс, компилятор
не создает результирующую переменную и ссылка на переменную @Result вызовет
ошибку. Для строк, вариантов или ссылок на интерфейс вызывающая программа ис&
пользует указатель @Result.

258 Глава 10. Написание графических приложений Windows

Стр. 258
отсутствует. 259

Компилятор создает стековые фреймы для вложенных подпрограмм, для подпро&


грамм, которые имеют локальные параметры, и для подпрограмм, которые имеют пара&
метры в стеке.
Автоматически дополнительно генерируется входной и выходной коды для подпро&
граммы, подобно показанному ниже.
PUSH EBP ; Присутствует, если Locals <> 0 или Params <> 0
MOV EBP,ESP ; Присутствует, если Locals <> 0 или Params <> 0
SUB ESP,Locals ; Присутствует, если Locals <> 0
...
MOV ESP,EBP ; Присутствует, если Locals <> 0
POP EBP ; Присутствует, если Locals <> 0 или Params <> 0
RET Params ; Присутствует всегда
Функции на языке ассемблера возвращают результаты следующим образом.
Порядковые значения возвращаются в регистре AL (8&разрядные значения), AX (16&
разрядные значения) или EAX (32&разрядные значения).
Вещественные значения возвращаются в регистре ST(0) для стека регистров сопро&
цессора.
Указатели, включая длинные строки, возвращаются в регистре EAX.
Короткие строки и варианты возвращаются во временном участке памяти, на кото&
рый указывает @Result.

Резюме
В данной главе кратко рассмотрено использование графических примитивов Windows
и процесс написания графических приложений. В созданном программным способом
окне можно будет отображать сообщения и реагировать на события мыши. Сведения,
приведенные в главе, могут послужить только основой для понимания принципов по&
строения окон и ни в коей мере не рассчитаны на полное описание всех возможностей
создания графических приложений. Однако усвоив основы построения графических при&
ложений, можно легко понять и более развитые средства разработки оконных приложе&
ний для Windows. В главе также описано использование языка ассемблера совместно
с языками высокого уровня. Необходимо хорошо представлять эти возможности и при&
менять их при создании эффективных программ.

Контрольные вопросы
1. Опишите структуру POINT.
2. Для чего используется структура WNDCLASS?
3. Что означают поля IpfnWndProc, style и hlnstance в структуре WNDCLASS?
4. Напишите пример вызова функции MessageBox.
5. Назовите две константы для кнопок, которые используются при вызове функции
MessageBox.
6. Назовите по крайней мере три задачи, которые выполняются процедурой WinMain.
7. Опишите роль процедуры WinProc, используемой в приведенной программе.
8. Опишите роль процедуры ErrorHandler, используемой в приведенной программе.
9. Почему возникает необходимость использовать язык ассемблера при написании
программ на языке высокого уровня?

Глава 10. Написание графических приложений Windows 259

Стр. 259
Глава 11

Работа в DOS
В этой главе...

Функции DOS
Вызовы процедур
Использование моделей памяти
Адресация переменных
Команда JMP для 16&разрядного режима
Перенаправление ввода&вывода

Ранее в книге описывались возможности ассемблера для создания программ, работаю&


щих в операционной системе Windows. Но на ассемблере MASM можно писать и про&
граммы для операционной системы DOS. Точнее сказать, этот ассемблер изначально
и разрабатывался для работы с DOS, а со временем его доработали так, чтобы можно бы&
ло разрабатывать программы и для Windows. Поэтому различие небольшое, и основное
из них то, что при разработке программ для DOS необходимо использовать функции
DOS, а не процедуры Windows. Также обратите внимание на то, что способ оформления
процедуры main иной: в ней производится начальная установка сегмента данных, а для
выхода из программы используется функция DOS 4C00h.

Функции DOS
При написании программ для 16&разрядного режима можно использовать прерыва&
ние INT 21h, предназначенное для вызова функций DOS. При этом может вызваться
около 90 различных функций. Номер функции предварительно помещается в регистр AH.
Функции разделяются на две группы: ввода и вывода. Каждая функция использует вход&
ные параметры, передающиеся через регистры, которые должны быть инициализирова&
ны перед вызовом прерывания INT 21h. Ниже приведены короткие примеры кодов, вы&
зывающих функции.

Функции вывода
02h (вывод символа). Функция посылает символ на стандартное устройство вывода.
В регистр DL помещается выводимый символ. Регистр AL модифицируется системой DOS.
MOV AH,2 ; Выбор функции 2 DOS.
MOV DL,'*’ ; Cимвол для отображения помещается в DL.
INT 21h ; Вызов DOS для выполнения.

Стр. 260
отсутствует. 261

O5h (вывод на принтер). Функция посылает один символ на принтер. В регистр DL по&
мещается выводимый символ. DOS ожидает, когда принтер будет готов принять символ.
Можно прервать ожидание нажатием комбинации клавиш <Ctrl+Break>. Выходным уст&
ройством по умолчанию является порт принтера LPT1. Для немедленной печати необхо&
димо послать символ конца строки (0Dh) или страницы (0Ch), так как многие принтеры
собирают символы для печати во внешнем буфере и не производят печать, пока буфер не
заполнится или не встретятся символы конца строки или страницы. Следующие коман&
ды выведут на принтер знак доллара ($).
MOV AH,5 ; Выбор функции печати.
MOV DL,'$' ; Символ для печати.
INT 21h ; Вызов DOS.
MOV DL,0Dh ; Сделать возврат каретки.
INT 21h ; Вызов DOS.
Нет необходимости снова помещать число 5 в регистр AH перед повторным вызовом
INT 21h. Это общее правило для функций по прерыванию INT 21h.
При использовании функций по прерыванию INT 21h, номер функции, помещенный в ре&
гистр AH, сохраняется до последующего изменения программным способом.

06h (прямой вывод). Функция может или читать из стандартного входного устройства,
или выводить на стандартное устройство. Ниже показана версия для отображения символа.
MOV AH,6 ; Выбор функции 6 DOS.
MOV DL,'&' ; DL — символ для вывода.
INT 21h ; Вызов DOS.
09h (вывод строки). Функция передает строку символов на стандартное устройство
вывода. В регистр DX помещается смещение строки. Строка должна оканчиваться симво&
лом доллара ($). Управляющие символы, такие как табуляция (9h) или возврат каретки,
распознаются системой DOS. В следующем примере выходная строка включает символы
возврата каретки (0Dh) и пропуска строки (0Ah).
MOV AH,9 ; Функция вывода строки.
MOV DX,OFFSET string ; Адрес строки.
INT 21h
.DATA
string DB 'Это строка байтов.',0Dh,0Ah,'$' ;
Эта функция DOS имеет недостаток: символ доллара ($) не может быть частью стро&
ки. А если символ доллара в конце объявления пропустить, то DOS будет производить
вывод до тех пор, пока не встретит нужный символ (24h). До этого может быть выведено
большое число символов.

Функции ввода
Ниже перечислено только несколько функций для стандартного ввода.
01h &&&& Фильтрующий ввод с дублированием на экране (эхо).
06h &&&& Прямой ввод без ожидания.
07h &&&& Прямой ввод с отключенным <Ctrl+Break>.
08h &&&& Прямой ввод с <Ctrl+Break>.
0Ah &&&& Буферизованный ввод.

Глава 11. Работа в DOS 261

Стр. 261
0Bh &&&& Получение статуса ввода.
0Ch &&&& Очистить входной буфер, вызвать функцию ввода.
3Fh &&&& Чтение из файла или устройства.

Буфер клавиатуры
Это кольцевой буфер для 15 символов, который DOS использует для хранения кодов
нажатых клавиш. Это сделано для того, чтобы исключить потерю символов при очень
быстром вводе, когда программа может не успеть отследить нажатие всех клавиш. DOS
запоминает эти нажатия, но может случиться и так, что буфер переполнится и произой&
дет потеря символов, о чем система сообщит звуковым сигналом. Однако в современных
быстрых компьютерах такая ситуация не происходит.
Функции DOS для ввода символов (функции 1, 6, 7 и 8) имеют большое количество
характеристик (табл. 11.1).

Таблица 11.1. Характеристики функций DOS для ввода с консоли


Номер функции DOS 1 6 7 8

Ожидает нажатие? Да Нет Да Да


Эхо&символы? Да Нет Нет Нет
Распознается <Ctrl+Break>? Да Нет Нет Да
Фильтруются управляющие символы? Да Нет Нет Нет

Наиболее важными критериями являются следующие.


Ожидание нажатия клавиши. Функция ожидает нажатия клавиши, проверяя буфер
клавиатуры.
Эхо+символы. Отображает вводимые символы на экране. Это может быть важно,
например, при вводе пароля.
Распознавание <Ctrl+Break>. Прекращает ввод при нажатии комбинации клавиш
<Ctrl+Break>. Это можно исправить путем установки собственного обработчика
<Ctrl+Break>. Когда <Ctrl+Break> распознается системой DOS, говорят, что ком&
бинация клавиш <Ctrl+Break> активна.
Фильтрация управляющих символов. Фильтрует управляющие символы ASCII, соз&
даваемые при нажатии управляющих клавиш <Enter>, <Tab> или <Backspace> и др.
Если фильтрация используется, то говорят, что вход активный, в противном слу&
чае он неактивен.
01h (фильтрующий ввод с дублированием на экране). Функция ожидает, пока символ
будет считан с устройства ввода, посылает символ на стандартный выход (дисплей) и со&
храняет его в регистре AL. Если символ уже находится в буфере клавиатуры, то он сразу
пересылается в регистр AL.
Комбинация клавиш <Ctrl+Break> активна, поэтому пользователь может прекратить
ввод нажатием комбинации клавиш <Ctrl+Break>. В следующем примере один символ
вводится и присваивается переменной CHAR.
MOV AH,1 ; Функция ввода.
INT 21h ; Вызов DOS, код клавиши помещается в AL.
MOV CHAR,AL ; Сохранение символа.

262 Глава 11. Работа в DOS

Стр. 262
отсутствует. 263

06h (прямой ввод без ожидания). Особенность использования функции 6 DOS состоит
в том, что она не ожидает поступления очередного символа и сама обращается к стан&
дартному входному буферу за следующим символом. Если буфер пуст, функция устанав&
ливает флаг ZF (ZF = 1). Комбинация <Ctrl+Break> неактивна и фильтрации управ&
ляющих символов нет.
Перед вызовом прерывания INT 21h необходимо поместить в регистр DL значение 0FFh.
Если обнаружен символ во входном буфере, он перемещается в регистр AL и флаг ZF
сбрасывается (ZF = 0). Если символ не обнаружен, то ZF = 1.
MOV AH,6 ; Проверка входного буфера.
MOV DL,0FFh
INT 21h
Иногда в программе необходимо освободить буфер клавиатуры для команд пользова&
теля на то время, пока компьютер не готов принять и обработать их. Следующая про&
грамма комбинирует функцию 6 с циклом для очистки буфера клавиатуры.
clear_keyboard PROC
PUSH AX
PUSH DX
L1:
MOV AH,6 ; Проверка входного буфера.
MOV DL,0FFh
INT 21h
JNZ L1 ; Переход к метке L1, если ZF=0.
POP DX
POP AX
RET ; Возврат в вызывающую программу.
clear_keyboard ENDP
Команда JNZ выполняет переход на метку, если флаг нуля (ZF) сброшен.
07h (прямой ввод с неактивной <Ctrl+Break>). Функция ожидает нефильтрованный
символ со стандартного входа без эхо&символа. Комбинация <Ctrl+Break> неактивна.
Регистр AL содержит вводимый символ.
MOV AH,7 ; Функция ввода.
INT 21h ; Вызов DOS.
MOV CHAR,AL ; Сохранение символа.
08h (прямой ввод с активной <Ctrl+Break>). Функция ожидает нефильтрованный сим&
вол с консоли без эхо&символа. Комбинация <Ctrl+Break> активна. Регистр AL содержит
вводимый символ.
MOV AH,8 ; Функция ввода.
INT 21h ; Вызов DOS.
MOV CHAR,AL ; Сохранение символа.
0Ah (буферизованный ввод). Функция 0Ah считывает строку символов размером до 255
символов со стандартного входа и сохраняет ее в буфере. Клавиша <Backspace> может
использоваться для стирания символов и возврата курсора. Пользователь может прервать
ввод нажатием клавиши <Enter>. DOS не пропускает нажатие клавиш, не создающих код
ASCII, таких как перемещение курсора или <PgDn>, которые не сохраняются в буфере.
Комбинация <Ctrl+Break> активна и все вводимые символы отображаются на экране.
Байт со смещением 0 содержит максимальное число символов, которое можно ввести,
включая клавишу <Enter>. Допустим, это число равняется 5, тогда DOS позволит ввести
только четыре символа плюс клавишу <Enter>. В байте со смещением 1 сохраняется ко&
личество введенных символов. Сами символы будут размещены в буфере со смещением 2.

Глава 11. Работа в DOS 263

Стр. 263
В следующем примере максимальное значение maxКeys равно 32, количество введенных
символов charsInput заполняется DOS после прерывания INT 21h, и буфер buffer
содержит символы, введенные пользователем.

Листинг 11.1. Ввод данных


title Ввод данных.
.model small
.stack 100h
.DATA
keyboard LABEL BYTE
maxKeys DB 32 ; Максимально разрешенное количество.
charsInput DB ? ; Количество введенных символов.
buffer DB 32 dup(0) ; Сохранение введенных символов.
.CODE
main proc
MOV AX,@DATA ; Установка сегмента данных.
MOV DS,AX
MOV DX,OFFSET keyboard ; DX - пространство параметров.
MOV AH,0Ah ; Выбор ввода с консоли.
INT 21h ; Вызов DOS.
MOV AX,4C00h
INT 21h
main endp
end main

Если проанализировать с помощью отладчика полученные при выполнении про


граммы переменные, то после вызова прерывания INT 21h видно, что максимально раз
решенное количество символов составляет 32, количество введенных символов равно 9,
а в буфере находятся все введенные значения, включая клавишу <Enter>, которая ото
бражается соответствующим символом, но счетчик не учитывает это значение (рис. 11.1).
Клавиша Insert
Клавиша CapsLock
Клавиша NumLock
Клавиша ScrollLock
Клавиша Alt
Клавиша Ctrl
Клавиша Shift (левая)
Клавиша Shift (правая)

7 6 5 4 3 2 1 0

Рис. 11.1. Содержание буфера ввода

OBh (получение статуса ввода). Функция 0Bh проверяет стандартный буфер ввода на
наличие в нем символов. Если есть символ, то регистр AL = 0FFh; в противном слу
чае AL = 0.
MOV AH,0Bh ; Проверка статуса ввода.
INT 21h ; Вызов DOS.
0Ch (очистка входного буфера, вызов функции ввода). Функция 0Ch очищает буфер кла
виатуры и вызывает функцию из прерывания INT 21h для ввода с консоли. Эту функцию

264 Глава 11. Работа в DOS

Стр. 264
отсутствует. 265

можно использовать для защиты от ввода до того, как на экране появится приглашение.
В регистр AL помещается номер функции DOS (1, 6, 7 или 8). Входной символ будет раз&
мещаться в регистре AL. В следующем примере происходит очистка буфера и вызов
функции 1 для ввода символа.
MOV AH,0Ch ; Очистка буфера.
MOV AL,1 ; Вызов функции 1 после окончания.
INT 21h ; Вызов DOS.
MOV CHAR,AL ; Сохранение символа.
3Fh (чтение из файла или входного устройства). Функция может использоваться для
чтения строки символов из файла или входного устройства. Регистр DX содержит смеще&
ние входного буфера, размер которого больше или равен значению величины, помещен&
ной в регистр CX. Регистр BX определяет стандартное входное устройство или индекс
файла. Для стандартной клавиатуры значение регистра BX=0. В регистре CX находится
значение максимального количества байтов для чтения.
Функция 3Fh может считывать символы со стандартного входа и прекращает работу
при нажатии клавиши <Enter>. Необходимо установить BX в 0 (ввод с клавиатуры), в CX
поместить максимальное число считываемых байтов и в DX установить смещение буфера.
Функция возвращает число реально считанных символов в регистр AX.
.DATA
inputBuffer DB 127 dup(0)
.CODE
MOV AH,3Fh ; Чтение из файла/устройства.
MOV BX,0 ; Устройство (при значении 0 - клавиатура).
MOV CX,127 ; Максимально можно считать 127 байт.
MOV DX,OFFSET inputBuffer
INT 21h ; В AX будет число считанных символов.
Клавиши <Backspace> и <←> можно применять для редактирования ввода, как и при
использовании функции 0Ah. При нажатии клавиши <Enter> DOS добавляет символы
возврата каретки (0Dh) и пропуска строки (0Ah) во входной буфер. Кроме того, DOS уве&
личивает на 2 счетчик символов в регистре AX. Количество считываемых символов, раз&
мещенное в CX, должно также включать символы возврата каретки и пропуска строки
(CR/LF), которые DOS добавляет к строке.

Управляющие клавиши клавиатуры


Клавиши, не имеющие соответствующего кода ASCII, называются управляющими кла%
вишами. В их число входят клавиши перемещения курсора, клавиши <PgUp>, <PgDown>,
<Home>, <End> и другие. При нажатии управляющей клавиши первым символом, по&
мещаемым во входной буфер, будет 00h, а за ним следует цифровой скан%код клавиши.
Когда вызываются функции прерывания INT 21h, они возвращают очередной байт из
буфера клавиатуры. Если же была нажата управляющая клавиша, возвращаемое значение
будет 00h. Чтобы получить скан&код нажатой клавиши, необходимо получить из буфера еще
один символ. Следующие команды читают и сохраняют скан&коды управляющих клавиш.
MOV AH,7 ; Функция ввода с консоли.
INT 21h ; Вызов DOS: AL = 0 для управляющих клавиш.
INT 21h ; Сейчас AL содержит скан-код клавиши.
MOV scanCode,AL ; Сохранение скан-кода.
Если пользователь нажмет клавишу со скан&кодом, возникнет непредвиденная ситуа&
ция. Компьютер остановится и будет ожидать нажатия следующей клавиши. Далее пока&
зано, как избежать этих проблем с помощью прерывания INT 16h.

Глава 11. Работа в DOS 265

Стр. 265
Функции даты/времени
2Ah (получить дату). Функция возвращает текущую системную дату, помещая год в ре&
гистр CX, а номер месяца &&&& в регистр DH. Номер дня помещается в регистр DL, а день не&
дели &&&& в регистр AL. Для дней недели используются числовые обозначения: 0 &&&& для
воскресенья, 1 &&&& для понедельника и т.д. В следующих командах вызывается функция
и сохраняются значения.
MOV AH,2Ah
INT 21h
MOV year,CX
MOV month,DH
MOV day,DL
MOV dayOfWeek,AL
2Bh (установить дату). Функция устанавливает текущую системную дату, используя
те же регистры, что и в функции 2Ah (получить дату). Функция возвращает значение 0
в регистр AL, если изменение прошло успешно, или значение 0FFh, если данные не были
изменены.
MOV AH,2Bh
MOV CX,year
MOV DH,month
MOV DL,day
INT 21h
CMP AL,0
JNE badDate
2Ch (получить время). Функция возвращает текущее системное время, помещая часы
в регистр CH, минуты &&&& в регистр CL, секунды &&&& в DH и сотые доли секунд &&&& в DL. По&
следнее значение обычно некорректное. Рассмотрим пример вызова функции.
MOV AH,2Ch
INT 21h
MOV hours,CH
MOV minutes,CL
MOV seconds,DH
2Dh (установить время). Функция устанавливает текущее системное время, используя
те же регистры, что и функция 2Ch (получить время). Функция возвращает значение 0,
если изменение прошло успешно, и значение 0FFh, если возникли ошибки.
MOV AH,2Dh
MOV CH,hours
MOV CL,minutes
MOV DH,seconds
INT 21h
CMP AL,0
JNE badTime

Управляющие клавиши
Ранее уже говорилось о том, что когда пользователь нажимает управляющие клавиши,
такие как F1 или PgUp, BIOS сохраняет два байта в буфере клавиатуры. Если использует&
ся функция 8 прерывания INT 21h для чтения управляющих клавиш, то прерывание
должно вызываться дважды. В листинге 11.2 процедура GetKey читает как коды ASCII,
так и управляющие клавиши.

266 Глава 11. Работа в DOS

Стр. 266
отсутствует. 267

Листинг 11.2. Чтение управляющих клавиш с помощью прерывания INT 21h


.DATA
key DB ?
extended DW ? ; 1 = true, 0 = false.
.CODE
GetKey PROC
MOV extended,0 ; Присвоить неуправляющим клавишам.
MOV AH,8 ; Принять нажатие без эхо-символа.
INT 21h ; Вызвать прерывание DOS.
CMP AL,0 ; Управляющая клавиша? Если да — AL = 0,
JNE L1 ; если нет — AL = код ASCII.
INT 21h ; Получить скан-код
MOV extended,1 ; и установить переключатель <extended>.
L1:
MOV key,AL ; Сохранить в переменной.
RET
GetKey ENDP

Вызовы процедур
Ранее подробно было рассмотрено использование процедур, но нигде не упоминалось
о ближних и дальних вызовах. При программировании под Windows такой проблемы не
возникает, так как все вызовы считаются ближними, в пределах одного большого сегмен&
та памяти, при этом используется 32&разрядная адресация. При работе с DOS можно ис&
пользовать ближние вызовы в пределах одного сегмента памяти размером 64 Кб, для чего
можно применить один 16&разрядный регистр, или дальние вызовы с заходом в другой
сегмент, что требует использования двух 16&разрядных регистров. Это связано с ограни&
чениями DOS на размер сегмента.
Поэтому команда вызова процедуры CALL помещает в стек адрес возврата, занимающий
16 бит, если процедура определена с атрибутом NEAR, и 32 бита, если она определена с атрибу&
том FAR. Процедуры с атрибутом NEAR могут быть вызваны только из того сегмента, в кото&
ром они находятся; процедуры с атрибутом FAR могут быть вызваны и из другого сегмента.
Если процедура имеет атрибут FAR, то команда CALL загружает также адрес сегмента
процедуры в регистр CS.
Если процедура содержит атрибут NEAR, то команда CALL помещает смещение адреса
следующей команды в стек. Если процедура включает атрибут FAR, то команда CALL по&
мещает в стек содержимое регистра CS, а затем смещение адреса.
Если процедура имеет атрибут NEAR (находится в том же сегменте, что и команда CALL),
то команда RET извлекает из стека одно слово и загружает его в указатель команд IP. Ес&
ли процедура имеет атрибут FAR (находится в другом сегменте), то команда RET извлека&
ет из стека два слова: сначала смещение адреса для загрузки в указатель команд IP, а за&
тем слово для загрузки в регистр CS.
Можно вызвать процедуру с атрибутом NEAR через регистр, например:
CALL BX
В данном случае регистр ВХ содержит смещение адреса процедуры относительно ре&
гистра сегмента CS. При исполнении этой команды микропроцессор копирует содержи&
мое регистра ВХ в указатель команд IP, затем передает управление команде, адресуемой
парой регистров CS:IP. Если, например, регистр ВХ содержит значение 1АВН, то микро&
процессор извлечет следующую команду из ячейки 1АВН, находящейся в сегменте команд.

Глава 11. Работа в DOS 267

Стр. 267
Процедуру с атрибутом NEAR можно вызвать косвенно, используя переменную разме&
ром в слово, например:
CALL WORD PTR [BX]
CALL WORD PTR [BX][SI]
CALL WORD PTR variable_name
CALL WORD PTR variable_name[ВХ]
CALL mem_word
CALL WORD PTR ES:[BX][SI]
Последняя команда CALL получает адрес процедуры из ячейки дополнительного сег&
мента (использовано переопределение сегмента ES:); остальные команды извлекают ад&
реса процедур из ячеек сегмента данных.
Ниже показан пример ближнего вызова процедуры. Перед переходом к процедуре
команда CALL сохраняет текущее значение указателя команд (IP) в стеке. Затем в IP по&
мещается смещение подпрограммы, что заставит процессор немедленно перейти к вы&
полнению первой команды подпрограммы.

Пример ближнего вызова


Вызывающая программа Ближняя процедура
main PROC sub1 PROC
0006: CALL sub1 0080: MOV AX,1
0009: INC AX
. .
. RET
main ENDP sub1 ENDP

Когда вызывающая программа и подпрограмма находятся в разных кодовых сегмен&


тах, то используется механизм дальнего вызова. Перед переходом к подпрограмме команда
CALL сохранит текущее значение регистров CS и IP в стеке и затем загрузит адрес сегмен&
та в регистр CS и смещение подпрограммы в IP. Выполнение начнется с нового адреса.
В следующем примере программа main вызывает подпрограмму из другого кодового
сегмента. Необходимо, чтобы декларация FAR была добавлена после имени подпрограм&
мы subroutine1, а оператор FAR PTR добавлен в команду CALL. Объявление подпро&
граммы с декларацией FAR сообщает ассемблеру, что для корректной работы программы
нужно использовать команду RETF вместо RET, которая применяется в исходном файле.
Вызов дальней подпрограммы занимает больше времени, так как возникает необходи&
мость в сохранении значения сегмента кодов (CS).

Пример дальнего вызова


Вызывающая программа Дальняя процедура
main PROC sub1 PROC FAR
2FC0:0006 CALL FAR PTR sub1 3AB6:0080 MOV AX,1
2FC0:0009 INC AX .
.
. RET
main ENDP sub1 ENDP

268 Глава 11. Работа в DOS

Стр. 268
отсутствует. 269

Возникает вопрос: зачем использовать дальние вызовы, если ближние вызовы проще
и быстрее? Дальние вызовы необходимы в следующих случаях.
Когда программа на языке ассемблера вызывается из программы на языке высо&
кого уровня. В этом случае вызывающая программа будет диктовать тип вызова.
Например, модель памяти large в программе на языке С требует дальнего вызова
подпрограмм.
Когда производится вызов из библиотеки подпрограмм, то библиотека может быть
разработана для использования дальних вызовов.
Когда прикладная программа настолько большая, что требуется более 64 Кбайт
памяти для команд, а также в случае, если используются модели памяти medium
или large (тогда может быть несколько сегментов кодов, поэтому требуется ис&
пользование дальних вызовов).

Использование моделей памяти


Директива .MODEL автоматически устанавливает атрибут NEAR или FAR для всех про&
цедур программы. Модели tiny, small и compact устанавливают атрибут в значение
NEAR. Модели medium, large и huge присваивают атрибуту значение FAR. Ассемблер
автоматически генерирует удаленные вызовы в командах CALL для этих моделей. Про&
грамма, приведенная в листинге 11.3, показывает, как именно это делается. В программе
main происходит вызов удаленной процедуры sub_FAR. Оператор FAR PTR требуется
для дальних вызовов вперед, когда вызываемая процедура размещается в другом сегмен&
те: CALL FAR PTR sub_FAR.

Листинг 11.3. Пример программы с большой моделью памяти


TITLE Программа с большой моделью памяти.
; Нельзя совмещать модель памяти large с загрузочными библиотеками,
; которые оттранслированы для модели памяти small.
.MODEL large ; Процедуры по умолчанию имеют атрибут FAR.
.STACK 100h
.DATA
msg2 DB "В процедуре Sub_Far",0dh,0ah,0
.CODE
EXTRN Writestring:PROC
main PROC
MOV AX,@DATA
MOV DS,AX
CALL FAR PTR Sub_FAR
MOV AX,4C00h
INT 21h
main ENDP

sub_FAR PROC
MOV DX,OFFSET msg2
CALL NEAR PTR Writestring ; Загрузчик выдаст ошибку.
RET
sub_FAR ENDP
END main

Обратите внимание на то, что загрузочные библиотеки могут быть оттранслированы


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

Глава 11. Работа в DOS 269

Стр. 269
изменять регистр CS, загружая в него значение из стека. Если программа выполняет даль&
ний вызов по умолчанию, то при использовании этих библиотек необходимо делать вызовы
с помощью оператора NEAR PTR для команды CALL. Несмотря на то что загрузчик будет
обнаруживать ошибку переполнения привязки адреса, работа будет происходить корректно.
Программы, транслируемые с использованием моделей памяти medium или large,
будут иметь отдельные сегменты кодов для каждого модуля. Например, программа в при&
веденном выше листинге оттранслирована с моделью памяти large. В таблице распре&
деления памяти (табл. 11.2) будут указаны имена всех сегментов. Сегменты кодов будут
иметь имена с окончанием _TEXT.

Таблица 11.2. Распределение памяти


Start Stop Length Name Class
00000H 00015H 00016H LARGEM_TEXT CODE
00016H 00092H 0007DH TEXT CODE
000A0H 000BBH 0001CH _DATA DATA
000C0H 001BFH 00100H STACK STACK

Можно дополнительно создать сегмент кодов, добавив его имя к директиве .CODE.
Будет создан новый сегмент кодов, отличающийся от сегмента по умолчанию. В следую&
щей директиве объявляется сегмент, названный MYCODE.
.CODE MYCODE
В таблице распределения памяти (табл. 11.3) появится сегмент MYCODE.

Таблица 11.3. Распределение памяти


Start Stop Length Name Class
00000H 00000H 00000H LARGEM_TEXT CODE
00000H 00015H 00016H MYCODE CODE
00016H 00092H 0007DH _TEXT CODE
000A0H 000BBH 0001CH _DATA DATA
000C0H 001BFH 00100H STACK STACK

Если создается сегмент кодов с именем, которое отличается от имеющихся в загрузоч&


ной библиотеке, ассемблер сообщит, что ближний вызов из другого сегмента не разрешен.
.CODE MYCODE
myProc PROC
MOV DX,OFFSET message
CALL NEAR PTR Writestring ; Ошибка!

Адресация переменных
При программировании для DOS регистры CS, DS, SS, ES используются как указатели на
соответствующий сегмент, т.е. содержат адрес сегмента, в отличии от программ для Windows,
где эти регистры указывают на соответствующие ячейки таблицы дескрипторов.
Смещение переменной &&&& это расстояние от начала сегмента до места размещения дан&
ных. Имена переменных автоматически связываются со смещением. Например, если

270 Глава 11. Работа в DOS

Стр. 270
отсутствует. 271

объявить массив из четырех символов, то его имя str1 определяет смещение первого
символа (A).
.DATA
str1 DB "ABCD"
Если, условно говоря, смещение первого символа равно 0, то смещение второго сим&
вола будет равно 1, следующего &&&& 2 и т.д. Смещение имени str1 равно 0, как и смеще&
ние первого символа.
При использовании команд условного перехода необходимо учитывать расстояние, на ко&
торое совершается переход. Адрес перехода должен быть на расстоянии –128 или +127 байт
от команды перехода, что не всегда удобно, если логическая структура большая. Для полу&
чения больших расстояний переходов необходимо использовать специальные конструкции.

Команда JMP для 16*разрядного режима


Если переход происходит в текущий сегмент, то смещение метки загружается непо&
средственно в регистр IP. Если метка находится в другом сегменте, адрес сегмента до&
полнительно загружается в регистр CS. Для преобразования меток или 32&разрядного ад&
реса ‘‘сегмент&смещение’’ пункта назначения существуют три формата команды JMP.
JMP SHORT пункт назначения
JMP NEAR PTR пункт назначения
JMP FAR PTR пункт назначения
Примеры различных переходов приведены ниже.
JMP L1 ; NEAR: пункт назначения в текущем сегменте.
JMP NEAR PTR L1 ; NEAR: пункт назначения в текущем сегменте.
JMP SHORT nextval ; SHORT: от -128 до +127 байт.
JMP FAR PTR error_rtn ; FAR: переход в другой сегмент.
Операторы, помещенные перед пунктом назначения, могут быть следующими:
SHORT &&&& переход на метку в диапазоне от –128 до +127 байт от адреса следующей
команды. Целое 8&разрядное число, называемое смещением, добавляется в указа&
тель команд IP;
NEAR PTR &&&& переход на метку где&нибудь в текущем сегменте. 16&разрядный
сдвиг добавляется в IP;
FAR PTR &&&& переход на метку в другом сегменте. Сегментная часть адреса метки
загружается в CS, а смещение &&&& в IP.
Оператор SHORT особенно эффективен, когда необходим переход вперед, потому что
ассемблер не знает адреса пункта назначения, пока не транслирует эту часть программы,
например:
label1: JMP SHORT label2 ; Используйте SHORT здесь.
label2: JMP label1 ; Переход SHORT.
Оператор NEAR PTR сообщает ассемблеру, что метка с новым адресом находится
в том же самом кодовом сегменте (чаще всего так и случается). Если метка находится за
пределами текущего сегмента, то необходим оператор FAR PTR. В каждом из показанных
ниже примеров метка exit указывает точку перехода.
JMP NEAR PTR exit
JMP FAR PTR exit

Глава 11. Работа в DOS 271

Стр. 271
Цикл, основанный на операторе JMP, никогда не остановится, как показано в приве&
денном примере. Но использование прерывания INT 21h позволяет остановить про&
грамму в отладчике с помощью нажатия клавиш <Ctrl+Break>.
start: MOV AH,2 ; Вывести символ на экран.
MOV DL,'A'
INT 21h
JMP start
Проследить, как транслируется команда JMP, поможет следующий фрагмент из лис&
тинга, сгенерированного ассемблером. В начале каждой строки указано шестнадцате&
ричное смещение каждой команды с последующим объектным кодом, создаваемым ас&
семблером.

Трансляция команды JMP


Смещение Машинный код Исходный код
0100 B4 02 start: MOV AH,2 ; Начало цикла.
0102 B2 41 MOV DL,'A' ; Вывести символ A.
0104 CD 21 INT 21h ; Вызов DOS.
0106 EB F8 JMP start ; Переход к началу.
0108 (и т.д.)

Перед трансляцией команд ассемблер инкрементирует внутренний счетчик команд,


который указывает смещение следующей команды. Например, когда транслируется коман&
да со смещением 0106h, ассемблер уже устанавливает счетчик команд в значение 0108h.
Переход к строке 0106h автоматически делается коротким, так как расстояние от счет&
чика команд до метки start меньше чем 128 байт.
Объектный код, созданный ассемблером для команды JMP в строке 0106h, будет вы&
глядеть таким образом: EBh и F8h. Первый байт (EBh) является кодом операции для
близкого перехода. Второй байт (F8h) или (-8) представляет смещение, которое указыва&
ет процессору, как далеко нужно переходить. Ассемблер рассчитывает это значение, вы&
читая из счетчика команд (0108h) значение пункта назначения (0100h). Результирую&
щее смещение (F8h) записывается как часть машинной команды.
op code -> EB F8 <- сдвиг

Перенаправление ввода*вывода
Обычно для ввода и вывода данных используются стандартные входные и выходные
устройства. Эти устройства можно назвать одним словом &&&& консоль, при этом для ввода
данных используется клавиатура, а вывод производится на дисплей.
Из командной строки DOS можно переопределить стандартные устройства так, что
ввод данных будет производиться из файла или аппаратного порта (вместо клавиатуры),
а вывод может быть перенаправлен в файл или любой аппаратный порт вместо дисплея.
Например, программа с именем prog1.exe может выполнять ввод&вывод так, как пока&
зано в табл. 11.4.

272 Глава 11. Работа в DOS

Стр. 272
отсутствует. 273

Таблица 11.4. Перенаправление ввода*вывода


Команда Действие
prog1 > prn Печать на принтере
prog1 < infile.txt Ввод из файла infile.txt
prog1 < infile.txt > prn Ввод из файла, вывод на принтер

Например, если ввести команду HELP>helpDOS.txt, то в текущем каталоге будет


создан файл helpDOS.txt с перечнем всех команд DOS.
Обозначение PRN является именем стандартного устройства, распознаваемого систе&
мой DOS. Полный перечень обозначений устройств приведен в табл. 11.5.

Таблица 11.5. Стандартные обозначения устройств в DOS


Устройство Описание
CON Консоль (дисплей и клавиатура)
LPT1 или PRN Первый параллельный порт для принтера
LPT2, LPT3 Параллельные порты 2 и 3
COM1, COM2 Последовательные порты 1 и 2
NUL Несуществующее (фиктивное) устройство

Глава 11. Работа в DOS 273

Стр. 273
Глава 12

Справочный раздел
В этой главе...

Система команд для процессоров Intel


Библиотека функций для работы в 32&разрядном режиме
Подключаемый файл для работы с библиотекой функций
Подключаемый файл с макроопределениями
Перечень команд DOS для Windows XP
Отображаемые символы в кодировке ASCII

Система команд для процессоров Intel


Данный раздел является справочником по системе команд реального режима семей&
ства процессоров Intel 8086.

Флаги
В описании каждой команды используется диаграмма для флагов, которые применяют&
ся при выполнении данной команды в процессоре. Каждый флаг определяется одной буквой:

O Переполнение S Знак P Четность


D Направление Z Нуль C Перенос
I Прерывание A Дополнительный перенос

В самой диаграмме используются следующие символы для указания действия флагов:

1 Установить флаг
0 Сбросить флаг
? Возможна установка флага в неопределенное состояние
(пусто) Состояние флага не изменяется
* Состояние флага изменяется в соответствии с правилами, определенными для этого флага

Например, в следующей диаграмме для регистра флагов определены такие состояния


флагов для отдельной команды: флаги переполнения, знака, нуля и четности принимают

Стр. 274
отсутствует. 275

случайное значение. Флаги переноса и дополнительного переноса изменяются в соответ&


ствии с заданными для них правилами. Флаги направления и прерывания не изменяются:

O D I S Z A P C
? ? ? * ? *

Формат и описание команд


Когда в команде присутствуют и операнд&отправитель, и операнд&получатель, ис&
пользуется единый порядок, принятый в описаниях всех команд для процессоров Intel,
при котором первым указывается операнд&получатель, а вторым операнд&отправитель.
Например, в команде MOV в операнд&получатель будет скопировано содержимое операн&
да&отправителя:
MOV получатель, отправитель
Для описания форматов команды используется ряд аббревиатур, которые перечисле&
ны в табл. 11.1. В описании отдельных команд используется обозначение (80386), кото&
рое говорит о том, что сама команда или один из ее вариантов доступны только для про&
цессоров 80386 и более старших моделей. Обозначение (80286) указывает на то, что
может использоваться процессор, аналогичный 80286.

Таблица 12.1. Аббревиатуры для описания команд


Обозначение Описание
reg 8&, 16& или 32&разрядный регистр из списка: AH, AL, BH, BL, CH, CL, AX,
BX, CX, DX, SI, DI, BP, SP, EAX, EBX, ECX, EDX, ESI, EDI, EBP и ESP
reg8, reg16, reg32 Регистр общего назначения, определяемый количеством бит
segreg 16&разрядный регистр сегмента (CS, DS, ES, SS, FS, GS)
accum AL, AX или EAX
mem Операнд памяти, используемый для адресации при стандартных моделях памяти
mem8, mem16, mem32 Операнд памяти, определяемый количеством бит
shortlabel Размещение в сегменте кодов в диапазоне от &&128 до +127 байт от текущего
положения
nearlabel Команда размещается в текущем сегменте кодов. Выделена меткой
farlabel Команда размещается во внешнем сегменте кодов. Выделена меткой
imm Непосредственный операнд
imm8, imm16, imm32 Непосредственный операнд с определенным количеством битов
instruction Команды языка ассемблера

Система команд
AAA

O D I S Z A P C
? ? ? * ? *

Глава 12. Справочный раздел 275

Стр. 275
ASCII+коррекция после сложения (ASCII Adjust after Additional). Корректирует результат
сложения двух чисел в формате ASCII в регистре AL. Если значение в AL больше 9, то
старшая цифра результата помещается в регистр AH и устанавливаются флаги переноса
и дополнительного переноса.
Формат команды: AAA

AAD

O D I S Z A P C
? * * ? * ?

Коррекция ASCII перед делением (ASCII Adjust before Division). Преобразовывает неупа&
кованные числа в формате BCD в регистрах AH и AL в двоичные значения перед выпол&
нением команды DIV.
Формат команды: AAD

AAM

O D I S Z A P C
? * * ? * ?

ASCII+коррекция после умножения (ASCII Adjust after Multiply). Корректирует результат


в регистре AX после перемножения двух неупакованных чисел в формате BCD.
Формат команды: AAM

AAS
O D I S Z A P C
? ? ? * ? *

ASCII+коррекция после вычитания (ASCII Adjust after Subtruction). Корректирует резуль&


тат в регистре AX после команды вычитания. Если значение в регистре AL>9, команда AAS
декрементирует значение в регистре AH и устанавливает флаги переноса и дополнитель&
ного переноса.
Формат команды: AAS

ADC
O D I S Z A P C
* * * * * *

Сложение с переносом (ADD Carry). Складывает операнды отправитель и получатель


и добавляет содержимое флага переноса к сумме, которая сохраняется в операнде&
получателе.
Формат команды:
ADC reg, reg ADC reg, immed
ADC mem, reg ADC mem, immed
ADC reg, mem ADC accum, immed

276 Глава 12. Справочный раздел

Стр. 276
отсутствует. 277

ADD

O D I S Z A P C
* * * * * *

Сложение (ADD). Складывает операнд&отправитель с операндом+получателем. Сумма


сохраняется в операнде&получателе.
Формат команды:
ADD reg, reg ADD reg, immed
ADD mem, reg ADD mem, immed
ADD reg, mem ADD accum, immed

AND
O D I S Z A P C
* * * ? * 0

Логическое И (logical AND). С каждым битом операнда&получателя и соответствующим


битом операнда&отправителя производится логическая операция И.
Формат команды:
AND reg, reg AND reg, immed
AND mem, reg AND mem, immed
AND reg, mem AND accum, immed

BOUND
O D I S Z A P C

Проверка границ массива (check array BOUND). (80282) Проверяет, находится ли ин&
декс массива в указанном диапазоне. Для процессоров 80282 операндом&получателем
может быть любой 16&разрядный регистр, содержащий проверяемый индекс. Операндом&
отправителем должен быть 32&разрядный операнд памяти, в котором старшее и младшее
слово содержит верхнюю и нижнюю границу диапазона. Для процессора 80383 операн&
дом&получателем должен быть 32&разрядный регистр, а операндом&отправителем должен
быть 64&разрядный операнд памяти.
Формат команды:
BOUND reg16, mem32 BOUND reg32, mem64

BSF, BSR
O D I S Z A P C
*

Сканирование битов (Bit Scan). (80383) Сканирует операнд для нахождения первого
единичного бита. Если бит найден, то флаг нуля сбрасывается и в операнде&получателе

Глава 12. Справочный раздел 277

Стр. 277
устанавливается номер позиции бита (индекс). Если единичный бит не найден, то ZF=1.
Команда BSF производит сканирование от нулевого бита к старшим битам, а команда BSR
сканирует от старшего бита к младшим.
Формат команды (для BSF и BSR):
BSF reg16, reg16 BSF reg32, reg32
BSF reg15, mem16 BSF reg32, mem32

BSWAP
O D I S Z A P C

Перестановка байт (Byte SWAP). (80486) Переставляет первый и четвертый, второй и


третий байты в 32&разрядном регистре.
Формат команды:
BSWAP reg32

BT, BTC, BTR, BTS


O D I S Z A P C
*

Проверка битов (Bit Tests). (80386) Копирует определенный бит во флаг переноса. Опе&
ранд&получатель содержит значение, в котором производится проверка, а операнд&
источник содержит номер позиции для проверки. Команда BT только копирует бит во флаг
переноса. Команда BTC копирует бит и инвертирует его в операнде&получателе. Команда BTR
копирует бит и устанавливает его в 0, а команда BTS копирует бит и устанавливает его в 1.
Формат команды (для BT, BTC, BTR и BTS):
BT reg16, immed8 BT mem16, immed8
BT reg16, reg16 BT mem16, reg16

CALL
O D I S Z A P C

Вызов процедуры (CALL procedure). Помещает в стек адрес следующей команды и со&
вершает переход по указанному адресу. Если процедура является ближней (в том же сег&
менте), то в стек помещается только смещение следующей команды, в противном случае
в стек помещается и смещение, и сегмент.
Формат команды:
CALL nearlabe CALL mem16
CALL farlabel CALL mem32
CALL reg

278 Глава 12. Справочный раздел

Стр. 278
отсутствует. 279

CBW
O D I S Z A P C

Преобразование байта в слово (Convert Byte to Word). Заполняет значением знакового


бита регистра AL регистр AH.
Формат команды: CBW

CDQ
O D I S Z A P C

Преобразование двойного слова в учетверенное (Convert Doubleword to Quadword). (80386)


Заполняет значением бита знака регистра EAX регистр EDX.
Формат команды: CDQ

CLC
O D I S Z A P C
0

Сбросить флаг переноса (CLear Carry flag). Установить флаг переноса в нуль.
Формат команды: CLC

CLD
O D I S Z A P C
0

Сбросить флаг направления (CLear Direction flag). Установить флаг направления в нуль.
Команды строковых примитивов автоматически инкрементируют регистры SI и DI.
Формат команды: CLD

CLI
O D I S Z A P C
0

Сбросить флаг прерывания (CLear Interrupt flag). Установить флаг прерывания в нуль.
Это вынуждает процессор не реагировать на аппаратные прерывания, пока не будет вы&
полнена команда STI.
Формат команды: CLI

CMC
O D I S Z A P C
*

Инвертировать флаг переноса (CoMplement Carry flag). Изменяет значение флага переноса.
Формат команды: CMC

Глава 12. Справочный раздел 279

Стр. 279
CMP
O D I S Z A P C
* * * * * *

Сравнение операндов (CoMPare). Сравнивает операнд&отправитель с операндом&полу&


чателем, производя вычитание одного операнда из другого.
Формат команды:
CMP reg, reg CMP reg, immed
CMP mem, reg CMP mem, immed
CMP reg, mem CMP accum, immed

CMPS, CMPSB, CMPSW, CMPSD


O D I S Z A P C
* * * * * *

Сравнение строк (CoMPare Strings). Сравнивает строки, расположенные по адресам


DS:SI и ES:DI. Вычитает одну строку из другой. Команда CMPSB сравнивает байты, ко&
манда CMPSW &&&& слова, а команда CMPSD &&&& двойные слова (для процессора 80386). В ре&
гистрах SI и DI автоматически производится инкремент или декремент в соответствии
с размерами операндов и в зависимости от состояния флага направления. Если DF=1, SI
и DI декрементируются, если DF=0, также инкрементируются SI и DI.
Формат команды:
CMPS, CMPSB source, dest
CMPS CMPSW CMPSD segreg:source, ES:dest

CMPXCHG
O D I S Z A P C
* * * * * *

Сравнить и переставить (CoMPare and eXCHanGe). (80486) Сравнить операнд&


получатель с аккумулятором (AL, AX или EAX). Если они равны, скопировать значение
операнда&отправителя в операнд&получатель. В противном случае операнд&получатель
скопировать в аккумулятор.
Формат команды:
CMPXCHG reg, reg CMPXCHG mem, reg

CWD
O D I S Z A P C

Преобразовать слово в двойное слово (Convert Word to Doubleword). Заполнить значением


знакового бита регистра AX регистр DX. Эта команда обычно используется для подготов&
ки операции деления (IDIV).
Формат команды: CWD

280 Глава 12. Справочный раздел

Стр. 280
отсутствует. 281

CWDE
O D I S Z A P C

Конвертировать слово в расширенное двойное слово (Convert WorD to Extended double).


(80386) Заполнить значением знакового бита регистра AX старшее слово регистра EAX.
Формат команды: CWDE

DAA
O D I S Z A P C
? * * * * *

Десятичная коррекция после сложения (Decimal Adjust after Addition). Корректирует


двоичное значение в регистре AL после сложения двух упакованных чисел в формате BCD.
Преобразовывает сумму двух чисел в формате BCD в регистре AL.
Формат команды: DAA

DAS
O D I S Z A P C
? * * * * *

Десятичная коррекция после вычитания (Decimal Adjust after Subtraction). Корректирует


двоичное значение в регистре AL после вычитания двух упакованных чисел в формате BCD.
Формат команды: DAS

DEC
O D I S Z A P C
* * * * *

Декремент (DECrement). Вычитает 1 из операнда. Не изменяет флаг переноса.


Формат команды:
DEC reg DEC mem

DIV
O D I S Z A P C
? ? ? ? ? ?

Деление без знака (unsigned integer DIVide). Выполняет деление 8&, 16& или 32&разряд&
ных чисел без знака. Если делитель является 8&разрядным числом, делимое помещается
в регистр AX, частное &&&& в регистр AL, а остаток &&&& в регистр AH. Если делимое является
16&разрядным числом, делимое помещается в регистры DX:AX, частное &&&& в регистр AX,
и остаток &&&& в регистр DX. Если делитель является 32&разрядным числом, делимое поме&
щается в регистры EDX:EAX, частное &&&& в регистр EAX и остаток &&&& в регистр EDX.
Формат команды:
DIV reg DIV mem

Глава 12. Справочный раздел 281

Стр. 281
ENTER
O D I S Z A P C
? * * * * *

Установка кадра стека. (80286) Создает кадр стека для процедуры, которая принима&
ет параметры и использует локальные переменные стека. Первый операнд определяет
число байт для хранения локальных переменных. Второй операнд определяет уровень
вложения процедур (устанавливается в нуль для C, Basic и FORTRAN).
Формат команды: ENTER immed16, immed8

HLT
O D I S Z A P C

Останов (HaLT). Останавливает процессор до тех пор, пока не произойдет аппаратное


прерывание. (Замечание: флаг прерывания должен быть установлен командой STI для
того, чтобы могло произойти аппаратное прерывание.)
Формат команды: HLT

IDIV
O D I S Z A P C
? ? ? ? ? ?

Деление целых чисел со знаком (signed Integer DIVision). Выполняет деление целых чисел
со знаком в регистрах EDX:EAX, DX:AX или AX. Если делитель имеет 8 разрядов, делимое
помещается в регистр AX, частное &&&& в AL и остаток &&&& в AH. Если делитель имеет 16 раз&
рядов, делимое помещается в регистры DX:AX, частное &&&& в AX и остаток &&&& в DX. Если
делитель имеет 32 разряда, делимое помещается в регистры EDX:EAX, частное &&&& в EAX и
остаток &&&& в EDX. Обычно операции IDIV предшествуют команды CBW или CWD для пре&
образования делимого.
Формат команды:
IDIV reg IDIV mem

IMUL
O D I S Z A P C
* ? ? ? ? *

Умножение целых чисел со знаком (signed Integer MULtiply). Выполняет умножение це&
лых чисел со знаком в регистрах AL или AX. Если множитель имеет 8 разрядов, то второй
сомножитель помещается в регистр AL, а результат &&&& в регистр AX. Если множитель име&
ет 16 разрядов, то второй сомножитель помещается в регистр AX, а результат &&&& в регистр
DX:AX. Если множитель имеет 32 разряда, то второй сомножитель помещается в регистр EAX,
а результат &&&& в регистр EDX:EAX. Флаги переноса и переполнения устанавливаются в слу&
чаях, когда: 16&разрядный результат производит расширение знака в регистр AH, 32&раз&
рядный результат выполняет расширение знака в регистр DX, 64&разрядный результат
производит расширение знака в регистр EDX.

282 Глава 12. Справочный раздел

Стр. 282
отсутствует. 283

Формат команды:
IMUL reg/mem8 IMUL reg/mem16
IMUL reg/mem32
IMUL reg16,reg/mem16 IMUL reg16,imm8
IMUL reg32,reg/mem32 IMUL reg32,imm8
IMUL reg16,imm16 IMUL reg32,imm32
IMUL reg16,reg/mem16,imm8 IMUL reg16,reg/mem16,imm16
IMUL reg32,reg/mem32,imm8 IMUL reg32,reg/mem32,imm32

IN
O D I S Z A P C

Ввод из порта (INput from port). Вводит байт или слово из порта в регистр AL или AX. Опе&
рандом является адрес порта, выраженный или 8&разрядной константой, или 16&разряд&
ным адресом в регистре DX. Для процессора 80386 из порта можно ввести двойное слово.
Формат команды:
IN accum, imm IN accum, DX

INC
O D I S Z A P C
* * * * *

Инкремент (INCrement). Добавляет 1 в регистр или операнд памяти. Не изменяет флаг


переноса.
Формат команды:
INC reg INC mem

INS, INSB, INSW, INSD


O D I S Z A P C

Ввод строк (INput from port to string). (80286) Вводит строку из порта по адресу, указан&
ному в регистрах ES:DI. Номер порта определяется в регистре DX. Для каждого вводи&
мого значения происходит коррекция, как и в команде LODSB при работе со строковыми
примитивами. С этой командой можно использовать префикс REP.
Формат команды:
INS dest, DX REP INSB dest, DX
REP INSW dest, DX REP INSD dest, DX

Глава 12. Справочный раздел 283

Стр. 283
INT
O D I S Z A P C
0

Прерывание (INTerrupt). Генерирует программное прерывание, которое вызывает


процедуру операционной системы. Сбрасывает флаг прерывания и помещает флаги, ре&
гистры CS и IP в стек перед переходом к подпрограмме.
Формат команды:
INT imm INT 3

INTO
O D I S Z A P C
* *

Прерывание при переполнении (INTerrupt on Overflow). Генерирует прерывание процес&


сора INT4, если установлен флаг переполнения. Подпрограмма обработки может быть
написана пользователем.
Формат команды: INTO

IRET
O D I S Z A P C
* * * * * * * *

Возврат из прерывания (Interrupt RETurn). Возврат из подпрограммы, вызванной по


прерыванию. Извлекает из стека значения регистров IP, CS и состояния флагов.
Формат команды: IRET

Jcondition
Переход, если условие выполнено (Jump CONDITIONal). Переход на метку, если установ&
лен флаг условия, определенный кодом операции. В процессорах более ранних, чем
80386, метка должна была находиться в диапазоне от &&128 до +127 байт от текущего по&
ложения. В процессорах 80386 и более поздних моделях диапазон составляет от &&32 768
до +32 767 байт. В табл. 11.2 приводится полный список мнемокодов для данной команды.
Формат команды: Jcondition label

Таблица 12.2. Мнемокоды для переходов по условию


Мнемокод Комментарий Мнемокод Комментарий
JA Если выше JE Если равно
JNA Если не выше JNE Если не равно
JAE Если выше или равно JZ Если нуль
JNAE Если не выше или равно JNZ Если не нуль
JB Если ниже JS Если знак ‘‘минус’’
JNB Если не ниже JNS Если знак ‘‘плюс’’
JBE Если не ниже или равно JC Если перенос

284 Глава 12. Справочный раздел

Стр. 284
отсутствует. 285

Окончание табл. 12.2


Мнемокод Комментарий Мнемокод Комментарий
JNBE Если не ниже или равно JNC Если нет переноса
JG Если больше JO Если переполнение
JNG Если не больше JNO Если нет переполнения
JGE Если больше или равно JP Если количество единичных битов четно
JNGE Если не больше или равно JPE Если количество единичных битов четно
JL Если меньше JNP Если количество единичных битов нечетно
JNL Если не меньше JPO Если количество единичных битов нечетно
JLE Если меньше или равно JNLE Если не меньше или равно

JCXZ, JECXZ
O D I S Z A P C

Переход, если CX=0 (Jump if CX is Zero). Переход на ближнюю метку, если значение в ре&
гистре CX равно нулю. Ближняя метка должна быть в диапазоне от &&128 до +127 байт от те&
кущего адреса. Для процессора 80386 по команде JECXZ совершается переход, если ECX=0.
Формат команды:
JCXZ label JECXZ label

JMP
O D I S Z A P C

Переход безусловный (JuMP unconditionally to a label). Метка может находиться в таких


зонах: короткой (от &&128 до +128 байт), ближней (текущий сегмент) или дальней (другой
сегмент).
Формат команды:
JMP shortlabel JMP reg16
JMP nearlabel JMP mem16
JMP farlabel JMP mem32

LAHF
O D I S Z A P C

Загрузка флагов в AH (Load AH from Flags). Копирует младшие 8 бит регистра флагов
в регистр AH. Флаги из старших битов не копируются (это флаги трассировки, прерыва&
ния, переполнения, направления и знака).
Формат команды: LAHF

Глава 12. Справочный раздел 285

Стр. 285
LDS, LES, LFS, LGS, LSS
O D I S Z A P C

Загрузка дальнего указателя (Load far pointer). Загружает операнд памяти (двойное слово)
в регистр сегмента и в регистр&получатель. В моделях процессоров, более младших чем
80386, команда LDS производит загрузку в регистр DS, а команда LES &&&& в регистр ES. Для
процессора 80386 команда LFS производит загрузку в регистр FS, команда LGS &&&& в GS
и команда LSS &&&& в регистр SS.
Формат команды (для LDS, LES, LFS, LGS, LSS): LDS reg, mem

LEA
O D I S Z A P C

Получение эффективного адреса (Load Effective Address). Рассчитывает и загружает 16& или
32&разрядный эффективный адрес операнда памяти. Действует подобно MOV OFFSET, за
исключением того, что только команда LEA получает адрес, рассчитанный во время вы&
полнения.
Формат команды: LEA reg,mem

LEAVE
O D I S Z A P C

Выход из процедуры (high+level procedure exit). Удаляет кадр стека процедуры, создан&
ный командой ENTER. Восстанавливает указатели (E)SP и (E)BP.
Формат команды: LEAVE

LOCK
O D I S Z A P C

Блокировка системной шины (LOCK the system bus). Блокирует системную шину на
время выполнения некоторых команд. Эта команда необходима, когда другой процессор
может модифицировать операнды памяти во время одновременной работы с ними ос&
новного процессора.
Формат команды: LOCK instruction

LODS, LODSB, LODSW, LODSD


O D I S Z A P C

Загрузка строки в аккумулятор (LOaD accumulator from String). Загружает байт памяти
или слово памяти по адресу, указанному в регистрах DS:SI, а также в аккумулятор (AL, AX
или EAX). Если используется команда LODS, то операнд памяти должен быть определен.
LODSB загружает байт в AL, LODSW загружает слово в AX и LODSD (для процессора 80386)

286 Глава 12. Справочный раздел

Стр. 286
отсутствует. 287

загружает двойное слово в EAX. Регистр SI инкрементируется или декрементируется


в соответствии с размерами операндов и состоянием флага направления. Если DF = 1, SI
декрементируется; если DF = 0, SI инкрементируется.
Формат команды:
LODS mem LODSB
LODS segreg, mem LODSW
LODSD

LOOP, LOOPW
O D I S Z A P C

Цикл (Loop). Декрементирует регистр (E)CX и совершает переход на короткую метку,


пока (E)CX больше нуля. Размер перехода не должен превышать зону от &&128 до +127
байт от текущего положения. Для 32&разрядных процессоров ECX используется как счет&
чик по умолчанию.
Формат команды:
LOOP shortlabel LOOPW shortlabel

LOOP
Цикл (LOOP).(80386) Декрементирует регистр ECX и совершает переход на короткую
метку, если ECX больше нуля. Размер перехода не должен превышать зону от &&128 до
+127 байт от текущего положения.
Формат команды: LOOP shortlabel

LOOPE, LOOPZ
O D I S Z A P C

Цикл, если равно нулю (LOOP if Equal (Zero)). Декрементирует регистр (E)CX и совер&
шает переход на короткую метку, если (E)CX больше нуля и флаг нуля установлен. Раз&
мер перехода не должен превышать зону от &&128 до +127 байт от текущего положения.
Формат команды:
LOOPE shortlabel LOOPZ shortlabel

LOOPNE, LOOPNZ
O D I S Z A P C

Цикл, если не равно нулю (LOOP if Not Equal (Zero)). Декрементирует регистр (E)CX
и совершает переход на короткую метку, если (E)CX больше нуля и флаг нуля установлен.
Формат команды:
LOOPNE shortlabel LOOPNZ shortlabel

Глава 12. Справочный раздел 287

Стр. 287
MOV
O D I S Z A P C

Пересылка операндов (MOVe). Копирует байт или слово из операнда&отправителя


в операнд&получатель.
Формат команды:
MOV reg, reg MOV reg, imm
MOV mem, reg MOV mem, imm
MOV reg, mem MOV mem16, segreg
MOV reg16, segreg MOV segreg, mem16
MOV segreg, reg16

MOVS, MOVSB, MOVSW, MOVSD


O D I S Z A P C

Пересылка строк (MOVe String). Копирует байт или слово из памяти, адрес которой
находится в регистрах DS:(E)SI, в память с адресом в регистрах ES:(E)DI. Команда
MOVS требует определения обоих операндов, команда MOVSB копирует один байт, коман&
да MOVSW копирует слово, а команда MOVSD (для процессоров 80386) копирует двойное
слово. Регистры (E)SI и (E)DI инкрементируются или декрементируются в зависимо&
сти от состояния флага направления. Если DF=1, регистры (E)SI и (E)DI декременти&
руются, если DF=0, (E)SI и (E)DI инкрементируются.
Формат команды:
MOVS dest, source MOVSB
MOVS ES:dest, seqreg:source MOVSW
MOVSD

MOVSX
O D I S Z A P C

Пересылка с нулевым расширением (MOVe with Sign+eXtend). Копирует байт или слово
из операнда&отправителя в регистр&получатель и заполняет значением знакового бита
старшую половину регистра&получателя. Эта команда используется для преобразования
8& или 16&разрядных чисел в числа большей разрядности.
Формат команды:
MOVZX reg32, reg16 MOV reg32, mem16
MOVZX reg16, reg8 MOV reg16, mem8

MOVZX
O D I S Z A P C

288 Глава 12. Справочный раздел

Стр. 288
отсутствует. 289

Пересылка с нулевым расширением (Move with zero+extend). Копирует байт или слово из
операнда&отправителя в регистр&получатель и заполняет нулями старшую половину ре&
гистра&получателя. Эта команда используется для преобразования 8& или 16&разрядных
чисел в числа большей разрядности.
Формат команды:
MOVZX reg32, reg16 MOV reg32, mem16
MOVZX reg16, reg8 MOV reg16, mem8

MUL
O D I S Z A P C
* ? ? ? ? *

Умножение целых чисел без знака (unsigned integer MULtiply). Перемножает значение
в регистре AL, AX или EAX со значением исходного операнда. Если исходное значение имеет
8 разрядов, то оно перемножается со значением в регистре AL и результат сохраняется
в регистре AX. Если исходное значение имеет 16 разрядов, то оно перемножается со зна&
чением в регистре AX, а результат сохраняется в регистрах DX:AX. Если исходное значе&
ние имеет 32 разряда, то оно перемножается со значением в регистре EAX и результат со&
храняется в регистре EDX:EAX.
Формат команды: MUL reg MUL mem

NEG
O D I S Z A P C
* * * * * *

Изменить знак операнда (NEGate). Рассчитывает двоичное дополнение операнда&по&


лучателя и сохраняет результат в операнде&получателе.
Формат команды: NEG reg NEG mem

NOP
O D I S Z A P C

Нет операции (NO oPeration). Эта команда не производит никаких операций. Ее ис&
пользуют во временных циклах или для выравнивания команд по границе слова.
Формат команды: NOP

NOT
O D I S Z A P C

Логическая операция НЕ (NOT). Выполняет логическую команду НЕ для операнда,


т.е. инвертирует каждый бит.
Формат команды: NOT reg NOT mem

Глава 12. Справочный раздел 289

Стр. 289
OR
O D I S Z A P C
0 * * ? * 0

Логическая операция включающее ИЛИ (inclusive OR). Выполняет логическую команду


ИЛИ для соответствующих битов операнда&отправителя и операнда&получателя. Если
оба бита равны 1, то результирующий бит равен 1.
Формат команды:
OR reg, reg OR reg, immed
OR mem, reg OR mem, immed
OR reg, mem OR accum, immed

OUT
O D I S Z A P C

Вывод в порт (OUTput to port). Для более ранних моделей процессоров, чем 80386,
с помощью этой команды выполнялся вывод байта или слова в порт из аккумулятора.
Адрес порта может указываться с помощью константы с диапазоном от 0 до FFh, или на&
ходиться в регистре DX с диапазоном от 0 до FFFFh. В более поздних моделях можно
производить вывод двойного слова.
Формат команды:
OUT imm8, accum OUT DX, accum

OUTS, OUTSB, OUTSW, OUTSD


O D I S Z A P C

Вывод строки в порт (OUTput String to port). Выводит в порт строку, адрес которой ука&
зан в регистрах ES:(E)DI. Номер порта определяется в регистре DX. Для каждого значе&
ния регистр (E)DI корректируется таким же образом, как это делается в команде LODSB
и для команд строковых примитивов. С этой командой может использоваться префикс REP.
Формат команды:
OUTS dest, DX REP OUTSB dest, DX
REP OUTSW dest, DX REP OUTSD dest, DX

POP
O D I S Z A P C

Извлечение операнда из стека (POP from stack). Копирует слово или двойное слово из
стека в операнд&получатель. Добавляет 2 или 4 к значению указателя стека (регистр (E)SP).

290 Глава 12. Справочный раздел

Стр. 290
отсутствует. 291

Формат команды:
POP reg16/reg32 POP segreg
POP mem16/mem32

POPA, POPAD
O D I S Z A P C

Извлечение из стека регистров общего назначения (POP All). Извлекает 16 байт из стека
в восемь регистров общего назначения в следующем порядке: DI, SI, BP, SP, BX, DX, CX, AX.
Значение указателя стека не восстанавливается. Команда POPA производит извлечение в
16&разрядные регистры, а POPAD (для процессоров 80386) &&&& в 32&разрядные регистры.
Формат команды: POPA POPAD

POPF, POPFD
O D I S Z A P C
* * * * * * * *

Извлечь флаги из стека (POP Flags from stack). Команда POPF извлекает из стека значе&
ния в 16&разрядный регистр флагов, а команда POPFD для процессора 80386 производит
извлечение в 32&разрядный регистр флагов.
Формат команды: POPF POPFD

PUSH
O D I S Z A P C

Поместить в стек (PUSH on stack). Вычитает 2 из указателя стека и копирует исход&


ный операнд в стек по адресу, указанному в указателе этого стека (регистр (E)SP). Для
процессоров 80186 в стек можно поместить непосредственное значение.
Формат команды:
PUSH reg16/reg32 PUSH segreg
PUSH mem16/mem32 PUSH immed16/immed32

PUSHA, PUSHAD
O D I S Z A P C

Поместить в стек все (PUSH All). Команда PUSHA (для процессора 80186) помещает в
стек 16&разрядные регистры общего назначения в следующем порядке: AX, CX, DX, BX,
SP, BP, SI и DI. А команда PUSHAD (для процессоров 80386) помещает регистры EAX,
ECX, EDX, EBX, ESP, EBP, ESI и EDI.
Формат команды: PUSHA PUSHAD

Глава 12. Справочный раздел 291

Стр. 291
PUSHF, PUSHFD
O D I S Z A P C

Помещение регистра флагов в стек (PUSH Flags). Команда PUSHF помещает 16&разряд&
ный регистр флагов в стек. Команда PUSHFD для процессоров 80386 помещает 32&раз&
рядный регистр флагов в стек.
Формат команды: PUSHF PUSHFD

PUSHW, PUSHD
O D I S Z A P C

Поместить в стек (PUSH on stack). Команда PUSHW помещает 16&разрядное слово


в стек, команда PUSHD для процессора 80386 помещает 32&разрядное двойное слово в стек.
Формат команды:
PUSH reg16/reg32 PUSH segreg
PUSH mem16/mem32 PUSH imm16/imm32

RCL
O D I S Z A P C
* *

Циклический сдвиг операнда влево через флаг переноса (Rotate Carry Left). Производит
циклический сдвиг операнда&получателя влево. Количество циклов задается в операнде&
отправителе. Флаг переноса копируется в младший бит, а старший бит копируется во
флаг переноса. Для процессоров 8086/8088 операнд imm8 может быть установлен в 1.
Формат команды:
RCL reg, imm8 RCL mem, imm8
RCL reg, CL RCL mem, CL

RCR
O D I S Z A P C
* *

Циклический сдвиг операнда вправо через флаг переноса (Rotate Carry Right). Производит
циклический сдвиг операнда&получателя вправо. Количество циклов задается в операн&
де&отправителе. Флаг переноса копируется в младший бит, а старший бит копируется во
флаг переноса. Для процессоров 8086/8088 операнд imm8 может быть установлен в 1.
Формат команды:
RCR reg, imm8 RCR mem, imm8
RCR reg, CL RCR mem, CL

292 Глава 12. Справочный раздел

Стр. 292
отсутствует. 293

REP
O D I S Z A P C

Повторить команду для строкового примитива (Repeat string). Повторяет команду для
строкового примитива, используя регистр (E)CX в качестве счетчика. Значение (E)CX
декрементируется при каждом повторении команды, пока не станет равным нулю.
Формат команды (с MOVS): REP MOVS получатель, отправитель

REPcondition
O D I S Z A P C
*

Повторять команды для строковых примитивов по условию (REPeat string CONDITIONally).


Повторяет команды для строковых примитивов, пока значение в регистре (E)CX не рав&
но нулю и пока флаг условия имеет значение TRUE. REPZ (REPE) выполняют повторение,
пока флаг нуля установлен, а REPZ (REPNE) выполняют повторение, пока флаг нуля
сброшен. Только команды SCAS и CMPS могут использоваться в REPcondition, так как
только они могут изменять флаг нуля.
Формат команды (с SCAS):
REPZ SCAS dest REPNE SCAS dest
REPZ SCASB REPNE SCASB
REPE SCASW REPNZ SCASW

RET, RETN, RETF


O D I S Z A P C

Возврат из процедуры (RETurn from procedure). Извлекает из стека адрес возврата из


процедуры. Команда RETN (ближний возврат) извлекает только одно значение из стека
в регистр (E)IP. Команда RETF (дальний возврат) помещает одно значение в регистр IP
и второе значение &&&& в регистр CS. Команда RET может делать или ближний, или дальний
возврат в зависимости от установленных атрибутов или подразумеваемых значений ди&
рективы PROC. Дополнительный 8&разрядный операнд определяет значение, которое
должен добавить процессор в регистр (E)SP после извлечения адреса.
Формат команды:
RET RET imm8
RETN RETN imm8
RETF RETF imm8

ROL
O D I S Z A P C
* *

Глава 12. Справочный раздел 293

Стр. 293
Циклический сдвиг влево (ROtate Left). Производит циклический сдвиг операнда&
получателя влево. Количество циклов задается в операнде&отправителе. Старший бит ко&
пируется во флаг переноса и перемещается в младший бит. Для процессоров 8086/8088
операнд imm8 может быть установлен в 1.
Формат команды:
ROL reg, imm8 ROL mem, imm8
ROL reg, CL ROL mem, CL

ROR
O D I S Z A P C
* *

Циклический сдвиг вправо (ROtate Right). Производит циклический сдвиг операнда&


получателя вправо. Количество циклов задается в операнде&отправителе. Младший бит
копируется во флаг переноса и перемещается в старший бит. Для процессоров 8086/8088
операнд imm8 может быть установлен в 1.
Формат команды:
ROR reg, imm8 ROR mem, imm8
ROR reg, CL ROR mem, CL

SAHF
O D I S Z A P C
* * * * *

Загрузка регистра флагов из регистра AH (Store AH into Flags). Копирует биты от 0 до 7


из регистра AH в регистр флагов. Флаги трассировки, прерывания, направления и пере&
полнения не изменяются.
Формат команды: SAHF

SAL
O D I S Z A P C
* * * ? * *

Арифметический сдвиг влево (Shift Arithmetic Left). Производит сдвиг влево каждого би&
та операнда&получателя. Количество циклов задается в операнде&отправителе. Старший
бит копируется во флаг переноса, а младшие биты заполняются нулями. Для процессо&
ров 8086/8088 операнд imm8 может быть установлен в 1.
Формат команды:
SAL reg, imm8 SAL mem, imm8
SAL reg, CL SAL mem, CL

SAR
O D I S Z A P C
* * * ? * *

294 Глава 12. Справочный раздел

Стр. 294
отсутствует. 295

Арифметический сдвиг вправо (Shift Arithmetic Right). Производит сдвиг вправо каждого
бита операндаполучателя. Количество циклов задается в операндеотправителе. Млад
ший бит копируется во флаг переноса, а старшие биты не изменяются. Этот сдвиг часто
используют при работе с операндами со знаком, чтобы сохранить знаковый бит. Для
процессоров 8086/8088 операнд imm8 может быть установлен в 1.
Формат команды:
SAR reg, imm8 SAR mem, imm8
SAR reg, CL SAR mem, CL

SBB
O D I S Z A P C
* * * * * *

Вычитание с заемом (SuBtract with Borrow). Вычитает операндотправитель из операн


даполучателя и затем вычитает флаг переноса из операндаполучателя.
Формат команды:
SBB reg, reg SBB reg, imm
SBB mem, reg SBB mem, imm
SBB reg, mem

SCAS, SCASB, SCASW, SCASD


O D I S Z A P C
* * * * * *

Сканирование строки (SCan String). Сканирует строку, адрес которой находится в ре
гистрах ES:(E)DI, сравнивая значения со значением в аккумуляторе. Команда SCAS
требует, чтобы операнд был определен. SCASB выполняет сканирование 8разрядных
значений, сравнивая со значениями в AL. Команда SCASW сканирует 16разрядные зна
чения, сравнивая со значениями в AХ, и SCASD производит сканирование 32разрядных
значений, сравнивая со значениями в EAХ. Регистр (E)DI инкрементируется или декре
ментируется в соответствии с размерами операнда и состоянием флага направления. Ес
ли DF=1, (E)DI декрементируется; если DF=0, (E)DI инкрементируется.
Формат команды:
SCAS dest SCASB
SCAS ES:dest SCASW

SETcondition
O D I S Z A P C

Установка по условию (SET CONDITIONally). Если заданное условие истинно, то байту


в операндеполучателе присваивается значение 1. Если условие ложно, то присваивается
значение 0. Возможные значения для условия приведены в таблице, расположенной выше.
Формат команды:
SETcond reg8 SETcond mem8

Глава 12. Справочный раздел 295

Стр. 295
SHL
O D I S Z A P C
* * * ? * *

Логический сдвиг влево (SHift Left). Производит сдвиг влево каждого бита операнда&
получателя. Количество циклов задается в операнде&отправителе. Старший бит копиру&
ется во флаг переноса, а младшие биты заполняются нулями (подобно SAL). Для процес&
соров 8086/8088 операнд imm8 может быть установлен в 1.
Формат команды:
SHL reg, imm8 SHL mem, imm8
SHL reg, CL SHL mem, CL

SHLD
O D I S Z A P C
* * * ? * *

Сдвиг влево двойной точности (SHift Left Double+precision). Помещает биты второго
операнда в первый операнд. Третий операнд определяет количество сдвигаемых битов.
Освобождаемые позиции заполняются старшими битами второго операнда. Второй опе&
ранд должен быть регистром, а третий может быть или непосредственным значением,
или регистром CL.
Формат команды:
SHLD reg16, reg16, imm8 SHLD mem16, reg16,imm8
SHLD reg32, reg32, imm8 SHLD mem32, reg32, imm8
SHLD reg16, reg16, CL SHLD mem16, reg16, CL
SHLD reg32, reg32, CL SHLD mem32, reg32, CL

SHR
O D I S Z A P C
* * * ? * *

Логический сдвиг вправо (SHift Right). Производит сдвиг вправо каждого бита операн&
да&получателя. Количество циклов задается в операнде&отправителе. Младший бит ко&
пируется во флаг переноса, а старшие биты заполняются нулями. Для процессоров
8086/8088 операнд imm8 может быть установлен в 1.
Формат команды:
SHR reg, imm8 SHR mem, imm8
SHR reg, CL SHR mem, CL

SHRD
O D I S Z A P C
? * * ? * *

296 Глава 12. Справочный раздел

Стр. 296
отсутствует. 297

Сдвиг вправо двойной точности (SHift Right Double+precision). (80386) Помещает биты
второго операнда в первый операнд. Третий операнд определяет количество сдвигаемых
битов. Освобождаемые позиции заполняются младшими битами второго операнда. Вто&
рой операнд должен быть регистром, а третий операнд может быть или непосредствен&
ным значением, или регистром CL.
Формат команды:
SHRD reg16, reg16, imm8 SHRD mem16, reg16, imm8
SHRD reg32, reg32, imm8 SHRD mem32, reg32, imm8
SHRD reg16, reg16, CL SHRD mem16, reg16, CL
SHRD reg32, reg32, CL SHRD mem32, reg32, CL

STC
O D I S Z A P C
1

Установить флаг переноса (SeT Carry flag). Устанавливает флаг переноса. Эту команду
используют в вызванной процедуре, чтобы информировать вызывающую программу об
ошибке.
Формат команды: STC

STD
O D I S Z A P C
1

Установить флаг направления (SeT Direction flag). Устанавливает флаг направления,


состояние которого определяет декремент или инкремент регистров (E)SI или (E)DI для
команд строковых примитивов. Просмотр строки будет происходить от старших адресов
к младшим.
Формат команды: STB

STI
O D I S Z A P C
1

Установить флаг прерывания (SeT Interrupt flag). Устанавливает флаг прерывания,


причем разрешаются маскируемые прерывания. Все прерывания автоматически запре&
щаются в начале обработки отдельного прерывания, поэтому в процедуре обработки
прерывания их необходимо немедленно разрешить с помощью команды STI.
Формат команды: STI

STOS, STOSB, STOSW, STOSD


O D I S Z A P C

Сохранение строки данных (STOre String data). Сохраняет содержимое аккумулятора


в памяти по адресу, указанному в регистрах ES:(E)DI. Если используется команда STOS,

Глава 12. Справочный раздел 297

Стр. 297
то операнд&получатель должен быть определен. Команда STOSB копирует в память ре&
гистр AL, STOSW копирует AX и STOSD (для процессора 80386) копирует EAX.
Формат команды:
STOS mem STOSB
STOS ES:mem STOSW

SUB
O D I S Z A P C

Вычитание (SUBtract). Вычитает операнд&отправитель из операнда&получателя.


Формат команды:
SUB reg, reg SUB reg, imm
SUB mem, reg SUB mem, imm
SUB reg, mem SUB accum, imm

TEST
O D I S Z A P C
* * * ? * 0

Проверка (TEST). Проверяет отдельные биты операнда&получателя с соответствую&


щими битами операнда&отправителя. Выполняет команду логическое И, результат кото&
рой сказывается только на состоянии флагов.
Формат команды:
TEST reg, reg TEST reg, imm
TEST mem, reg TEST mem, imm
TEST reg, mem TEST accum, imm

WAIT
O D I S Z A P C

Ожидание сопроцессора (WAIt for coprocessor). Приостанавливает работу процессора,


пока сопроцессор не закончит выполнение очередной команды.
Формат команды: WAIT

XADD
O D I S Z A P C
* * * * * *

Обмен и сложение (eXchange and ADD). (80486) Складывает операнд&отправитель с


операндом&получателем. В то же время оригинальное значение операнда&получателя пе&
ремещается в операнд&отправитель.
Формат команды:
XADD reg, reg XADD mem, reg

298 Глава 12. Справочный раздел

Стр. 298
отсутствует. 299

XCHG
O D I S Z A P C

Обмен (ExCHanGe). Обменивает содержимое операнда&отправителя и операнда&


получателя.
Формат команды:
XCH reg, reg XCH mem, reg
XCH reg, mem

XLAT, XLATB
O D I S Z A P C

Преобразования байтов (transLATe byte). Использует значение в регистре AL как ин&


декс для таблицы, на которую указывает значение в регистрах DS:BX. Байт, на который
указывает индекс, копируется в регистр AL. Операнд может быть определен с подменой
сегмента. Команда XLATB может быть заменена командой XLAT.
Формат команды:
XLAT XLAT segreg:mem
XLAT mem XLATB

XOR
O D I S Z A P C
* * * ? * 0

Операция логическое исключающее ИЛИ (eXclusive OR). Над каждым битом операнда&
отправителя и соответствующим битом операнда&получателя производится операция
логическое исключающее ИЛИ. Бит в операнде&получателе будет иметь значение 1 только
в том случае, если соответствующие биты операнда&отправителя и операнда&получателя
имеют различные значения.
Формат команды:
XOR reg, reg XOR reg, imm
XOR mem, reg XOR mem, imm
XOR reg, mem XOR accum, imm

Библиотека функций для работы в 32*разрядном


режиме
Title Исходный код Учебной библиотеки (StudyLib32.asm)
Comment @
Список процедур
----------------------------------
ClrScr

Глава 12. Справочный раздел 299

Стр. 299
Crlf
Delay
DumpMem
DumpRegs
GetCommandtail
GetDateTime
GetMaxX
GetMseconds
Gotoxy
IsDigit
Random32
Randomize
RandomRange
ReadChar
ReadDec
ReadHex
ReadInt
ReadKey
ReadKeyFlush
ReadKeyTranslate
ReadString
SetTextColor
Str_compare
Str_copy
Str_length
Str_trim
Str_ucase
WaitMsg
WriteBin
WriteChar
WriteDec
WriteHex
WriteHexB
WriteInt
WriteString

Замечания по реализации:
1. Функция Windows Sleep изменяет содержимое регистра ECX.
2. Не забывайте сохранять и восстанавливать все 32-разрядные регистры
общего назначения (кроме EAX) перед вызовом функций MS-Windows API.
Конец коментария----------------------------------------------------@
;OPTION CASEMAP:NONE ; Чувствительность к регистру.

INCLUDE Study32.inc ; Прототипы для библиотечных функций.


INCLUDE Macros.inc ; Макроопределения.

;-----------------------------------------------------------------
ShowFlag MACRO flagName,shiftCount
LOCAL flagStr, flagVal, L1
; Вспомогательное макроопределение.
; Отображает значения флагов CPU
; Непосредственный доступ к eflags в Study32.asm
;---------------------------------------------------------------------
.data
flagStr DB " &flagName="
flagVal DB ?,0

.code

300 Глава 12. Справочный раздел

Стр. 300
отсутствует. 301

push eax
push edx
mov eax,eflags ; Извлечение флагов.
mov flagVal,'1'
shr eax,shiftCount ; Смещение во флаг переноса.
jc L1
mov flagVal,'0'
L1:
mov edx,OFFSET flagStr ; Отображение имени флага и значение.
call WriteString
pop edx
pop eax
ENDM

;-------------------------------------------------------------
CheckInit MACRO
; Вспомогательное макроопределение.
; Проверяет инициализацию дескрипторов консоли. При необходимости
; вызывает функцию инициализации.
;-------------------------------------------------------------
LOCAL exit
cmp InitFlag,0
jne exit
call Initialize
exit:
ENDM

;********************* Данные **********************


MAX_DIGITS = 80

.DATA ; инициализированные данные


InitFlag DB 0 ; флаг инициализации
xtable BYTE "0123456789ABCDEF"

;.data? ; неинициализированные данные


consoleInHandle DWORD ? ; дескриптор стандартного входа
consoleOutHandle DWORD ? ; дескриптор стандартного выхода
bytesWritten DWORD ? ; число записанных байт
eflags DWORD ?
digitBuffer BYTE MAX_DIGITS DUP(?),?

buffer DB 512 DUP(?)


bufferMax = ($ - buffer)
bytesRead DD ?
sysTime SYSTEMTIME <> ; структура системного времени

;********************** Код ************************


.CODE
Clrscr PROC
LOCAL bufInfo:CONSOLE_SCREEN_BUFFER_INFO
; Очистка экрана с помощью записи пробелов во все позиции
; Принимает: ничего
; Возвращает: ничего
; Ограничения: только первые 512 символов каждой строки очищаются.
;-------------------------------------------------------------
MAX_COLS = 512
.data
blanks BYTE MAX_COLS DUP(' ') ; Одна строка экрана.

Глава 12. Справочный раздел 301

Стр. 301
attribs WORD MAX_COLS DUP(0)
lineLength DWORD 0
cursorLoc COORD <0,0>
count DWORD ?
.code
pushad
CheckInit

; Получить размер буфера консоли и аттрибуты


INVOKE GetConsoleScreenBufferInfo, consoleOutHandle, ADDR bufInfo
mov ax,bufInfo.dwSize.X;
mov WORD PTR lineLength,ax
.IF lineLength > MAX_COLS
mov lineLength,MAX_COLS
.ENDIF

; Заполнить массив атрибутов.


mov ax,bufInfo.wAttributes
mov ecx,lineLength
mov edi,OFFSET attribs
rep stosw
movzx ecx,bufInfo.dwSize.Y ; счетчик: число строк
L1: push ecx

; Запись пробелов в экранный буфер


INVOKE WriteConsoleOutputCharacter,
consoleOutHandle,
ADDR blanks, ; указывает на буфер
lineLength, ; число пробелов для записи
cursorLoc, ; координаты первой ячейки
ADDR count ; выходной счетчик

; Заполнение всех позиций буфера текущими атрибутами


INVOKE WriteConsoleOutputAttribute,
consoleOutHandle,
ADDR attribs, ; указывает на массив атрибутов
lineLength, ; число атрибутов для записи
cursorLoc, ; координаты первой ячейки
ADDR count ; выходной счетчик

add cursorLoc.Y, 1 ; указывает на следующую строку буфера


pop ecx
Loop L1

; Переместить курсор в 0,0


mov cursorLoc.Y,0
INVOKE SetConsoleCursorPosition, consoleOutHandle, cursorLoc
popad
ret
Clrscr ENDP

;-----------------------------------------------------
Crlf PROC
; Записывает последовательность символов возврата
; каретки (0Dh,0Ah) в стандартный выход.
;-----------------------------------------------------
CheckInit ; Проверка инициализации.
mNewLine ; вызов макроопределения.

302 Глава 12. Справочный раздел

Стр. 302
отсутствует. 303

ret
Crlf ENDP

;------------------------------------------------------
Delay PROC
; Задержка текущего процесса на заданное число миллисекунд
; Принимает: EAX = число миллисекунд
; Возарвщает: ничего
;------------------------------------------------------
pushad
INVOKE Sleep,eax
popad
ret
Delay ENDP

;---------------------------------------------------
DumpMem PROC
LOCAL unitsize:dword, byteCount:word
; Записывает участок памяти в стандартный выход в
; шестнадцатеричном виде.
; Принимает: ESI = начальное смещение, ECX = число модулей,
; EBX = размер модуля (1=байт, 2=слово, 4=двойное слово)
; Возвращает: ничего
;---------------------------------------------------
.data
oneSpace DB ' ',0

dumpPrompt DB 13,10,"Dump of offset ",0


dashLine DB "-------------------------------",13,10,0

.code
pushad
mov edx,OFFSET dumpPrompt
call WriteString
mov eax,esi ; Получить смещение.
call WriteHex
mNewLine
mov edx,OFFSET dashLine
call WriteString
mov byteCount,0
mov unitsize,ebx
cmp ebx,4 ; Формат вывода.
je L1
cmp ebx,2
je L2
jmp L3

; Вывод двойного слова.


L1:
mov eax,[esi]
call WriteHex
mWriteSpace 2
add esi,ebx
Loop L1
jmp L4

; Вывод слова.
L2:

Глава 12. Справочный раздел 303

Стр. 303
mov ax,[esi] ; Получить слово из памяти.
ror ax,8 ; Отобразить старший байт.
call HexByte
ror ax,8 ; Отобразить младший байт.
call HexByte
mWriteSpace 1 ; Отобразить пробел.
add esi,unitsize ; Указать на следующее слово.
Loop L2
jmp L4

; Вывод побайтно, 16 байт на строку.


L3:
mov al,[esi]
call HexByte
inc byteCount
mWriteSpace 1
inc esi

; Если( byteCount mod 16 == 0 ) вызывается Crlf.


mov dx,0
mov ax,byteCount
mov bx,16
div bx
cmp dx,0
jne L3B
mNewLine
L3B:
Loop L3
jmp L4
L4:
mNewLine
popad
ret
DumpMem ENDP

;---------------------------------------------------
DumpRegs PROC
; Отображает EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP в
; шестнадцатеричном виде. Также отображает флаги Zero,
; Sign,Carry и Overflow.
; Принимает: ничего.
; Возвращает: ничего.
;---------------------------------------------------
.data
saveIP DWORD ?
saveESP DWORD ?
.code
pop saveIP ; Получить EIP
mov saveESP,esp ; Сохранить значение ESP's value at entry
push saveIP ; replace it on stack
push eax ; Сохранить EAX (восстановление выхода)

pushfd ; Поместить в стек флаги.


pushfd ; Поместить в стек флаги еще раз и
pop eflags ; сохранить их в переменной.

mNewLine
mShowRegister EAX,EAX

304 Глава 12. Справочный раздел

Стр. 304
отсутствует. 305

mShowRegister EBX,EBX
mShowRegister ECX,ECX
mShowRegister EDX,EDX
mNewLine
mShowRegister ESI,ESI
mShowRegister EDI,EDI
mShowRegister EBP,EBP

mov eax,saveESP
mShowRegister ESP,EAX
mNewLine

mov eax,saveIP
mShowRegister EIP,EAX
mov eax,eflags
mShowRegister EFL,EAX

; Отобразить флаги с помощью переменной eflags.


ShowFlag CF,1
ShowFlag SF,8
ShowFlag ZF,7
ShowFlag OF,12

mNewLine
mNewLine

popfd
pop eax
ret
DumpRegs ENDP

;------------------------------------------------------------
GetCommandtail PROC
; Копирует аргументы командной строки в буфер
; (после первого аргумента - имени программы)
; Принимает: EDX указывает на 129-байт буфер для приема данных.
; Возвращает: Флаг Carry = 1 если нет аргументов, иначе CF=0
;-------------------------------------------------------------
pushad
INVOKE GetCommandLine ; Функция Win32 API
; возвращает указатель в EAX.
; Копирует командную строку в массив,
; на который указывает EDX (пропускается имя_файла.exe).

mov esi,eax
L0: mov al,[esi] ; Пропуск первого аргумента.
inc esi
cmp al,' ' ; Наличие пробела.
jne L0
mov edi,edx ; Сохранение адреса буфера.
L1: mov al,[esi]
mov [edx],al
inc esi
inc edx
cmp al,0 ; Найдет нулевой байт?
jne L1 ; нет, очередная итерация.
clc ; Предполагает наличие аргументов.
cmp BYTE PTR [edi],0 ; Буфер начинается с нуля?

Глава 12. Справочный раздел 305

Стр. 305
jne L2
stc ; Да, устанавливаем флаг переноса.
L2: popad
ret
GetCommandtail ENDP

;--------------------------------------------------
GetDateTime PROC,
pDateTime:PTR QWORD
LOCAL flTime:FILETIME
; Получает локальную дату и время, сохраняя их в
; 64-разрядном числе.
; Принимает: указатель на QWORD
; Возвращает: ничего.
;--------------------------------------------------
pushad

; Получить локальное время.


INVOKE GetLocalTime,
ADDR sysTime

; Преобразовать SYSTEMTIME в FILETIME


INVOKE SystemTimeToFileTime,
ADDR sysTime,
ADDR flTime

; Скопировать FILETIME в Quadword


mov esi,pDateTime
mov eax,flTime.loDateTime
mov DWORD PTR [esi],eax
mov eax,flTime.hiDateTime
mov DWORD PTR [esi+4],eax
popad
ret
GetDateTime ENDP

;----------------------------------------------------------------
GetMaxXY PROC
LOCAL bufInfo:CONSOLE_SCREEN_BUFFER_INFO
; Возвращает текущее положение курсора консоли: колонка (X), строка (Y).
; Принимает: ничего.
; Возвращает: DH = строка (Y); DL = колонка (X) (диапазон 1-255)
;----------------------------------------------------------------
.data
.code
push eax
CheckInit

; Получить размер буфера и атрибуты.


pushad
INVOKE GetConsoleScreenBufferInfo, consoleOutHandle, ADDR bufInfo
popad

mov dx,bufInfo.dwSize.X
mov ax,bufInfo.dwSize.Y
mov dh,al

pop eax

306 Глава 12. Справочный раздел

Стр. 306
отсутствует. 307

ret
GetMaxXY ENDP

;----------------------------------------------------------------
GetMseconds PROC USES ebx edx
LOCAL hours:DWORD, min:DWORD, sec:DWORD
;Возвращает число миллисекунд после полуночи.
;Принимает: ничего.
;Возвращает: число миллисекунд.
;Подсчет: ((часы*3600)+(минуты*60)+секунды))*1000+миллисекунды
;-----------------------------------------------------------------
pushad
INVOKE GetLocalTime,OFFSET sysTime
; Преобразование часов в секунды.
popad
movzx eax,sysTime.wHour
mov ebx,3600
mul ebx
mov hours,eax

; Преобразование минут в секунды.


movzx eax,sysTime.wMinute
mov ebx,60
mul ebx
mov min,eax

; Добавление секунд к общему количеству.


movzx eax,sysTime.wSecond
mov sec,eax

; Умножение секунд на 1000.


mov eax,hours
add eax,min
add eax,sec
mov ebx,1000
mul ebx

; Добавление миллисекунд к общему количеству.


movzx ebx,sysTime.wMilliseconds
add eax,ebx

ret
GetMseconds ENDP

;--------------------------------------------------
Gotoxy PROC
; Размещение курсора на консоле.
; Принимает: DH = строка, DL = колонка.
; Последнее обновление: 7/11/01
;--------------------------------------------------------
.data
_cursorPosition COORD <>
.code
pushad

CheckInit ; Консоль инициализирована?


movzx ax,dl
mov _cursorPosition.X, ax

Глава 12. Справочный раздел 307

Стр. 307
movzx ax,dh
mov _cursorPosition.Y, ax
INVOKE SetConsoleCursorPosition, consoleOutHandle, _cursorPosition

popad
ret
Gotoxy ENDP

;----------------------------------------------------
Initialize PROC private
; Получить стандартные дескрипторы консоли для входа и выхода и
; установить переменную (флаг) инициализации.
;----------------------------------------------------
pushad

INVOKE GetStdHandle, STD_INPUT_HANDLE


mov consoleInHandle,eax

INVOKE GetStdHandle, STD_OUTPUT_HANDLE


mov consoleOutHandle,eax

mov InitFlag,1

popad
ret
Initialize ENDP

;-----------------------------------------------
IsDigit PROC
; Определяет, является ли символ в AL десятичной цифрой.
; Принимает: AL = символ
; Возвращает: ZF=1 если AL содержит цифру,
; в противном случае ZF=0.
;-----------------------------------------------
cmp al,'0'
jb ID1
cmp al,'9'
ja ID1
test ax,0 ; устанавливает ZF = 1
ID1:
ret
IsDigit ENDP

;--------------------------------------------------------------
RandomRange PROC
; Генерирует псевдо-случайную последовательность 32-разрядных
; целых чисел в диапазоне 0 ... (n-1).
; Принимает: EAX = n.
; Возвращает: EAX = случайное число.
;--------------------------------------------------------------
push ebx
push edx

mov ebx,eax ; Максимальное значение.


call Random32 ; eax = случайное число.
mov edx,0
div ebx ; Деление на максимальное значение
mov eax,edx ; Возвращение остатка.

308 Глава 12. Справочный раздел

Стр. 308
отсутствует. 309

pop edx
pop ebx

ret
RandomRange ENDP

;--------------------------------------------------------------
Random32 PROC
; Генерирует псевдо-случайную последовательность 32-разрядных
; целых чисел в диапазоне 0 ... FFFFFFFFh.
; Принимает: ничего.
; Возвращает: EAX = случайное число.
;--------------------------------------------------------------
.data
seed DWORD 1
.code
push edx
mov eax, 343FDh
imul seed
add eax, 269EC3h
mov seed, eax ; Нач. значение для очередного вызова.
ror eax,8 ; Ротация младщих цифр.
pop edx
ret
Random32 ENDP

;--------------------------------------------------------
Randomize PROC
; Инициирует генератор случайных чисел текущим временем в секундах.
; Принимает: ничего.
; Возвращает: ничего.
;--------------------------------------------------------
pushad
INVOKE GetSystemTime,OFFSET sysTime
movzx eax,sysTime.wMilliseconds
mov seed,eax
popad
ret
Randomize ENDP

;------------------------------------------------------------
ReadChar PROC
; Считывает один символ со стандартного входа (консоли). Символ
; не дублируется на экран. Ожидает ввода, если входной буфер пуст.
; Принимает: ничего.
; Возвращает: AL = код ASCII
; Добавлено.
;----------------------------------------------------------
push ebx
push eax

L1: mov eax,10 ; Выделяет 10ms


call Delay
call ReadKey ; Наличие данных в буфере клавиатуры.
jz L1 ; При ZF=1 нет данных.

; Дополнительный код для сохранения старших 24 бит EAX.

Глава 12. Справочный раздел 309

Стр. 309
mov bl,al ; Сохранить код ASCII.
pop eax
mov al,bl
pop ebx
ret
ReadChar ENDP

;--------------------------------------------------------
ReadDec PROC USES ebx ecx edx esi
LOCAL saveDigit:DWORD
; Считывает 32-разрядное беззнаковое целое число со стандартного
; входа. Концом ввода служит нажатие клавиши <Enter>.
; Все цифры, введенные до нецифрового символа, преобразовываются
; в числовое значение. Предшествующие пробелы игнорируются.
; Принимает: ничего.
; Возвращает:
; Если ничего не введено, EAX=0 and CF=1
; Если введены одни пробелы, EAX=0 and CF=1
; Если целое число больше 2^32-1, EAX=0 and CF=1
; В остальных случаях, EAX=преобразованное число и CF=0
;--------------------------------------------------------

; Ввод строки символов с помощью ReadString.


mov edx,OFFSET digitBuffer
mov esi,edx ; Сохранить смещение в ESI.
mov ecx,MAX_DIGITS
call ReadString
mov ecx,eax ; Сохранить длину в ECX.
cmp ecx,0 ; Больше чем 0?
jne L1 ; Да: продолжение.
mov eax,0 ; Нет: установить возвращаемое
jmp L5 ; значение и выход с CF=1.

; Пропустить все пробелы.


L1: mov al,[esi] ; Взять символ из буфера.
cmp al,' ' ; Обнаружен пробел?
jne L2 ; Нет: к следующему шагу.
inc esi ; Да: указать на следующий символ.
loop L1 ; Все пробелы?
jmp L5 ; Да: выход с CF=1.

; Преобразование числа.
L2: mov eax,0 ; Очистить аккумулятор.
mov ebx,10 ; EBX является делителем.

; Посторить цикл для каждой цифры.


L3: mov dl,[esi] ; Взять символ из буфера.
cmp dl,'0' ; значение < '0'?
jb L4
cmp dl,'9' ; значение > '9'?
ja L4
and edx,0Fh ; Нет: преобразовать в двоичное.

mov saveDigit,edx
mul ebx ; EDX:EAX = EAX * EBX
jc L5 ; quit if Carry (EDX > 0)
mov edx,saveDigit

310 Глава 12. Справочный раздел

Стр. 310
отсутствует. 311

add eax,edx ; Добавить цифру к сумме.


jc L5 ; Выход если перенос.
inc esi ; Указать на следующую цифру.
jmp L3 ; Получить следующую цифру.

L4: clc ; Успешное завершение (CF=0).


jmp L6

L5: mov eax,0 ; Установить нуль.


stc ; Сигнализировать об ошибке (CF=1).
L6:
ret
ReadDec ENDP

;--------------------------------------------------------
ReadHex PROC USES ebx ecx edx esi
; Считывает 32-разрядное шестнадцатеричное число со стандартного
; входа. Концом ввода служит нажатие клавиши <Enter>.
; Принимает: ничего.
; Возвращает: EAX = двоичное целое число
; Если ничего не введено, EAX=0 and CF=1
; Если введены одни пробелы, EAX=0 and CF=1
; В остальных случаях, EAX=преобразованное число и CF=0
; Замечание: Не производится проверки ошибок ввода.
;--------------------------------------------------------
.data
xbtable BYTE 0,1,2,3,4,5,6,7,8,9,7 DUP(0FFh),10,11,12,13,14,15
numVal DWORD ?
charVal BYTE ?

.code
mov edx,OFFSET digitBuffer
mov esi,edx ; save in ESI also
mov ecx,MAX_DIGITS
call ReadString ; Считать строку.
mov ecx,eax ; Сохранить длину в ECX.
cmp ecx,0 ; Больше чем 0?
jne B1 ; Да: продолжать.
jmp B8 ; Нет: выход с CF=1.

; Пропустить все пробелы.


B1: mov al,[esi] ; Взять символ из буфера.
cmp al,' ' ; Обнаружен пробел?
jne B4 ; Нет: к следующему шагу.
inc esi ; Да: указать на следующий символ.
loop B1 ; Все пробелы?
jmp B8 ; Да: выход с CF=1.

; Преобразование числа.
B4: mov numVal,0 ; Очистить аккумулятор.
mov ebx,OFFSET xbtable ; Транслировать таблицу.

; Посторить цикл для каждой цифры.


B5: mov al,[esi] ; Получить символ из буфера.
cmp al,'F' ; Нижний регистр?
jbe B6 ; Нет.
and al,11011111b ; Ла: преобразовать в верхний.

Глава 12. Справочный раздел 311

Стр. 311
B6: sub al,30h ; Скорректировать таблицу.
xlat ; Преобразовать в двоичное.
mov charVal,al
mov eax,16 ; numVal *= 16
mul numVal
mov numVal,eax
movzx eax,charVal ; numVal += charVal
add numVal,eax
inc esi ; Указать на следующую цифру.
loop B5 ; Повторять, декрементировать счетчик.

B7: mov eax,numVal ; Возвратить допустимое значение.


clc ; CF=0
jmp B9

B8: mov eax,0 ; Ошибка: возвратить 0


stc ; CF=1

B9: ret
ReadHex ENDP

;--------------------------------------------------------
ReadInt PROC USES ebx ecx edx esi
LOCAL Lsign:SDWORD, saveDigit:DWORD

; Считывает 32-разрядное число со знаком из стандартного


; входа. Концом ввода служит нажатие клавиши <Enter>.
; Все цифры, введенные до нецифрового символа, преобразовываются
; в числовое значение. Предшествующие пробелы игнорируются, при
; этом вначале могут стоять знаки "+" и "-". Если введены одни
; пробелы, то возвращаемым значением будет 0.
; Принимает: ничего.
; Возвращает: при CF=0 в EAX допустимое двоичное целое число.
; Если CF=1, то недопустимое значение и EAX=0.
;--------------------------------------------------------
.data
overflow_msgL BYTE " <32-bit integer overflow>",0
invalid_msgL BYTE " <invalid integer>",0
.code

; Ввод строки цифр с помощью ReadString.


mov Lsign,1 ; Предположим число положительное.
mov edx,OFFSET digitBuffer
mov esi,edx ; Сохранение смещения в ESI.
mov ecx,MAX_DIGITS
call ReadString
mov ecx,eax ; Сохранение длины в ECX.
cmp ecx,0 ; Длина больше нуля?
jne L1 ; Да: прололжение
mov eax,0 ; Нет: остановить возвращаемое
jmp L10 ; значение и выйти.

; Пропуск пробелов.
L1: mov al,[esi] ; Получить символ из буфера.
cmp al,' ' ; Пробел?
jne L2 ; Нет: проверить знак.
inc esi ; Да: указать на следующий символ.
loop L1

312 Глава 12. Справочный раздел

Стр. 312
отсутствует. 313

mov eax,0 ; Все пробелы?


jmp L10 ; Возвратить нуль как значение.

; Check for a leading sign.

L2: cmp al,'-' ; Обнаружен знак минуса?


jne L3 ; Нет: проверить знак плюса.

mov Lsign,-1 ; Да: знак положительный.


dec ecx ; Уменьшить счетчик.
inc esi ; Указать на следующий символ.
jmp L3A
L3: cmp al,'+' ; Обнаружен знак плюса?
jne L3A ; Нет: пропуск.
inc esi ; Да: к следующему символу.
dec ecx ; Уменьшить счетчик.

; Test the first digit, and exit if nonnumeric.


; Проверка первого символа и выход, если это не цифра.
L3A: mov al,[esi] ; Получить первый символ.
call IsDigit ; Это цифра?
jnz L7A ; Нет: сообщение об ошибке.

; Start to convert the number.


L4: mov eax,0 ; Очистить аккумулятор.
mov ebx,10 ; EBX как делитель.

; Repeat loop for each digit.

L5: mov dl,[esi] ; Получить символ из буфера.


cmp dl,'0' ; символ < '0'?
jb L9
cmp dl,'9' ; символ > '9'?
ja L9
and edx,0Fh ; Нет: преобразовать в двоичное.

mov saveDigit,edx
imul ebx ; EDX:EAX = EAX * EBX
mov edx,saveDigit

jo L6 ; Выход если переполнение.


add eax,edx ; Добавить очередную цифру в AX.
jo L6 ; Выход если переполнение.
inc esi ; Указать на следующую цифру.
jmp L5 ; Получить следующую цифру.

; Переполнение при EAX = 80000000h и отрицательном знаке.


L6: cmp eax,80000000h
jne L7
cmp Lsign,-1
jne L7 ; Переполнение.
jmp L9 ; Допустимое значение.

; Сообщение "integer overflow".


L7: mov edx,OFFSET overflow_msgL
jmp L8

; Сообщение "invalid integer".

Глава 12. Справочный раздел 313

Стр. 313
L7A:
mov edx,OFFSET invalid_msgL

; Отобразить сообщение об ошибке, на которое указывает EDX,


; и установить флаг переполнения (Overflow).

L8: call WriteString


call Crlf
mov al,127
add al,1 ; Установить флаг переполнения.
mov eax,0 ; Возвращаемое значение 0.
jmp L10 ; Выйти.

; Команда IMUL оставляет флаг знака (Sign) неопределенным,


; поэтому используется команда OR.
L9: imul Lsign ; EAX = EAX * sign
or eax,eax ; Определение знака числа.
L10:
ret
ReadInt ENDP

;------------------------------------------------------------------
ReadKey PROC USES ecx
LOCAL evEvents:WORD, saveFlags:DWORD
; Выполняет непосредственную проверку клавиатуры и считывает
; доступный символ.
; Если код Ascii равен нулю, то обрабатываются функцианальные
; клавиши.
; Принимает: ничего.
; Возвращает: ZF установлен, если нет нажатия, сбрасывается при
; считывании символа.
; AL = код Ascii клавиши (0 для специальных клавиш).
; AH = Виртуальный скан-код (или преобразованный при Translate).
; DX = Виртуальный код клавиши.
; EBX = Флаги клавиатуры (Alt,Ctrl,Caps и т.д.)
; Верхние половинки EAX и EDX переписывается.
;-----------------------------------------------------------------
.data
evBuffer INPUT_RECORD <> ; Буфера клавиш.
evRepeat WORD 0 ; Счетчик повторений.
.code
CheckInit ; Инициализация, если не инициализировано.

; Сохранение флагов консоли.


INVOKE GetConsoleMode,consoleInHandle,ADDR saveFlags

; Сброс флагов консоли для возможности обрабатывать Ctrl-C


; и Ctrl-S.
INVOKE SetConsoleMode,consoleInHandle,0

cmp evRepeat,0 ; Клавиша уже нажималась?


ja HaveKey ; Да, продолжить обработку.

Peek:
; Если есть задержанные события, обработать их.
INVOKE PeekConsoleInput, consoleInHandle, ADDR evBuffer,
1, ADDR evEvents
test evEvents,0FFFFh

314 Глава 12. Справочный раздел

Стр. 314
отсутствует. 315

jz NoKey ; Задержанных событий нет.

INVOKE ReadConsoleInput, consoleInHandle, ADDR evBuffer,


1, ADDR evEvents
test evEvents,0FFFFh
jz NoKey ; Задержанных событий нет.

cmp evBuffer.eventType,KEY_EVENT ; Это событие клавиатуры?


jne Peek ; Нет: проверить следующее событие.
TEST evBuffer.Event.bKeyDown, KBDOWN_FLAG ; Это нажатие клавиши?
jz Peek ; Нет: проверить следующее событие.

mov ax,evBuffer.Event.wRepeatCount ; Установить счетчик.


mov evRepeat,ax

HaveKey:
mov al,evBuffer.Event.uChar.AsciiChar ; Симол Ascii в AL.
mov ah,BYTE PTR evBuffer.Event.wVirtualScanCode ; Скан-код в AH.
mov dx,evBuffer.Event.wVirtualKeyCode ; Код вирт.клавиши в DX.
mov ebx,evBuffer.Event.dwControlKeyState ; Флаги в EBX.

; Игнорировать нажатия Shift, Ctrl, Alt и т.д.


; Не обрабатывать до нажатия клавиши.
.IF dx == VK_SHIFT || dx == VK_CONTROL || dx == VK_MENU || \
dx == VK_CAPITAL || dx == VK_NUMLOCK || dx == VK_SCROLL
jmp Peek ; Не обрабатывать, дать следующее нажатие.
.ENDIF

call ReadKeyTranslate ; Перевести скан-код.


dec evRepeat ; Увеличить счетчик.
or dx,dx ; Есть нажатие: сбросить флаг нуля.
jmp Done

NoKey:
mov evRepeat,0 ; Переустановить счетчик повторений.
test eax,0 ; Нет нажатия: ZF=1 и выход.

Done:
pushfd ; Сохранить флаг нуля.
pushad
; Восстановить режим консоли.
INVOKE SetConsoleMode,consoleInHandle,saveFlags

call ReadKeyFlush

popad
popfd ; Восстановить флаг нуля.
ret
ReadKey ENDP

;-----------------------------------------------------------------
ReadKeyFlush PROC
; Выгружает входной буфер консоли и сбрасывает счетчики.
; Может использоваться для быстрой реакции на нажатия клавиш в
; играх, где использование обработчика данных приводит к задержкам.
; Принимает: ничего.
; Возвращает: ничего.
;-----------------------------------------------------------------

Глава 12. Справочный раздел 315

Стр. 315
INVOKE FlushConsoleInputBuffer, consoleInHandle ; Выгрузить буфер
mov evRepeat,0 ; Переустановить счетчик.
ret
ReadKeyFlush ENDP

;-----------------------------------------------------------------
ReadKeyTranslate PROC PRIVATE USES ebx ecx edx esi
; Перевести скан-код в совместимые с DOS/BIOS возвращаемые значения
; Принимает:
; al = код Ascii клавиши,
; ah = виртуальный скан-код,
; dx = виртуальный код клавиши,
; ebx = флаги клавиатуры (Alt,Ctrl,Caps и т.д.)
; Возвращает:
; ah = измененный скан-код (для Alt/Ctrl/Shift и др.)
; al = измененный код Ascii клавиши (0 для функциональных)
;-----------------------------------------------------------------
.data ; Таблица перевода.
; Порядок: виртуальная клавиша, скан-код, CtrlScan, AltScan
SpecialCases \
BYTE VK_LEFT, 4Bh, 73h, 4Bh
CaseSize = ($ - SpecialCases) ; Размер элемента.
BYTE VK_RIGHT, 4Dh, 74h, 4Dh
BYTE VK_UP, 48h, 8Dh, 48h
BYTE VK_DOWN, 50h, 91h, 50h
BYTE VK_PRIOR, 49h, 84h, 49h ; PgUp
BYTE VK_NEXT, 51h, 76h, 51h ; PgDn
BYTE VK_HOME, 47h, 77h, 47h
BYTE VK_END, 4Fh, 75h, 4Fh
BYTE VK_INSERT,52h, 92h, 52h
BYTE VK_DELETE,53h, 93h, 53h
BYTE VK_ADD, 4Eh, 90h, 4Eh
BYTE VK_SUBTRACT,4Ah,8Eh, 4Ah
BYTE VK_F11, 85h, 85h, 85h
BYTE VK_F12, 86h, 86h, 86h
BYTE VK_11, 0Ch, 0Ch, 82h
BYTE VK_12, 0Dh, 0Dh, 83h
BYTE 0 ; Конец таблицы.

.code
pushfd ; Сохранение флагов.
mov esi,0

; Поиск в таблице.
Search:
cmp SpecialCases[esi],0 ; Проверка окончания таблицы.
je NotFound

cmp dl,SpecialCases[esi] ; Проверка специального случая.


je Found

add esi,CaseSize ; Инкрементировать индекс таблицы.


jmp Search ; Продолжать поиск.

Found:
.IF ebx & CTRL_MASK
mov ah,SpecialCases[esi+2] ; Установить скан-код Ctrl.
mov al,0 ; Обновить символ.

316 Глава 12. Справочный раздел

Стр. 316
отсутствует. 317

.ELSEIF ebx & ALT_MASK


mov ah,SpecialCases[esi+3] ; Установить скан-код Ctrl Alt
mov al,0 ; Обновить символ.
.ELSE
mov ah,SpecialCases[esi+1]
.ENDIF
jmp Done

NotFound:
.IF ! (ebx & KEY_MASKS) ; Выполнить, если нет комбинации shift/ctrl/alt.
jmp Done
.ENDIF

.IF dx >= VK_F1 && dx <= VK_F10 ; Проверить клавиши от F1 до F10.


.IF ebx & CTRL_MASK
add ah,23h ; 23h выделение Ctrl/Fn.
.ELSEIF ebx & ALT_MASK
add ah,2Dh ; 2Dh выделение Alt/Fn.
.ELSEIF ebx & SHIFT_MASK
add ah,19h ; 19h выделение Shift/Fn.
.ENDIF
.ELSEIF al >= '0' && al <= '9' ; Проверка от Alt/1 до Alt/9
.IF ebx & ALT_MASK
add ah,76h ; 76h выделение Alt/n
mov al,0
.ENDIF
.ELSEIF dx == VK_TAB ; Проверка Shift/Tab (backtab)
.IF ebx & SHIFT_MASK
mov al,0 ; ah всегда 0Fh, al=0 для специальных.
.ENDIF
.ENDIF

Done:
popfd ; Восстановление.
ret
ReadKeyTranslate ENDP

;------------------------------------------------------------------
ReadString PROC
LOCAL bufSize:DWORD, saveFlags:DWORD, junk:DWORD
;
; Считывание строки с консоли в буфер.
; Принимает: EDX смещение входного буфера.
; ECX = максимальное число символов с нулевым.
; Возвращает: EAX = размер входной строки.
; Комментарий: Прекращение ввода при нажатии <Enter>.
;---------------------------------------------------------------------
.data
_$$temp DWORD ?
.code
pushad
CheckInit

mov edi,edx
mov bufSize,ecx

push edx
INVOKE ReadConsole,

Глава 12. Справочный раздел 317

Стр. 317
consoleInHandle,
edx,
ecx,
OFFSET bytesRead,
0
pop edx
cmp bytesRead,0
jz L5

dec bytesRead
cld
mov ecx,bufSize
mov al,0Ah
repne scasb
jne L1

; если здесь, то длина входной строки <= (bufsize - 2)


dec bytesRead
sub edi,2 ; обнаружен 0Ah: обратно 2 позиции
cmp edi,edx
jae L2
mov edi,edx ; 0Ah должен быть в буфере.
jmp L2

L1: mov edi,edx ; Указать на последний байт буфера.


add edi,bufSize
dec edi
mov BYTE PTR [edi],0 ; Вставить нулевой байт.

; Сохранить текущий режим консоли.


INVOKE GetConsoleMode,consoleInHandle,ADDR saveFlags
; Режим ввода символа.
INVOKE SetConsoleMode,consoleInHandle,0

L6: INVOKE ReadConsole,consoleInHandle,ADDR junk,1,ADDR _$$temp,0


mov al,BYTE PTR junk
cmp al,0Ah
jne L6

INVOKE SetConsoleMode,consoleInHandle,saveFlags ; restore console mode.


jmp L5

L2: mov BYTE PTR [edi],0 ; Вставить нулевой байт.

L5: popad
mov eax,bytesRead
ret
ReadString ENDP

;---------------------------------------------------------------------
SetTextColor PROC
; Изменить цвет последующего выходного текста.
; Принимает: EAX = атрибуты. Биты 0-3 цвет символа,
; биты 4-7 цвет фона.
; Возвращает: ничего.
;---------------------------------------------------------------------
.data
scrAttrib DWORD ?

318 Глава 12. Справочный раздел

Стр. 318
отсутствует. 319

.code
pushad
mov scrAttrib,eax ; Младший байт содержит атрибуты.
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov [consoleOutHandle],eax

; Установить цвета белый и голубой


INVOKE SetConsoleTextAttribute, consoleOutHandle, scrAttrib
popad
ret
SetTextColor ENDP

;---------------------------------------------------------------------
Str_compare PROC USES eax edx esi edi,
string1:PTR BYTE,
string2:PTR BYTE
; Сравнение строк.
; Возврат: ничего, но флаги нуля и переноса изменяются как
; при команде CMP.
;-----------------------------------------------------
mov esi,string1
mov edi,string2

L1: mov al,[esi]


mov dl,[edi]
cmp al,0 ; конец строки 1?
jne L2 ; Нет.
cmp dl,0 ; Да: конец строки 2?
jne L2 ; Нет.
jmp L3 ; Да, выход с ZF = 1.

L2: inc esi


inc edi
cmp al,dl ; символы равны?
je L1 ; Да: продолжает цикл.
; Нет: выход с установленными флагами.
L3: ret
Str_compare ENDP

;---------------------------------------------------------
Str_copy PROC USES eax ecx esi edi,
source:PTR BYTE, ; source string
target:PTR BYTE ; target string
;
; Копирование строки.
; Требования: приемная строка должна содержать достаточно места.
;----------------------------------------------------------
INVOKE Str_length,source ; EAX = длина исходной.
mov ecx,eax ; Счетчик.
inc ecx ; add 1 for null byte
mov esi,source
mov edi,target
cld ; Направление вверх.
rep movsb ; Копирование.
ret
Str_copy ENDP

;---------------------------------------------------------

Глава 12. Справочный раздел 319

Стр. 319
Str_length PROC USES edi,
pString:PTR BYTE ; pointer to string
;
; Подсчет длины строки с нулевым окончанием.
; Принимает: pString - указатель на строку
; Возвращает: EAX = длина строки
;---------------------------------------------------------
mov edi,pString
mov eax,0
L1:
cmp BYTE PTR [edi],0
je L2
inc edi
inc eax
jmp L1
L2: ret
Str_length ENDP

;-----------------------------------------------------------
Str_trim PROC USES eax ecx edi,
pString:PTR BYTE, ; points to string
char:BYTE ; char to remove
;
; Удаляет все вхождения заданного символа
; в конце строки.
; Возвращает: ничего.
;-----------------------------------------------------------
mov edi,pString
INVOKE Str_length,edi
cmp eax,0
je L2
mov ecx,eax
dec eax
add edi,eax
mov al,char
std
repe scasb
jne L1
dec edi
L1: mov BYTE PTR [edi+2],0
L2: ret
Str_trim ENDP

;---------------------------------------------------
Str_ucase PROC USES eax esi,
pString:PTR BYTE
; Устанавливает символы строки в верхний регистр.
; Принимает: pString - указатель на строку.
; Возвращает: ничего.
;---------------------------------------------------
mov esi,pString
L1:
mov al,[esi] ; очередной символ.
cmp al,0 ; конец строки?
je L3 ; Да: выход.
cmp al,'a' ; Перед "a"?
jb L2
cmp al,'z' ; После "z"?

320 Глава 12. Справочный раздел

Стр. 320
отсутствует. 321

ja L2
and BYTE PTR [esi],11011111b ; Преобразовать.

L2: inc esi ; Следующий символ.


jmp L1

L3: ret
Str_ucase ENDP

;------------------------------------------------------
WaitMsg PROC
; Отображает запрос и ожидает нажатия клавиши Enter.
; Принимает: ничего.
; Возвращает: ничего.
;------------------------------------------------------
WAITMSG_BUFSIZE = 5
.data
waitmsgstr DB "Press [Enter] to continue...",0
localBuf BYTE WAITMSG_BUFSIZE DUP(?)
.code
pushad
CheckInit
mov edx,OFFSET waitmsgstr
call WriteString
mNewLine

w1: INVOKE FlushConsoleInputBuffer,consoleInHandle

INVOKE ReadConsole,
consoleInHandle, ; Дескриптор входа консоли.
OFFSET localBuf, ; Указатель на локальный буфер.
WAITMSG_BUFSIZE, ; Максимальное показание счетчика.
OFFSET bytesRead,
0
cmp bytesRead,2
jnz w1 ; Цикл до возврата ReadConsole 2 байт.

popad
ret
WaitMsg ENDP

;------------------------------------------------------
WriteBin PROC
; Запись 32-разрядного целого числа в выходной поток.
; Принимает: EAX = число.
; Возвращает: ничего.
;------------------------------------------------------
push ebx
mov ebx,4 ; Формат двойного слова.
call WriteBinB
pop ebx
ret
WriteBin ENDP

;------------------------------------------------------
WriteBinB PROC
; Запись 32-разрядного целого числа в выходной поток.
; Принимает: EAX = число.

Глава 12. Справочный раздел 321

Стр. 321
; EBX = отображаемый размер (1,2,4)
; Возвращает: ничего.
;------------------------------------------------------
pushad
cmp ebx,1 ; Убедиться, что EBX равен 1, 2 или 4
jz WB0
cmp ebx,2
jz WB0
mov ebx,4
WB0:
mov ecx,ebx
shl ecx,1 ; Число из 4-бит в конце EAX.
cmp ebx,4
jz WB0A
ror eax,8 ; Предположим TYPE==1.
cmp ebx,1
jz WB0A ; Предположение правильно.
ror eax,8 ; TYPE==2.
WB0A:
mov esi,OFFSET buffer
WB1:
push ecx ; Сохраняем счетчик.
mov ecx,4 ; В группе 4 бита.
WB1A:
shl eax,1 ; Сдвигает EAX во флаг переноса.
mov BYTE PTR [esi],'0'
jnc WB2
mov BYTE PTR [esi],'1'
WB2:
inc esi
Loop WB1A
mov BYTE PTR [esi],' ' ; Пробел
inc esi ; между группами.
pop ecx ; Восстановить счетчик.
loop WB1 ; Следующие 4-бита.
dec esi ; Исключить пробелы.
mov BYTE PTR [esi],0 ; Нулевой байт в конец.
mov edx,OFFSET buffer ; Отобразить буфер.
call WriteString
popad
ret
WriteBinB ENDP

;------------------------------------------------------
WriteChar PROC
; Записать символ в выходной поток.
; Принимает: AL = символ.
;------------------------------------------------------
pushad
pushfd
CheckInit

mov buffer,al
cld ; Обязательно сбросить флаг направления.
INVOKE WriteConsole,
consoleOutHandle,
OFFSET buffer,
1,

322 Глава 12. Справочный раздел

Стр. 322
отсутствует. 323

OFFSET bytesWritten,
0

popfd
popad
ret
WriteChar ENDP

;-----------------------------------------------------
WriteDec PROC
; Записать 32-разрядное число в выходной поток.
; Вход: EAX = число.
;------------------------------------------------------
.data
; До 10 цифр.
BUFFER_SIZE = 12
bufferL BYTE BUFFER_SIZE DUP(?),0

.code
pushad
CheckInit
mov ecx,0 ; Счетчик.
mov edi,OFFSET bufferL
add edi,(BUFFER_SIZE - 1)
mov ebx,10 ; Основание счисления.

WI1: mov edx,0


div ebx
xchg eax,edx
call AsciiDigit ; Преобразовать AL в ASCII
mov [edi],al
dec edi
xchg eax,edx
inc ecx
or eax,eax
jnz WI1

; Отобразить цифры (CX = cчетчик)


WI3:
inc edi
mov edx,edi
call WriteString

WI4:
popad
ret
WriteDec ENDP

;------------------------------------------------------
WriteHex PROC
; Записать 32-разрядное шестнадцатеричное число
; без знака в выходной поток.
; Вход: EAX = Число.
;------------------------------------------------------
push ebx
mov ebx,4
call WriteHexB
pop ebx

Глава 12. Справочный раздел 323

Стр. 323
ret
WriteHex ENDP

;------------------------------------------------------
WriteHexB PROC
LOCAL displaySize:DWORD
; Записать 32-разрядное шестнадцатеричное число
; без знака в выходной поток.
; Принимает: EAX = число.
; EBX = отображаемый размер (1,2,4)
; Возвращает: ничего.
;------------------------------------------------------
DOUBLEWORD_BUFSIZE = 8
.data
bufferLHB BYTE DOUBLEWORD_BUFSIZE DUP(?),0
.code
pushad
mov displaySize,ebx

.IF EBX == 1
and eax,0FFh
.ELSE
.IF EBX == 2
and eax,0FFFFh
.ELSE
mov displaySize,4
.ENDIF
.ENDIF

CheckInit

mov edi,displaySize
shl edi,1
mov bufferLHB[edi],0
dec edi
mov ecx,0
mov ebx,16
L1:
mov edx,0
div ebx

xchg eax,edx
call AsciiDigit
mov bufferLHB[edi],al
dec edi
xchg eax,edx
inc ecx
or eax,eax
jnz L1
mov eax,displaySize
shl eax,1
sub eax,ecx
jz L3
mov ecx,eax
L2:
mov bufferLHB[edi],'0'
dec edi
loop L2

324 Глава 12. Справочный раздел

Стр. 324
отсутствует. 325

; Отображаем цифры.
L3:
mov ecx,displaySize
shl ecx,1
inc edi
mov edx,OFFSET bufferLHB
add edx,edi
call WriteString
popad
ret
WriteHexB ENDP

;-----------------------------------------------------
WriteInt PROC
; Записывает 32-разрядное число со знаком в стандартный выход
; в коде ASCII.
; Принимает: EAX = целое число.
; Возвращает: ничего.
; Комментарий: Отображает знак числа.
;-----------------------------------------------------
WI_Bufsize = 12
true = 1
false = 0
.data
buffer_B BYTE WI_Bufsize DUP(0),0 ; Буфер для цифр.
neg_flag BYTE ?

.code
pushad
CheckInit

mov neg_flag,false ; Предполагаем neg_flag = false


or eax,eax ; AX положительное?
jns WIS1 ; Да: переход на B1
neg eax ; Нет: делает положительным
mov neg_flag,true ; устанавливая neg_flag = true

WIS1:
mov ecx,0 ; Счетчик цифр = 0
mov edi,OFFSET buffer_B
add edi,(WI_Bufsize-1)
mov ebx,10 ; Будет делить на 10

WIS2:
mov edx,0 ; Устанавливает делимое в 0.
div ebx ; Делим AX на 10.
or dl,30h ; Преобразовывает остаток в ASCII
dec edi ; Возвращаемся.
mov [edi],dl ; Сохранение цифр ASCII.
inc ecx ; Инкрементируем счетчик.
or eax,eax ; Частное > 0?
jnz WIS2 ; Да: делим дальше.

; Вставляем знак.
dec edi
inc ecx
mov BYTE PTR [edi],'+' ; Вставляет знак плюс.

Глава 12. Справочный раздел 325

Стр. 325
cmp neg_flag,false ; Число положительное?
jz WIS3 ; Да.
mov BYTE PTR [edi],'-' ; Нет: вставляем знак минус.

WIS3: ; Отображаем число.


mov edx,edi
call WriteString
popad
ret
WriteInt ENDP

;--------------------------------------------------------
WriteString PROC
;
; Записывает строку с нулевым окончанием в стандартный выход.
; Принимает: EDX указывает на строку.
;--------------------------------------------------------
pushad

CheckInit

INVOKE Str_length,edx ; Возвращает длину строки в EAX.


cld ; Необходимо выполнить до WriteConsole.

INVOKE WriteConsole,
consoleOutHandle, ; Дескриптор выхода.
edx, ; Указывает на строку.
eax, ; Длина строки.
OFFSET bytesWritten, ; Число записанных байт.
0

popad
ret
WriteString ENDP

;----------- Специальные процедуры --------------------------


; Преобразовывает AL в ASCII. Используется в WriteHex и WriteDec
AsciiDigit PROC PRIVATE
push ebx
mov ebx,OFFSET xtable
xlat
pop ebx
ret
AsciiDigit ENDP

HexByte PROC PRIVATE


; Отображает байт AL в шестнадцатеричном виде.
pushad
mov dl,al
rol dl,4
mov al,dl
and al,0Fh
mov ebx,OFFSET xtable
xlat
mov buffer,al ; Сохранение первого символа.
rol dl,4
mov al,dl

326 Глава 12. Справочный раздел

Стр. 326
отсутствует. 327

and al,0Fh
xlat
mov [buffer+1],al ; Сохранение второго символа.
mov [buffer+2],0

mov edx,OFFSET buffer ; Отображаем буфер.


call WriteString

popad
ret
HexByte ENDP
end

Подключаемый файл для работы


с библиотекой функций
NOLIST ; Не включать в листинг.
; Подключаемый файл для функций и констант Windows API
DO_NOT_SHARE = 0
NULL = 0
TRUE = 1
FALSE = 0

FILE_ATTRIBUTE_READONLY = 1
FILE_ATTRIBUTE_HIDDEN = 2
FILE_ATTRIBUTE_SYSTEM = 4
FILE_ATTRIBUTE_DIRECTORY = 10h
FILE_ATTRIBUTE_ARCHIVE = 20h
FILE_ATTRIBUTE_DEVICE = 40h
FILE_ATTRIBUTE_NORMAL = 80h
FILE_ATTRIBUTE_TEMPORARY = 100h
FILE_ATTRIBUTE_SPARSE_FILE = 200h
FILE_ATTRIBUTE_REPARSE_POINT = 400h
FILE_ATTRIBUTE_COMPRESSED = 800h
FILE_ATTRIBUTE_OFFLINE = 1000h
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 2000h
FILE_ATTRIBUTE_ENCRYPTED = 4000h
FILE_READ_DATA = 1
FILE_WRITE_DATA = 2
FILE_APPEND_DATA = 4
FILE_DELETE_CHILD = 40h
FILE_BEGIN = 0
FILE_CURRENT = 1
FILE_END = 2

CREATE_NEW = 1
CREATE_ALWAYS = 2
OPEN_EXISTING = 3
OPEN_ALWAYS = 4
TRUNCATE_EXISTING = 5

GENERIC_READ = 80000000h
GENERIC_WRITE = 40000000h
GENERIC_EXECUTE = 20000000h

Глава 12. Справочный раздел 327

Стр. 327
GENERIC_ALL = 10000000h

INVALID_HANDLE_VALUE = -1

ENABLE_PROCESSED_INPUT = 1
ENABLE_LINE_INPUT = 2
ENABLE_ECHO_INPUT = 4
ENABLE_WINDOW_INPUT = 8
ENABLE_MOUSE_INPUT = 16

ENABLE_PROCESSED_OUTPUT = 1
ENABLE_WRAP_AT_EOL_OUTPUT = 2

; Константы событий.
KEY_EVENT = 1
MOUSE_EVENT = 2
WINDOW_BUFFER_SIZE_EVENT = 4 ; Запись изменений событий окна.
MENU_EVENT = 8 ; Запись меню событий.
FOCUS_EVENT = 16 ; Изменение фокуса.

; Константы состояния клавиш клавиатуры


KEY_MASKS = 00011111b ; Маска при установке Shift/Ctrl/Alt.
ALT_MASK = 00000011b ; Флаги левой и правой клавиши Alt.
CTRL_MASK = 00001100b ; Флаги левой и правой клавиши CTRL.
SHIFT_MASK = 00010000b ; Флаги левой и правой клавиши SHIF.

KBDOWN_FLAG = 00000001h ; Устанавливается при нажатии,


; при отпускании сбрасывается.

; Стандартные определения виртуальных клавиш Windows


VK_LBUTTON = 01H
VK_RBUTTON = 02H
VK_CANCEL = 03H
VK_BACK = 08H
VK_TAB = 09H
VK_CLEAR = 0cH
VK_RETURN = 0dH
VK_SHIFT = 10H
VK_CONTROL = 11H
VK_MENU = 12H
VK_PAUSE = 13H
VK_CAPITAL = 14H
VK_ESCAPE = 1bH
VK_SPACE = 20H
VK_PRIOR = 21H
VK_NEXT = 22H
VK_END = 23H
VK_HOME = 24H
VK_LEFT = 25H
VK_UP = 26H
VK_RIGHT = 27H
VK_DOWN = 28H
VK_PRINT = 2aH
VK_EXECUTE = 2bH
VK_SNAPSHOT = 2ch ; Клавиша печати
VK_INSERT = 2dH
VK_DELETE = 2eH
VK_HELP = 2fH

328 Глава 12. Справочный раздел

Стр. 328
отсутствует. 329

VK_NUMPAD0 = 60H
VK_NUMPAD1 = 61H
VK_NUMPAD2 = 62H
VK_NUMPAD3 = 63H
VK_NUMPAD4 = 64H
VK_NUMPAD5 = 65H
VK_NUMPAD6 = 66H
VK_NUMPAD7 = 67H
VK_NUMPAD8 = 68H
VK_NUMPAD9 = 69H
VK_MULTIPLY = 6AH
VK_ADD = 6BH
VK_SEPARATER = 6CH
VK_SUBTRACT = 6DH
VK_DECIMAL = 6EH
VK_DIVIDE = 6FH
VK_F1 = 70H
VK_F2 = 71H
VK_F3 = 72H
VK_F4 = 73H
VK_F5 = 74H
VK_F6 = 75H
VK_F7 = 76H
VK_F8 = 77H
VK_F9 = 78H
VK_F10 = 79H
VK_F11 = 7aH
VK_F12 = 7bH
VK_F13 = 7cH
VK_F14 = 7dH
VK_F15 = 7eH
VK_F16 = 7fH
VK_F17 = 80H
VK_F18 = 81H
VK_F19 = 82H
VK_F20 = 83H
VK_F21 = 84H
VK_F22 = 85H
VK_F23 = 86H
VK_F24 = 87H
VK_NUMLOCK = 90H
VK_SCROLL = 91H
VK_11 = 0BDh ; клавиша -
VK_12 = 0BBh ; клавиша +

STD_INPUT_HANDLE EQU -10 ; Предопределенная константа Win API.


STD_OUTPUT_HANDLE EQU -11 ; Предопределенная константа Win API.
ReadConsole EQU <ReadConsoleA>
CreateFile EQU <CreateFileA>
exit EQU <INVOKE ExitProcess,0> ; Выход из программы
PeekConsoleInput EQU <PeekConsoleInputA>
ReadConsoleInput EQU <ReadConsoleInputA>
WriteConsoleOutputCharacter EQU <WriteConsoleOutputCharacterA>

COORD STRUCT
X WORD ?
Y WORD ?
COORD ENDS

Глава 12. Справочный раздел 329

Стр. 329
SYSTEMTIME STRUCT
wYear WORD ?
wMonth WORD ?
wDayOfWeek WORD ?
wDay WORD ?
wHour WORD ?
wMinute WORD ?
wSecond WORD ?
wMilliseconds WORD ?
SYSTEMTIME ENDS

KEY_EVENT_RECORD STRUCT
bKeyDown DWORD ?
wRepeatCount WORD ?
wVirtualKeyCode WORD ?
wVirtualScanCode WORD ?
UNION uChar
UnicodeChar WORD ?
AsciiChar BYTE ?
ENDS
dwControlKeyState DWORD ?
KEY_EVENT_RECORD ENDS

SMALL_RECT STRUCT
Left WORD ?
Top WORD ?
Right WORD ?
Bottom WORD ?
SMALL_RECT ENDS

INPUT_RECORD STRUCT
eventType WORD ?
ALIGN DWORD
event KEY_EVENT_RECORD <>
INPUT_RECORD ENDS

Clrscr PROTO ; Очистка экрана.


DumpMem PROTO ; Отобразить дамп памяти.
ReadChar PROTO ; Считывание символа с консоли.
WriteDec PROTO ; Вывод десятичного числа.
GetLastError PROTO ; Получить последнее значение ошибки.
Crlf PROTO ; Возврат курсора, пропуск строки.
WaitMsg PROTO ; Отобразить сообщение, ждать нажатия.

FlushConsoleInputBuffer PROTO, ; flush the input buffer


inHandle:DWORD ; standard input handle

CloseHandle PROTO, handle:DWORD ; Закрыть дескриптор.


WriteString PROTO ; Запись строки с нулевым окончанием.

ExitProcess PROTO, ; Окончание программы.


dwExitCode:DWORD ; Код возврата.

GetStdHandle PROTO, ; Получение стандартного дескриптора.


nStdHandle:DWORD ; Тип дескриптора.

GetConsoleScreenBufferInfo PROTO,

330 Глава 12. Справочный раздел

Стр. 330
отсутствует. 331

outHandle:DWORD, ; Дескриптор экранного буфера


pBufferInfo:PTR CONSOLE_SCREEN_BUFFER_INFO

GetConsoleMode PROTO,
ioHandle:DWORD, ; Дескриптор входа.
pMode:PTR DWORD ; Указатель на dword, содержащий флаги.

PeekConsoleInput PROTO,
inHandle:DWORD, ; Дескриптор входа.
pBuffer:PTR BYTE, ; Указатель на буфер.
nNumberOfBytesToRead:DWORD, ; Количество символов для чтения.
pNumEventsRead:PTR DWORD ; Указатель на события.

SetConsoleMode PROTO,
outHandle:DWORD, ; Дескриптор выхода.
dwMode:DWORD ; Режим флагов консоли.

ReadConsoleInput PROTO,
inHandle:DWORD, ; Дескриптор входа.
pInputRec:PTR INPUT_RECORD, ; Указатель на входную запись.
numRecs:DWORD, ; request number of recs
pNumRead:PTR DWORD ; ptr to number of bytes read

Sleep PROTO, ; Задержка на n миллисекунд.


dwMilliseconds:DWORD

WriteConsoleA PROTO, ; Запись в буфер консоли.


handle:DWORD, ; Дескриптор выходного устройства.
lpBuffer:PTR BYTE, ; Указатель на буфер.
nNumberOfBytesToWrite:DWORD, ; Размер буфера.
lpNumberOfBytesWritten:PTR DWORD,; Количество записанных байт.
lpReserved:DWORD ; Зарезервировано.

WriteConsoleOutputCharacter PROTO,
outHandle:DWORD, ; Дескриптор выхода консоли.
pBuffer:PTR BYTE, ; Указатель на буфер.
bufsize:DWORD, ; Размер буфера.
xyPos:COORD, ; Координаты первой ячейки.
pCount:PTR DWORD ; Счетчик.

ReadConsole PROTO,
handle:DWORD, ; Дескриптор входного устройства.
lpBuffer:PTR BYTE, ; Указатель на буфер.
nNumberOfBytesToRead:DWORD, ; Количество байт для чтения.
lpNumberOfBytesWritten:PTR DWORD,; Количество считанных байт.
lpReserved:DWORD ; Зарезервировано.

CreateFile PROTO,
pFilename:PTR BYTE, ; Указатель на имя файла.
desiredAccess:DWORD, ; Режим доступа.
shareMode:DWORD, ; Режим совместной работы.
IpSecurity:DWORD, ; Указатель на атрибуты защиты.
creationDisposition:DWORD, ; Опции создания файла.
flagsAndAttributes:DWORD, ; Атрибуты файла.
htemplate:DWORD ; Дескриптор шаблона файла.

WriteFile PROTO,
fileHandle:DWORD, ; Дескриптор выходного устройства.

Глава 12. Справочный раздел 331

Стр. 331
pBuffer:PTR BYTE, ; Указатель на буфер.
nBufsize:DWORD, ; Размер буфера.
pBytesWritten:PTR DWORD, ; Число записанных байт.
pOverlapped:PTR DWORD ; Указатель на информацию асинхронного режима.

ReadFile PROTO, ; Считывание в буфер из входного файла.


handle:DWORD, ; Дескриптор файла.
pBuffer:PTR BYTE, ; Указатель на буфер.
nBufsize:DWORD, ; Количество байт для считывания.
pBytesRead:PTR DWORD, ; Количество реально считанных байт.
pOverlapped:PTR DWORD ; Указатель на информацию асинхронного режима.

SetConsoleWindowInfo PROTO, ; Положение окна консоли.


nStdHandle:DWORD, ; Дескриптор консоли.
bAbsolute:DWORD, ; Тип координат.
pConsoleRect:PTR SMALL_RECT ; Указатель на границы.

WriteConsoleOutputAttribute PROTO,
outHandle:DWORD, ; Дескриптор выходного устройства.
pAttribute:PTR WORD, ; Атрибуты записи.
nLength:DWORD, ; Количество ячеек.
xyCoord:COORD, ; Координаты первой ячейки.
pCount:PTR DWORD ; Количество записанных ячеек.

WriteConsoleOutputCharacterA PROTO,
outHandle:DWORD, ; Дескриптор выходного устройства.
pBuffer:PTR BYTE, ; Указатель на буфер.
bufsize:DWORD, ; Размер буфера.
xyPos:COORD, ; Координаты первой ячейки.
pCount:PTR DWORD ; Счетчик.

SetConsoleCursorPosition PROTO,
outHandle:DWORD, ; Дескриптор выходного устройства.
coords:COORD ; Координаты X,Y экрана.

GetLocalTime PROTO, ; Системное время, как локальное


pSystemTime:PTR SYSTEMTIME ; Указатель на объект SYSTEMTIME

;************* Структуры для графических приложений


POINT STRUCT
X DWORD ?
Y DWORD ?
POINT ENDS

RECT STRUCT
left DWORD ?
top DWORD ?
right DWORD ?
bottom DWORD ?
RECT ENDS

MSGStruct STRUCT
msgWnd DWORD ?
msgMessage DWORD ?
msgWparam DWORD ?
msgLparam DWORD ?
msgTime DWORD ?
msgPt POINT <>

332 Глава 12. Справочный раздел

Стр. 332
отсутствует. 333

MSGStruct ENDS

WNDCLASS STRUC
style DWORD ?
lpfnWndProc DWORD ?
cbClsExtra DWORD ?
cbWndExtra DWORD ?
hInstance DWORD ?
hIcon DWORD ?
hCursor DWORD ?
hbrBackground DWORD ?
lpszMenuName DWORD ?
lpszClassName DWORD ?
WNDCLASS ENDS

;********** Прототипы функций для графических приложений

CreateWindowExA PROTO, ; Создание и регистрация класса window.


exWinStyle:DWORD,
className:PTR BYTE,
winName:PTR BYTE,
winStyle:DWORD,
X:DWORD,
Y:DWORD,
rWidth:DWORD,
rHeight:DWORD,
hWndParent:DWORD,
hMenu:DWORD,
hInstance:DWORD,
lpParam:DWORD

DefWindowProcA PROTO, ; Обработчик сообщений Windows по умолчанию.


hWnd:DWORD,
locMsg:DWORD,
wParam:DWORD,
lParam:DWORD

DispatchMessageA PROTO, ; Диспетчер сообщений в приложении.


pMsg:PTR BYTE

ExitProcess PROTO, ; Выход из текущего процесса.


exitCode:DWORD

FormatMessageA PROTO, ; Форматирование сообщения.


dwFlags:DWORD,
lpSource:DWORD,
msgID:DWORD,
languageID:DWORD,
lpBuffer:PTR BYTE,
nSize:DWORD,
pArgs:DWORD

GetMessageA PROTO,
lpMsg:PTR BYTE,
hWnd:DWORD,
firstMsg:DWORD,
lastMsg:DWORD

Глава 12. Справочный раздел 333

Стр. 333
GetModuleHandleA PROTO,
pString:PTR BYTE
GetWindowDC PROTO, ; Получить текущий контекст объекта Windows.
hWind:DWORD
GetWindowRect PROTO, ; Получить размеры текущего объекта Windows.
hWind:DWORD,
pRect:PTR RECT
LoadCursorA PROTO, ; Загрузить ресурсы курсора.
hInstance:DWORD,
pString:PTR BYTE
LoadIconA PROTO, ; Загрузить ресурсы пиктограмм.
hInstance:DWORD,
pString:PTR BYTE
LocalFree PROTO, ; Освободить локальную память, используя ее дескриптор.
hMem:DWORD
MessageBoxA PROTO, ; Отобразить всплывающее сообщение.
hWnd:DWORD,
lpText:PTR BYTE,
lpCaption:PTR BYTE,
style:DWORD
PostQuitMessage PROTO, ; Сообщить Windows о закрытии приложения.
exitCode:DWORD
RegisterClassA PROTO, ; Зарегистрировать новый оконный класс.
pWndClass:PTR WNDCLASS

ShowWindow PROTO, ; Показать окно.


hWnd:DWORD,
showState:DWORD

UpdateWindow PROTO, ; Отобразить окно.


hWnd:DWORD

Comment !
SelectObject PROTO, ; GDI32.LIB
hdc:DWORD
hGDIobject:DWORD
DeleteObject PROTO, ; GDI32.LIB
hGDIobject:DWORD
!

;*********** Переопределение имен Win32 в стандартные имена API.


GetModuleHandle TEXTEQU <GetModuleHandleA>
LoadCursor TEXTEQU <LoadCursorA>
LoadIcon TEXTEQU <LoadIconA>
RegisterClass TEXTEQU <RegisterClassA>
CreateWindowEx TEXTEQU <CreateWindowExA>
DispatchMessage TEXTEQU <DispatchMessageA>
GetMessage TEXTEQU <GetMessageA>
DefWindowProc TEXTEQU <DefWindowProcA>
FormatMessage TEXTEQU <FormatMessageA>
MessageBox TEXTEQU <MessageBoxA>

334 Глава 12. Справочный раздел

Стр. 334
отсутствует. 335

; Константы регистрации класса окна


COLOR_WINDOW = 5 ; Цвет window.
COLOR_BACKGROUND = 2 ; 2-голубой, F-серый.
IDI_APPLICATION = 32512 ; Пиктограмма приложения.
IDC_ARROW = 07f00h ; Курсор.
CW_USEDEFAULT = 80000000h ; Положение и размер по умолчанию.

;************* Константы стиля окна


WS_OVERLAPPED = 00000000h
WS_BORDER = 00800000h
WS_DLGFRAME = 00400000h
WS_SYSMENU = 00080000h
WS_MAXIMIZEBOX = 00010000h
WS_MINIMIZEBOX = 00020000h
WS_THICKFRAME = 00040000h
WS_CAPTION = WS_BORDER+WS_DLGFRAME
WS_VISIBLE = 10000000h

;************* Константы отображения окна


SW_SHOW = 05h

;************* Переменные обработчика ошибок


FORMAT_MESSAGE_ALLOCATE_BUFFER = 100h
FORMAT_MESSAGE_FROM_SYSTEM = 1000h

;************* Сообщения окна


WM_CLOSE = 0010h ; Закрыть окно.
WM_CREATE = 01h ; Создать окно.
WM_LBUTTONDOWN = 0201h ; Нажатие левой кнопки мыши.

;MB_ICONERROR = 010h
MB_ICONHAND = 10h
MB_ICONQUESTION = 20h
MB_ICONEXCLAMATION = 30h
MB_ICONASTERISK = 40h

MB_USERICON = 80h
MB_ICONWARNING = MB_ICONEXCLAMATION
MB_ICONERROR = MB_ICONHAND
MB_ICONINFORMATION = MB_ICONASTERISK
MB_ICONSTOP = MB_ICONHAND

;************* Сообщения кнопок окна


MB_OK = 0
MB_OKCANCEL = 1
MB_ABORTRETRYIGNORE = 2
MB_YESNOCANCEL = 3
MB_YESNO = 4
MB_RETRYCANCEL = 5
MB_CANCELTRYCONTINUE = 6
NULL EQU 0

MAIN_WINDOW_STYLE =
WS_VISIBLE+WS_DLGFRAME+WS_CAPTION+WS_BORDER+WS_SYSMENU \
+WS_MAXIMIZEBOX+WS_MINIMIZEBOX+WS_THICKFRAME

CONSOLE_SCREEN_BUFFER_INFO STRUCT

Глава 12. Справочный раздел 335

Стр. 335
dwSize COORD <>
dwCursorPos COORD <>
wAttributes WORD ?
srWindow SMALL_RECT <>
maxWinSize COORD <>
CONSOLE_SCREEN_BUFFER_INFO ENDS
.LIST

Подключаемый файл с макроопределениями


.NOLIST
COMMENT !
Список макроопределений:
mClrscr MACRO - Считка экрана.

mDump MACRO varName:REQ, useLabel


- Отображение переменной.
- useLabelis — дополнительно. Если не пусто, будет отображено имя.

mDumpMem MACRO address:REQ, itemCount:REQ, componentSize:REQ


- Дамп памяти.

mGotoxy MACRO X:REQ, Y:REQ


- Положение курсора.

mReadStr MACRO varName:REQ


- Считывание с входного потока.

mShow MACRO itsName:REQ, format:=<HIN>


- Отображение переменной или регистра в заданных форматах.

mShowRegister MACRO regName, regValue


- Отображение имени регистра с его содержимого.

mWrite MACRO text:REQ


- Вывод строкового литерала.

mWriteLn MACRO text:REQ


- Вывод строкового литерала и перевод каретки.

mWriteStr MACRO buffer:REQ


- Вывести строковую переменную.

mNewLine MACRO
- Новая строка.

mWriteSpace MACRO count


- Один или более пробел.

Конец перечня ***************************************** !

IsDefined MACRO symbol


IFDEF symbol
EXITM <-1> ;; True
ELSE

336 Глава 12. Справочный раздел

Стр. 336
отсутствует. 337

EXITM <0> ;; False


ENDIF
ENDM

mNewLine MACRO
mWrite <0dh,0ah>
ENDM

;------------------------------------------------------
mClrscr MACRO
call Clrscr
ENDM

;----------------------------------------------------
mDump MACRO varName:REQ, useLabel
IFB <varName>
EXITM
ENDIF
call Crlf
IFNB <useLabel>
mWrite "Variable name: &varName"
ELSE
mWrite " "
ENDIF
mDumpMem OFFSET varName, LENGTHOF varName, TYPE varName
ENDM

;------------------------------------------------------
mDumpMem MACRO address:REQ, itemCount:REQ, componentSize:REQ
IFB <address>
EXITM
ENDIF
IFB <itemCount>
EXITM
ENDIF
IFB <componentSize>
EXITM
ENDIF
push ebx
push ecx
push esi
mov esi,address
mov ecx,itemCount
mov ebx,componentSize
call DumpMem
pop esi
pop ecx
pop ebx
ENDM

;------------------------------------------------------
mGotoxy MACRO X:REQ, Y:REQ
IFB <X>
EXITM
ENDIF
IFB <Y>
EXITM
ENDIF

Глава 12. Справочный раздел 337

Стр. 337
push edx
mov dh,Y
mov dl,X
call Gotoxy
pop edx
ENDM

;------------------------------------------------------
mReadStr MACRO varName:REQ
IFB <varName>
EXITM
ENDIF
push ecx
push edx
mov edx,OFFSET varName
mov ecx,(SIZEOF varName) - 1
call ReadString
pop edx
pop ecx
ENDM

;---------------------------------------------------
mShow MACRO itsName:REQ, format:=<HIN>
LOCAL tempStr
; format — строка из следующих символов:
; H - шестнадцатеричное
; D - десятичное без знака
; I - десятичное со знаком
; B - двоичное
; N - добавление перевода каретки
; Отображение в каждом указанном формате
.data
tempStr BYTE " &itsName = ",0
.code
pushad
IF (OPATTR (itsName)) AND 00010000b
MSHOWITSNAMETYPE = 0
FOR reg8,<al,ah,bl,bh,cl,ch,dl,dh>
IFIDNI <itsName>,<reg8>
MSHOWITSNAMETYPE = 1
movzx ecx,itsName
movsx edx,itsName
ENDIF
ENDM

FOR reg16,<ax,bx,cx,dx,si,di,bp,sp>
IFIDNI <itsName>,<reg16>
MSHOWITSNAMETYPE = 2
movzx ecx,itsName
movsx edx,itsName
ENDIF
ENDM

FOR regseg,<cs,ds,es,fs,gs,ss>
IFIDNI <itsName>,<regseg>
MSHOWITSNAMETYPE = 2
mov ax,itsName
movsx edx,ax

338 Глава 12. Справочный раздел

Стр. 338
отсутствует. 339

movzx ecx,ax
ENDIF
ENDM

FOR reg32,<eax,ebx,ecx,edx,esi,edi,ebp,esp>
IFIDNI <itsName>,<reg32>
MSHOWITSNAMETYPE = 4
mov ecx,itsName
mov edx,itsName
ENDIF
ENDM

ELSE
MSHOWITSNAMETYPE = TYPE itsName
IF MSHOWITSNAMETYPE EQ 4
mov ecx,itsName
mov edx,ecx
ELSE
movzx ecx,itsName
movsx edx,itsName
ENDIF

ENDIF
push edx
mov edx,OFFSET tempStr
call WriteString
pop edx

FORC fmt,<format>
IFIDNI <fmt>,<H> ;; формат H.
mov eax,ecx
mov ebx,MSHOWITSNAMETYPE
call WriteHexB
mWrite "h "
ENDIF

IFIDNI <fmt>,<D> ;; формат D.


mov eax,ecx
call WriteDec
mWrite "d "
ENDIF

IFIDNI <fmt>,<I> ;; формат I.


mov eax,edx
call WriteInt
mWrite "d "
ENDIF

IFIDNI <fmt>,<B> ;; формат B.


mov eax,ecx
mov ebx,MSHOWITSNAMETYPE
call WriteBinB
mWrite "b "
ENDIF

IFIDNI <fmt>,<N> ;; N
call Crlf
ENDIF

Глава 12. Справочный раздел 339

Стр. 339
ENDM
popad
ENDM

;---------------------------------------------------
; Альтернативное имя для mShowRegister:
ShowRegister TEXTEQU <mShowRegister>
;---------------------------------------------------
mShowRegister MACRO regName, regValue
LOCAL tempStr
.data
tempStr BYTE " &regName=",0
.code
push eax
push edx
mov edx,OFFSET tempStr
call WriteString
pop edx
mov eax,regValue
call WriteHex
pop eax
ENDM

;------------------------------------------------------
mWrite MACRO text:REQ
LOCAL string
IFB <text>
EXITM
ENDIF
.data ;; Локальные данные.
string BYTE text,0
.code
push edx
mov edx,OFFSET string
call WriteString
pop edx
ENDM

;------------------------------------------------------
mWriteLn MACRO text:REQ
IFB <text>
EXITM
ENDIF
mWrite text
call Crlf
ENDM

;------------------------------------------------------
mWriteSpace MACRO count
LOCAL spaces
.data
IFB <count>
spaces BYTE ' ',0
ELSE
spaces BYTE count DUP(' '),0
ENDIF
.code
push edx

340 Глава 12. Справочный раздел

Стр. 340
отсутствует. 341

mov edx,OFFSET spaces


call WriteString
pop edx
ENDM

;------------------------------------------------------
mWriteStr MACRO buffer:REQ
IFB <buffer>
EXITM
ENDIF
push edx
mov edx,OFFSET buffer
call WriteString
pop edx
ENDM
.LIST

Перечень команд DOS для Windows XP


Команда Действие
ASSOC Отображает или модифицирует установленные расширения файлов
AT Устанавливает график запуска отдельных программ в назначенные время и день
ATTRIB Отображает или изменяет атрибуты файлов
BREAK Устанавливает или сбрасывает проверку комбинации клавиш <CTRL+C>
CACLS Отображает или модифицирует список контроля доступа (ACL) файлов
CALL Вызывает одну командную программу из другой
CD Отображает имя или изменяет текущий каталог
CHCP Отображает или устанавливает номер активной кодовой страницы
CHDIR Отображает или изменяет имя текущего каталога
CHKDSK Проверяет диск и отображает отчет
CHKNTFS Отображает или модифицирует проверку диска во время загрузки
CLS Очищает экран
CMD Запускает новый экземпляр командного интерпретатора
COLOR Устанавливает цвета консоли
COMP Сравнивает содержимое нескольких файлов
COMPACT Отображает или изменяет сжатые файлы разделов NTFS
CONVERT Преобразовывает тома FAT в NTFS. Изменения нельзя делать на текущем томе
COPY Копирует один или несколько файлов
DATE Отображает или устанавливает дату
DEL Удаляет один или несколько файлов
DIR Отображает список файлов и подкаталогов каталога
DISKCOMP Сравнивает содержимое двух флоппи&дисков
DISKCOPY Копирует содержимое одного флоппи&диска в другой
DOSKEY Редактирование командных строк, повторный вызов команд Windows и создание макросов

Глава 12. Справочный раздел 341

Стр. 341
Продолжение таблицы
ECHO Отображение сообщений или переключение режима повторения (эхо)
ENDLOCAL Окончание изменений локализации среды в командных файлах
ERASE Удаление одного или нескольких файлов
EXIT Выход из программы командного интерпретатора CMD.EXE
FC Сравнение нескольких файлов и отображение различий между ними
FIND Поиск текстовой строки в одном или нескольких файлах
FINDSTR Поиск строк в файлах
FOR Запуск определенных команд для каждого файла из набора файлов
FORMAT Форматирование диска для использования с Windows
FTYPE Отображение или модификация типов файлов, используемых в перечне соответствий
типов и расширений
GOTO Перенаправляет интерпретатор команд на строку с меткой в командной программе
GRAFTABL Отображает кодовую страницу для расширенного набора символов
HELP Отображает справочную информацию о командах
IF Оператор условия в командных программах
LABEL Создает, изменяет или удаляет метку тома
MD Создает каталог
MKDIR Создает каталог
MODE Конфигурация системных устройств
MORE Отображает последовательно экраны при выводе большого количества информации
MOVE Перемещает один или несколько файлов из одного каталога в другой
PATH Отображает или устанавливает пути для поиска файлов
PAUSE Приостанавливает выполнение командного файла и отображает сообщение
POPD Восстанавливает прежнее значение текущего каталога, сохраненного командой PUSHD
PRINT Выдача на печать текстового файла
PROMPT Изменение вида запроса
PUSHD Сохраняет текущий каталог перед изменением
RD Удаляет каталог
RECOVER Восстанавливает доступную информацию с запорченных дисков
REM Используется для записи комментариев в командных файлах или файлах CONFIG.SYS
REN Переименовывает файлы
RENAME Переименовывает файлы
REPLACE Заменяет файл
RMDIR Удаляет каталоги
SET Отображает, устанавливает или удаляет переменные окружения Windows
SETLOCAL Начало изменений по локализации в командном файле
SHIFT Сдвигает позиции заменяемых параметров в командных файлах
SORT Сортировка
START Запуск определенной команды или программы в отдельном окне

342 Глава 12. Справочный раздел

Стр. 342
отсутствует. 343

Окончание таблицы
SUBST Создание виртуального диска
TIME Отображение или установка системного времени
TITLE Установка заголовка для окна с сеансом CMD.EXE
TREE Графическое отображение структуры каталогов
TYPE Отображение содержимого текстового файла
VER Отображение версии Windows
VERIFY Запускает проверку правильности записи файлов на диск
VOL Отображает метку тома и серийный номер
XCOPY Копирует файлы и каталоги

Глава 12. Справочный раздел 343

Стр. 343
Приложение

Ответы на контрольные
вопросы
Глава 2
1. Процессор непосредственно производит вычислительные действия, которые ле&
жат в основе выполнения любой программы. Можно сказать, что процессор &&&& это
и есть компьютер, так как именно под определенный тип процессора разрабаты&
ваются все остальные узлы компьютера.
2. Процессор, шина, память, устройства ввода&вывода.
3. Операционная система представляет программу с набором команд, которая управ&
ляет всей аппаратной частью компьютера.
4. Для первых компьютеров операционная система разрабатывалась с учетом аппа&
ратной части. В настоящее время это взаимозависимый процесс и при разработке
операционной системы учитывается аппаратная часть, так же как и при разработ&
ке компьютера учитываются возможности операционной системы.
5. Потому что изменяется аппаратная часть.
6. Windows, Linux.
7. Для хранения выполняемых программ и результатов промежуточных вычислений.
8. Кэш&память необходима для согласования скорости работы процессора и опера&
тивной памяти. Без кэш&памяти невозможно получить высокое быстродействие.
9. Регистры находятся непосредственно в процессоре, поэтому их можно рассматри&
вать как сверхоперативную память, где сохраняются как необходимые при выпол&
нении программ данные, так и результаты промежуточных вычислений.
10. Аккумулятор, базовый регистр, счетчик, регистр данных.
11. CH, 8 бит.
12. Счетчик.
13. 32 бита.
14. При доступе к оперативной памяти происходит выход за пределы процессора, т.е. вы&
полняется последовательное обращение к шине и устройству памяти. Регистры на&
ходятся непосредственно в процессоре и при обращении к ним нет необходимости
выполнять дополнительные операции.
15. Защищенный режим позволяет использовать плоскую модель памяти, что повы&
шает эффективность выполнения больших программ. Все программы для опера&
ционной системы Windows должны использовать плоскую модель памяти.
16. Нет.

Стр. 344
отсутствует. 345

17. В настоящее время наиболее распространенные типы дисплея имеют разрешение


не менее 800×600 пикселей и 16 миллионов цветов.
18. Сегмент кода, сегмент данных, сегмент стека, дополнительный сегмент.
19. Регистр AH является старшим байтом регистра AX.
20. В регистре AX.
21. Регистр CS.
22. Регистр SS.
23. Регистр BP.
24. SI, DI.
25. Это флаги направления, разрешения прерываний и трассировки, которые обозна&
чаются как DF, IF и TF.
26. Это флаги переполнения, знака, нуля, дополнительного переноса и переноса.
27. Флаг CF.
28. Флаг SF.
29. Стек размещается в оперативной памяти.

Глава 3
1. Да.
2. Можно.
3. 8, 16, 32.
4. От 0 до 2^16–1.
5. Как команды, так и данные размещаются в оперативной памяти в виде набора чи&
сел, но команды размещаются в участке памяти, выделенном для команд (сегмент
code), а данные располагаются в сегменте данных.
6. Основание системы счисления &&&& это число знаков, используемых для отображе&
ния чисел. В десятичной системе счисления это цифры: 1, 2, 3, 4, 5, 6, 7, 8, 9, 0.
В шестнадцатеричной системе счисления в качестве цифр используются следую&
щие символы: 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, F.
7. Записанные в шестнадцатеричной системе счисления числа хорошо воспринима&
ются человеком, с ними удобно работать и, самое главное, они легко преобразо&
вываются в двоичные числа и обратно.
8. Числа без знака используют все восемь бит для получения значения. Байт со зна&
ком использует только семь бит для получения значения, а старший восьмой бит
зарезервирован для знака, при этом 0 соответствует положительному значению,
а 1 &&&& отрицательному.
9. Дополнение до двух (двоичное дополнение) &&&& это число, которое получается из ис&
ходного числа после изменения всех единиц на нули, а нулей на единицы (инвер&
сия) и прибавления к полученному числу единицы.
10. Строка символов представляет в памяти последовательность байт. Например, чи&
словым кодам строки ‘‘ABC123’’ будет соответствовать последовательность значе&
ний 41h, 42h, 43h, 31n, 32h и 33h.

Приложение. Ответы на контрольные вопросы 345

Стр. 345
11. Применяется двоичное представление числа, если числа используются для вычис&
лений. И наоборот, лучше хранить коды ASCII, если данные значения будут ис&
пользоваться для отображения символов на экране. Например, число 123 можно
сохранить в памяти двумя способами: как последовательность кодов ASCII для чи&
сел 1, 2 и 3 или как один байт со значением 123.
12. Константы &&&& это неизменные значения, обращение к которым происходит непо&
средственно. Переменные &&&& это значения, которые могут изменяться в процессе
выполнения программы. Для обращения к этим значениям используются ссылки.
13. Утверждения бывают двух типов: команды и директивы.
14. С помощью метода ‘‘сверху вниз’’.
15. Ставится задача и составляется проект программы. Вводятся команды программы
в компьютер с помощью редактора. Транслируется программа с помощью ассембле&
ра. Результат работы ассемблера преобразуется в исполняемый модуль с помощью
компоновщика. Выполнение программы. Проверка результатов и поиск ошибок.
16. TITLE, .386, .MODEL, .STACK, .DATA, .CODE, main PROC, main ENDP, END main.
17. Отладчик представляет собой программу, которая позволяет отображать на экране
значения необходимых переменных, получать состояние всех регистров и ячеек
памяти при пошаговом выполнении программы, вносить изменения в программу,
указывать точки останова и многое другое.
18. DOS работает в режиме эмуляции.
19. Нет.

Глава 4
1. /SUBSYSTEM:CONSOLE.
2. Функции Windows API, работающие с текстом, обычно имеют две версии: одна для
работы с 8&разрядными символами ASCII/ANSI (имена оканчиваются символом A)
и другая для работы с расширенным набором символов, включая Unicode (имена
оканчиваются символом W).
3. Windows NT, 2000 и XP.
4. Типы MASM: BYTE, PTR BYTE, DWORD, SDWORD, WORD.
5. Дескриптор является 32&разрядным целым числом без знака, которое уникально
идентифицирует объект.
6. Функция GetStdHandle возвращает дескриптор потока для консоли: вход, выход
или выход ошибок.
7. Прототип функции &&&& это предварительное описание заголовка функции, необхо&
димое для сообщения системе о наличии такой функции.
8. В подключаемом файле описываются прототипы функций.
9. Структуры данных, такие как COORD и SMALL_RECT. Структура COORD содержит
координаты экрана X и Y, измеряемые в символах. Структура SMALL_RECT содер&
жит координаты окна, также измеряемые в символах.
10. Функция ReadConsoleOutputCharacter.
11. Библиотека загрузочных модулей служит для хранения отлаженных процедур.
12. *.obj, *.lib.

346 Приложение. Ответы на контрольные вопросы

Стр. 346
отсутствует. 347

13. Все переменные, константы или процедуры, объявленные внутри модуля, являют&
ся закрытыми по умолчанию. Такая концепция называется инкапсуляцией.
14. Функция CreateFile или создает новый файл или открывает уже существующий.
15. Байт с кодом символа и байт атрибутов.

Глава 5
1. Переменная является символическим именем для определенного места в памяти,
где размещены данные.
2. Директивы размещения данных используются для заполнения памяти.
3. Число значений, распределяемых с помощью одной директивы размещения дан&
ных, может быть ограничено только размером доступной памяти.
4. Строка символов может быть представлена переменной, которая указывает сме&
щение начала строки.
5. С помощью оператора DUP можно продублировать одну или несколько перемен&
ных после размещения их в памяти.
6. Указателем называется переменная, в которой сохраняется смещение другой пе&
ременной или подпрограммы.
7. Ассемблер переставляет байты слова, когда сохраняет его в памяти. Последний
байт слова будет иметь младший адрес в памяти.
8. Константа является неизменяемым значением, которому присвоено символиче&
ское имя.
9. Да.
10. С помощью директивы .MODEL можно выбрать одну из стандартных моделей па&
мяти для программ на языке ассемблера.
11. Tiny (тонкая), small (малая), medium (средняя), compact (компактная), large
(большая), huge (огромная), flat (плоская).
12. Сегменты являются строительными блоками программы и каждый из них предна&
значен для определенной цели.
13. Для создания участков памяти с однородными данными, т.е. программа, данные, стек.
14. При плоской модели памяти.
15. Оператор SIZE возвращает общее число байт, выделенных для переменной. Опе&
ратор SIZE рассчитывается как длина (LENGTH), умноженная на тип (TYPE).
16. Для использования соответствующего набора команд.
17. 184100h.
18. Когда с помощью директив DB, DW, DD или других создаются переменные, ассемб&
лер присваивает им определенные атрибуты (байт, слово, двойное слово и т.д.) в за&
висимости от их размера. Этот атрибут проверяется, когда переменная использует&
ся, и если ее тип не соответствует тому, который необходим, происходит ошибка.
19. Флаг переполнения устанавливается, когда в результате арифметической операции
получается число со знаком, превышающее максимально допустимое для операн&
да&получателя. Установленный флаг переполнения говорит о том, что в операнде&
получателе находится некорректное число.

Приложение. Ответы на контрольные вопросы 347

Стр. 347
20. Регистры, непосредственные и косвенные операнды. Базовый и индексный опе&
ранды. Базо&индексные операнды. Базо&индексные операнды с дополнительным
смещением.
21. Косвенным операндом является регистр, который содержит смещение данных
в памяти.
22. В языке ассемблера выражением является комбинация операторов и операндов,
которые преобразовываются ассемблером в числовую константу.
23. Арифметические операторы, логические операторы.

Глава 6
1. Подпрограммы сервисных прерываний оказывают существенную помощь при ра&
боте с консолью, диском, принтером при вводе&выводе или контроле времени
и размещении памяти. Эти подпрограммы позволяют не вдаваться в детали про&
граммирования на уровне аппаратуры и обеспечивают работу программы на лю&
бых устройствах с процессорами архитектуры x86.
2. Указатель стека содержит адрес последнего элемента, помещенного в стек.
3. INT 21h (подпрограммы DOS). Подпрограммы для ввода&вывода, управления
файлами и управления памятью, называемые вызовами функций DOS.
4. Обобщающий термин прерывание используется для двух случаев: аппаратное пре&
рывание &&&& это сигнал от любого устройства системы для процессора, который по
этому сигналу должен обслужить данное устройство, и программное прерывание,
которое создается программами BIOS или DOS для вызова сервисных подпро&
грамм.
5. 0 && 255.
6. Ссылки на точки входа подпрограмм.
7. Подпрограммы прерываний BIOS работают быстрее.
8. Прикладные программы не могут непосредственно управлять аппаратными уст&
ройствами по той простой причине, что в разных системах это делается по&
разному. Поэтому не будет переносимости программ.
9. INT 16h.
10. При написании программ для 16&разрядного режима можно использовать преры&
вание INT 21h, предназначенное для вызова функций DOS. При этом может вы&
зваться около 90 различных функций.
11. 02h (вывод символа). Функция посылает символ на стандартное устройство вывода.
12. Скан&код &&&& это код цифровой клавиши, определяемый по месту расположения
клавиши на клавиатуре.
13. 0Dh.
14. Атрибут цвета, негативного отображения, мигания, подчеркивания, яркости.
15. 0Ah.
16. 00h.
17. В сторону младших адресов.
18. LIFO (last&in first&out) в переводе на русский означает ‘‘последним вошел &&&& пер&
вым вышел’’.

348 Приложение. Ответы на контрольные вопросы

Стр. 348
отсутствует. 349

Глава 7
1. AND, OR, XOR, NOT, NEG, TEST, BT, BTR, BTC, BTS, CMP.
2. Операторы AND, OR, NOT и XOR выполняют поразрядные операции для целых чи&
сел во время трансляции.
3. Команда JMP &&&& это команда безусловного перехода, а команда JZ &&&& это услов&
ный переход.
4. Флаг нуля.
5. Флаги нуля и переполнения.
6. JCXZ.
7. Это условные переходы, основанные на общем сравнении. Процессор делает пе&
реход на основе анализа флагов нуля, переноса, паритета или регистров CX и ECX.
При этом по команде JL совершается переход, если оператор 1 меньше операто&
ра 2 (op1<op2). А по команде JNE совершается переход, если операторы не равны.
8. По команде JA совершается переход, если оператор 1 больше оператора 2 (op1>op2).
А по команде JAE совершается переход, если оператор 1 больше или равен опера&
тору 2 (op1>=op2).
9. Нет.
10. Флаг переноса.

Глава 8
1. Для подсчета суммы нужно сложить два числа в кодировке ASCII, но после сложе&
ния необходимо откорректировать результат. Для подобных операций в языке ас&
семблера можно использовать команду корректировки сложения AAA.
2. Команда SAL.
3. Команда SHR.
4. Команда SAR.
5. Бит, находившийся ранее во флаге переноса, теряется.
6. Произвести сдвиг на 4 бита влево.
7. Произвести сдвиг на 2 бита вправо.

Глава 9
1. myStruct STRUC
first DB ?
second DB ?
myStruct ENDS
2. mPushData MACRO
PUSH EAX
PUSH EBX
PUSH ECX
PUSH EDX
ENDM
3. К более высокой скорости работы.

Приложение. Ответы на контрольные вопросы 349

Стр. 349
4. mWrite MACRO text
LOCAL string ;; Локальные метки.
.data
string BYTE text,0 ;; Определение строки.
.code
PUSH EDX
MOV EDX,OFFSET string
CALL Writestring
POP EDX
ENDM
5. Директива условия IFB позволяет выполнять проверку пропущенных аргументов.
6. Директива IFIDN <арг1>,<арг2> разрешает трансляцию, если оба аргумента рав&
ны. Сравнение можно сделать чувствительным к регистру, если используется ди&
ректива IFIDNI.
7. EXITM.
8. Директива IFIDNI сравнивает два идентификатора с учетом регистра, включая
имена параметров макроопределения. Директива IFIDN выполняет проверку без
учета регистра.
9. Директива IFDEF разрешает трансляцию, если указываемое вместе с ней имя оп&
ределено.
10. ENDIF.
11. mWriteLn MACRO text:=<" ">
mWrite text
CALL Crlf
ENDM
12. Оператор замены.
13. Оператор выделения символа.

Глава 10
1. Структура POINT определяет координаты X и Y точки экрана, измеренные в пик&
селях. Это ее свойство можно использовать при работе с графическими объектами,
окнами и при щелчках мыши.
2. Структура WNDCLASS определяет класс окна. Каждое окно в программе должно
принадлежать определенному классу и в каждой программе должен быть указан
класс основного окна. Этот класс должен быть зарегистрирован в операционной
системе перед тем, как произойдет обращение к основному окну.
3. IpfnWndProc DWORD ? ; Указатель на функцию
WinProc. style DWORD ? ; Опции стиля окна.
hlnstance DWORD ? ; Дескриптор текущей программы.
4. INVOKE MessageBox, hWnd, ADDR QuestionText,
ADDR QuestionTitle, MB_OK + MB_ICONQUESTION.
5. MB_OK и MB_YESNO.
6. Процедура WinMain получает дескриптор текущей программы, загружает пикто&
граммы и курсоры текущей программы; регистрирует класс основного окна програм&
мы и идентифицирует процедуру, которая будет выполнять обработку сообщений

350 Приложение. Ответы на контрольные вопросы

Стр. 350
отсутствует. 351

для окна; создает основное окно; отображает и обновляет основное окно; начина&
ет циклический процесс приема и диспетчеризации сообщений.
7. Процедура WinProc принимает и обрабатывает все сообщения о событиях, отно&
сящиеся к окну. Большинство событий создает пользователь, щелкая мышкой
и перетаскивая объекты, нажимая клавиши клавиатуры и т.д. Процедура декоди&
рует каждое сообщение о событии, и если сообщение распознано, то обращается
к соответствующим функциям для выполнения задач, связанных с окном.
8. Процедура ErrorHandler будет вызываться, если возникнет сбой при регистра&
ции и создании основного окна программы.
9. Не зная языка ассемблера или не зная, как его использовать с языками высокого
уровня, программисты будут лишены мощного и гибкого инструмента програм&
мирования. Используя языки ассемблера и высокого уровня, можно совместить
отличный интерфейс пользователя и быстродействие разрабатываемой программы.

Приложение. Ответы на контрольные вопросы 351

Стр. 351
Ïðåäìåòíûé óêàçàòåëü

A Входной буфер, 96
Выборка, 28
ASCII, 60 Вызов, 169
дальний, 268
B Выполнение, 28
Выражение, 146
BIOS, 37 константное, 62

L Д
LIFO, 167 Дальний указатель, 97
LOCAL, 234 Декодирование, 28
Дескриптор, 73; 98
А Директива, 63; 64
Дополнение
Адрес
до двух, 58; 192
возврата, 163
до единицы, 191
относительный, 49
физический, 49 Драйвер, 51
эффективный, 49 Драйвер устройства, 19
Альтернативное имя, 152
Аргумент, 168; 236 З
Арифметический сопроцессор, 141
Ассемблер, 18 Заголовочный файл, 98
Ассоциативность операторов, 150
И
Б Имя, 64
Базовое число, 54; 55 Инкапсуляция, 107
Байт, 54 Интерфейс прикладного
атрибутов, 179 программирования, 96
Бит, 54
Булевы команды, 188
К
В Ключевое слово, 64
Кодировка ASCII, 223
Ветвление, 153 Команда, 63
Виртуальная память, 48 Командная консоль, 68
Вложение процедур, 164 Командный файл, 84
Внутренний счетчик команд, 272
Комментарии, 231
Возврат, 169
Комментарий, 61
Вход
активный, 262
Компоновщик, 18; 65
неактивный, 262 Консоль, 272

Стр. 352
отсутствует. 353

Константа Описание структуры, 227


символическая, 63 Основание системы счисления, 55
символьная, 63 Отладчик, 18; 66; 89
Кэш&память, 24; 36
П
Л
Память
Литерал видео, 36
цифровой, 62 виртуальная, 45
Логическая структура, 195 динамическая, 36
статическая, 36
Параметр, 168
М Передача управления, 153
Макроопределение, 230; 231; 233; 245 Переменная, 64; 129
вложенное, 235 записи, 216
текста, 135 Переопределение сегмента, 156
Маскирование бит, 189 Пересылка данных, 138
Математический сопроцессор, 21 Переход, 169
Метка, 64 безусловный, 153
Микропрограмма, 20 условный, 154
Платформа, 96
Многопоточность, 47
Платы расширения, 37
Множественная инициализация, 131
Подпрограмма, 169
Моделирование нажатия клавиши, 177
Подпрограммы
Модель
обслуживания прерываний, 176
сегментированная, 48
Порт
страничная, 48
параллельный, 38
Модель памяти, 135 последовательный, 38
Модуль Прерывание, 44
исполняемый, 65 аппаратное, 39; 174
объектный, 65 программное, 174
Монитор, 35; 42 Приоритетное назначение регистров, 25
Программа
Н интерфейсная, 19
исходная, 65
Набор непереносимая, 19
инструментальных средств разработки, 96 объектная, 65
символов, 96 перемещаемая, 65
Прокрутка окна, 184
О Процедура, 163; 166; 169
Процесс, 44
Обработчик прерываний, 176 Процессор, 23
Операнд, 61 выборка, 23
непосредственный, 144 декодирование, 23
отправитель, 138 исполнение, 23
память, 144
получатель, 138 Р
регистр, 144
Оператор Разрешение трансляции, 236
$, 132 Регистр
DUP, 132 общего назначения, 25

Предметный указатель 353

Стр. 353
состояния и управления, 25 Т
флагов, 25; 27
Регистр флагов, 30 Таблица векторов прерываний, 175
Регистры, 24 Тип
Режим команды, 63
виртуальный, 22 Трассировка, 28
защищенный, 21; 48
реальный, 21; 48
системного управления, 48
У
Указатель, 132; 150
С Упакованные десятичные числа, 225
Управляющие клавиши, 265
Сегмент Условный переход, 195
данных, 70 Утверждение, 63
кодов, 70 Утилиты, 18
логический, 136
программный, 136
стека, 70 Ф
Сегментация, 48 Файл, 45
Симметричная многопроцессорная Фактор повторения, 185
обработка, 47 Функция, 169
Система команд, 20
Системная плата, 34
Скан&код, 265 Ш
Слово, 54 Шестнадцатеричные символы, 55
Смещение, 27; 49 Шина
переменной, 270 PCI, 35
Совместимость сверху вниз, 20 адреса, 24
Стек, 70; 166 внутренняя, 24
указатель, 167 данных, 24
Стековый фрейм, 168 системная, 24; 34
Страница, 181
Страничная организация памяти, 46
Строка
Э
символов, 131 Экранный буфер, 96
Структура, 227 Элемент управления, 73
Суперскалярная архитектура, 22 Эффективный адрес, 144
Счетчик сдвига, 209
Я
Язык ассемблера, 18

354 Предметный указатель

Стр. 354
Научнопопулярное издание

Геннадий Владимирович Галисеев

Ассемблер для Win 32


Самоучитель

Литературный редактор П.Н. Мачуга


Верстка В.И. Бордюк
Художественный редактор В.Г. Павлютин
Корректор Л.В. Чернокозинская

Издательский дом ‘‘Вильямс’’


127055, г. Москва, ул. Лесная, д. 43, стр. 1

Подписано в печать 31.01.2007. Формат 70х100/16.


Гарнитура Times. Печать офсетная.
Усл. печ. л. 29,67. Уч.&изд. л. 18,47.
Тираж 3000 экз. Заказ № 0000.

Отпечатано по технологии CtP


в ОАО ‘‘Печатный двор’’ им. А. М. Горького
197110, Санкт&Петербург, Чкаловский пр., 15.

Стр. 355
ЯЗЫК АССЕМБЛЕРА
ДЛЯ ПРОЦЕССОРОВ INTEL

Кип Р. Ирвин В основу четвертого издания


этой книги положено описание
архитектуры процессоров
фирмы Intel, называемой
IA- 32, сделанное с точки
зрения программиста.
По сравнению с третьим
изданием, книга полностью
переписана, и теперь
основной акцент в ней
сделан на создании
32-разрядных приложений
для системы Windows. Ее
отличает последовательный
и методически грамотный
подход к изложению
материала. Материал
данной книги подобран
в соответствиие с ее
первоначальным замыслом —
научить студентов писать и
отлаживать программы на
уровне машинных кодов. Она
никогда не заменит собой
полноценный учебник по
архитектуре компьютеров,
www.williamspublishing.com но позволит студентам
получить из первых рук
бесценный опыт в написании
программ и продемонстрирует,
как на самом деле
работает компьютер.

ISBN 5-8459-0779-9 в продаже

Стр. 356
МОДЕРНИЗАЦИЯ И РЕМОНТ ПК
17-е издание

Скотт Мюллер Эта книга — самое полное


издание о компонентах
современных персональных
компьютеров. Здесь подробно
описывается каждый
компонент современного ПК —
от процессора до монитора
и мыши. Вы познакомитесь
с новейшими компьютерными
технологиями, узнаете, какие
преимущества обеспечивают
беспроводные сети, какое
аппаратное обеспечение
наиболее перспективно
и как на его основе собрать
оптимальную компьютерную
систему. Книга окажется
бесценным источником
информации, если вы хотите
узнать о преимуществах
двухъядерных процессоров
последних поколений,
разобраться в принципе
работы блоков питания
и даже просто понять,
www.williamspublishing.com следует ли приобретать
систему, оснащенную парой
видеоадаптеров. Кроме того,
эта книга — превосходный
справочник, который
позволит проследить
развитие персональных
компьютеров от момента
их появления до наших дней.
ISBN 978-5-8459-1126-1 в продаже

Стр. 357
MICROSOFT WINDOWS VISTA
САМОУЧИТЕЛЬ

О.А. Меженный Данная книга предназначена


для пользователей, которые
хотят научиться работать
с Windows Vista. Материал
рассчитан прежде всего на
читателей, не имеющих опыта
применения предыдущих
версий операционной системы
Windows. В то же время книга
может быть полезна и тем,
кому ранее приходилось иметь
дело с этой ОС, и кто теперь
стремится освоить новую
версию — Windows Vista.
На страницах книги читатель
найдет необходимые сведения
о новшествах Windows
Vista и интерфейсе этой
операционной системы,
об использовании окон и
программ, о манипулировании
файлами и папками, работе
с Интернетом, электронной
почтой и мультимедиа,
а также о настройке
компьютера для работы
www.dialektika.com нескольких пользователей.

ISBN 978-5-8459-1225-1 в продаже

Стр. 358
MICROSOFT WINDOWS XP
САМОУЧИТЕЛЬ
2 ИЗДАНИЕ
О.А. Меженный Книга предназначена для
тех, кто хочет научиться
работать с Windows ХР. Здесь
читатель найдет необходимые
сведения об интерфейсе,
справочной системе и методах
использования окон и
программ этой операционной
системы, описание приемов
манипулирования файлами и
папками, инструкции по работе
с Интернет, электронной
почтой и мультимедиа,
а также рекомендации
настройке компьютера для
обеспечения работы на нем
нескольких пользователей.
Материал рассчитан прежде
всего на читателей, не
имеющих опыта использования
предыдущих версий этой
операционной системы. В то
же время книга может быть
полезна и тем, кому ранее
приходилось иметь дело с
предыдущими версиями
www.dialektika.com Windows и кто теперь
стремится освоить более новую
версию этой операционной
системы (Windows ХР SP2).

ISBN 978-5-8459-1005-9 в продаже

Стр. 359
JAVASCRIPT
КАРМАННЫЙ СПРАВОЧНИК

Кристиан Уэнц

Этот краткий справочник


составлен в удобной
и доступной форме
по принципу разговорника
и содержит самый
основной код и команды
для решения типичных задач,
возникающий при разработке
Web-приложений JavaScript
и AJAX, включая применение
изображений и анимации,
таблиц CSS, DOM
и DHTML, cookie-наборов
и форм, окон и рамок,
Web-служб, технологии
AJAX и встраиваемых
мультимедийных средств.
Книга адресована тем,
кто лишь начинает
программировать на JavaScript
www.williamspublishing.com и еще не досконально
разбирается в коде
Web-приложений.

ISBN 978-5-8459-1186-5 в продаже

Стр. 360
LINUX
КАРМАННЫЙ СПРАВОЧНИК

Скотт Граннеман Данная книга представляет


собой краткое пособие
по основным командам
операционной системы
Linux. Читатель найдет
в ней описание большинства
команд, необходимых ему
в повседневной работе.
В первых главах представлены
те средства, с которых
любой новичок начинает
знакомство с неизвестной
ему операционной системой.
В них рассматриваются
вопросы вывода на экран
информации о каталогах,
перехода из одного каталога
в другой, создания каталогов,
отображения содержимого
файлов и т.д. По мере чтения
книги материал усложняется.
Читатель получает
представление о работе
с принтерами, правах доступа
к файлам, архивировании
www.williamspublishing.com и сжатии данных, поиске
информации и др.
В книгу включено большое
количество примеров,
иллюстрирующих
использование каждой
описанной в ней команды.

ISBN 978-5-8459-1118-6 в продаже

Стр. 361
ЯЗЫК
ПРОГРАММИРОВАНИЯ C
второе издание

Брайан Керниган,
Деннис Ритчи

Классическая книга по языку C,


написанная самими
разработчиками этого языка.
Книга является как практически
исчерпывающим справочником,
так и учебным пособием по
самому распространенному языку
программирования. Предлагаемое
второе издание книги было
существенно переработано по
сравнению с первым в связи
с появлением стандарта ANSI C,
для которого она частично
послужила основой. Для
изучения книги требуются знания
основ программирования
и вычислительной техники.
Книга предназначена для
широкого круга программистов
и компьютерных специалистов.
Может использоваться как
www.williamspublishing.com учебное пособие для вузов.

ISBN 978"5"8459"0891"4 в продаже

Стр. 362
JAVA 2
БИБЛИОТЕКА ПРОФЕССИОНАЛА
ТОМ 1. ОСНОВЫ
СЕДЬМОЕ ИЗДАНИЕ

К. Хорстманн Книга адресована прежде всего


программистам-профессионалам
Г. Корнелл и представляет собой
исчерпывающий справочник и
методическое пособие по основам
программирования на языке Java.
Однако это не просто учебник по
синтаксису языка. Назначение
книги — обучить методам
объектно-ориентированного
программирования и научить
справляться с основными
проблемами в этой области.
В книге подробно описаны
объектно-ориентированное
программирование, средства
отражения, proxу-объекты,
классы Java, в том числе
интерфейсы и внутренние
классы, обработка событий, пакет
Swing и создание на его базе
графического пользовательского
интерфейса, генерация и
обработка исключений,
использование потоков ввода-
вывода, сериализация объектов.
Работа с книгой не требует опыта
программирования на языке
С++ и применения методов
ООП. Любой программист,
работавший с такими языками,
как Visual Basic, C, Cobol или
www.williamspublishing.com Pascal, не будет испытывать
затруднений при работе с ней.

ISBN 5-8459-0970-8 в продаже

Стр. 363
JAVA 2
БИБЛИОТЕКА ПРОФЕССИОНАЛА
ТОМ II. ТОНКОСТИ ПРОГРАММИРОВАНИЯ
СЕДЬМОЕ ИЗДАНИЕ
К. Хорстманн В книге уделено внимание
Г. Корнелл таким сложным вопросам, как
поддержка распределенных
объектов; в частности,
читатель найдет информацию
о технологиях RMI, CORBA
и SOAP. Здесь продолжено
обсуждение Swing и AWT,
начатое в первом томе.
При разработке Java-
программ важную роль
играют компоненты JavaBeans;
им посвящена одна из глав
книги. Не забыты и вопросы
безопасности. В книге
подробно рассматриваются
загрузчики классов,
диспетчеры защиты, средства
шифрования, авторизации и
аутентификации. Благодаря
разнообразию и глубине
излагаемого материала книга,
несомненно, будет полезна
как начинающим, так и
опытным разработчикам.

www.williamspublishing.com

ISBN 5-8459-1033-1 в продаже

Стр. 364
DELPHI™
ДЛЯ “ЧАЙНИКОВ”

Нил Дж. Рубенкинг Эта книга предлагает


введение в одну из наиболее
успешных сред быстрой
разработки приложений —
Delphi 2006. В ней в простом
и доступном стиле описаны
основы компоновки форм,
написание собственного
кода, дополняющего и
расширяющего существующую
функциональность, а также
работа с разнообразными
стандартными компонентами,
которые предлагает Delphi.
Также рассматриваются
особенности взаимодействия
с Windows, работа с базами
данных, отладка создаваемых
программ и распространенные
ошибки, которые часто
возникают у начинающих.
Книга изобилует множеством
простых примеров кода,
которые помогают лучше
усвоить материал. Все
исходные коды доступны
www.dialektika.com для загрузки на Web-
сайте издательства.
Книга рассчитана на
широкий круг читателей.

ISBN 978-5-8459-0958-9 в продаже

Стр. 365
HTML И CSS В ПРИМЕРАХ,
ТИПОВЫХ РЕШЕНИЯХ И ЗАДАЧАХ

С.А. Соколов Книга посвящена языку HTML


и технологии CSS, а также
методам их практического
использования
при построении Web-сайтов.
В ней приведены
теоретические основы
HTML и CSS, наряду
со стандартными решениями,
применяемыми в процессе
проектирования современных
Web-сайтов, причем
повышенное внимание уделено
вопросу взаимодействия
HTML и CSS. Материал
базируется на оригинальных
спецификациях HTML 4.01
и CSS2 и характеризуется
высокой степенью
детализации материала.
Благодаря особой структуре,
книга будет полезна
и новичкам в Web-дизайне,
как эффективный учебник,
и более опытным читателям,
как компактный справочник
и сборник готовых рецептов.
www.williamspublishing.com К книге прилагается
компакт-диск, содержащий
тексты всех примеров,
обсуждающихся в книге.

ISBN 978-5-8459-1192-6 в продаже

Стр. 366
WINDOWS VISTA
ДЛЯ “ЧАЙНИКОВ”

Энди Ратбон Перед вами самая популярная


книга о Windows Vista в
мире! Благодаря прекрасно
подобранному и изложенному
материалу вы приобретете
необходимый минимум
знаний по работе с новой
операционной системой. В
книге простым и понятным
языком рассказывается
о том, как поддерживать
безопасность системы; как
найти, запустить и закрыть
программу; как настроить
компьютер для использования
его всеми членами семьи; как
скопировать информацию с
компакт-диска или, наоборот,
записать ее на компакт-диск
либо DVD; как ограничить
использование ПК детьми
или зашифровать данные
на жестком диске; как
устранить неполадку, когда
Windows Vista начинает
вести себя неподобающим
www.dialektika.com образом, и многое другое.
Книга предназначена для
начинающих пользователей.

ISBN 978-5-8459-1219-0 в продаже

Стр. 367
AUTODESK 3DS MAX 9
ДЛЯ “ЧАЙНИКОВ”

Шаммс Мортье Вы горите желанием изучить


самую популярную в мире
программу трехмерного
моделирования 3ds max, однако
вас пугает ее сложность? Ваши
опасения напрасны! Настоящее
руководство расскажет вам
обо всех основных моментах
использования 3ds max — от
элементов пользовательского
интерфейса и навигации до
методов моделирования и
применения модификаторов
и карт. За короткое время вы
станете настоящим мастером
в вопросах разработки
трехмерной анимации.
На прилагаемом к книге
компактдиске: окончательная
визуализация примеров,
библиотеки материалов,
подборка карт и многое другое.
Книга расчитана для
начинающих пользователей

www.dialektika.com

ISBN 978-5-8459-1215-2 в продаже

Стр. 368

Вам также может понравиться