Вы находитесь на странице: 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