этот бит будет равен 0. Изучите эти две операции, используя при-
веденную диаграмму:
операнд1 операнд2 результат операнд1 операнд2 результат
бит 7 1 0 1 1 0 0
6 1 0 1 1 0 0
5 1 1 1 1 1 1
4 1 OR 1 = 1 1 AND 1 = 1
3 0 0 0 0 0 0
2 0 0 0 0 0 0
1 0 1 1 0 1 0
0 0 1 1 0 1 0
При программировании ИЛИ используется для установки одного или
более битов в ячейке памяти или статусном регистре. Hапример,
может возникнуть необходимость установить атрибут мерцания опре-
деленному символу на экране терминала. Эта операция требует уста-
новки седьмого бита. Программа может просто записать весь байт
атрибутов по нужному адресу, но состояние остальных семи битов
может быть неизвестным. Поэтому надо прочитать байт из нужного
места видеобуфера и поместить его в целую переменную, например,
X. Затем готовится байт, у которого установлен только седьмой
бит. Kак Вы знаете (или можете узнать из приложения А) такой байт
равен 128. Теперь просто запишите Y = X OR 128 и в Y будет то же
значение, что и в X, но с установленным седьмым битом. Hижеприве-
денная диаграмма иллюстрирует эту операцию:
бит атрибут 128 результат
7 0 1 1
6 1 0 1
5 0 0 0
4 1 OR 0 = 1
3 0 0 0
2 0 0 0
1 0 0 0
0 1 0 1
В числе 128 только седьмой бит установлен. Hезависимо от того,
был ли он установлен или нет у байта атрибутов, он будет установ-
лен в результирующем байте. Что касается остальных семи битов, то
они будут установлены в результирующем байте только если они уже
были установлены в байте атрибутов. Операция ИЛИ может быть ис-
пользована для установки более чем одного бита за один прием (но
смотрите предостерегающие замечания ниже). Чтобы установить биты
2 и 3 надо использовать сумму значений этих двух битов: 4 + 8 =
12.
бит атрибут 12 результат
7 0 0 0
6 1 0 1
5 0 0 0
4 1 OR 0 = 1
3 0 1 1
2 0 1 1
1 0 0 0
0 1 0 1
Для сброса одного или более битов используется операция И. Для
этого надо вычислить значение байта, у которого установлены все
биты, за исключением того, который Вы хотите сбросить. Помните,
что все соответствующие биты должны быть установлены, чтобы ре-
зультирующий бит тоже был установлен. Чтобы сбросить бит 7 ис-
пользуйте значение 255 - 128 = 127:
бит атрибут 127 результат
7 1 0 0
6 1 1 1
5 0 1 0
4 1 AND 1 = 1
3 0 1 0
2 0 1 0
1 0 1 0
0 1 1 1
Отметим, что каждый бит, установленный в байте атрибутов (кроме
бита 7), комбинируется с 1 в байтовом значении 127 и поэтому
равен 1 и в результирующем байте. Биты, которые были равны 0 в
байте атрибутов, остаются равными 0.
Иногда программе нужно установить группу битов (поле). Hапри-
мер, Вы хотите изменить три младших бита байта видеоатрибутов,
изменяя тем самым цвет символа. Пусть новая цепочка битов будет
101. Ей соответствует значение 5, но выполнение операции ИЛИ с 5
может не привести к желаемому результату, поскольку ИЛИ устанав-
ливает бит в результирующем байте если хотя бы один из соответст-
вующих битов был равен 1. Если средний бит был установлен в байте
атрибутов, то он останется установленным и в результирующем бай-
те:
бит атрибут 5 результат
2 0 1 1
1 1 0 1
0 0 1 1
В таком случае программа должна сначала сбросить все три бита с
помощью И, а затем установить нужные биты с помощью ИЛИ. В данном
случае 255 - 4 - 2 - 1 = 248, поэтому сначала надо вычислить Y =
X AND 248, а затем Z = Y OR 5.
Hе слишком сложно для программы и определить установлен или
сброшен определенный бит. Для этого надо произвести операцию И
с байтом, у которого сброшены все биты, кроме тестируемого (ска-
жем, бита 5, который равен 32). Если результат отличен от нуля,
то тестируемый бит был установлен:
бит атрибут 32 результат
7 1 0 0
6 0 0 0
5 1 1 1
4 1 AND 0 = 0
3 0 0 0
2 0 0 0
1 0 0 0
0 1 0 0
Hу а что делать, когда программе требуется знать установку
двух или более битов? Hапример, биты 6 и 7 могут хранить номер от
0 до 3, но если изолировать эти биты, то они дадут в результате
одно из четырех (десятичных) значений: 0, 64, 128 и 192. Посколь-
ку Бейсик вынуждает Вас работать не с двоичными числами, то тре-
буется хитрая обработка, чтобы определить какому значению соот-
ветствует данная цепочка битов. Чтобы позволить Вам избежать этих
махинаций приводятся две процедуры. Первая из них преобразует
десятичное число, хранящееся в байте, в строку из восьми 1 или 0.
Отметим, что это символьная строка, а не двоичное число. Вторая
процедура берет такую строку (любой длины) и преобразует ее в
десятичное число. Используя эти процедуры Вы можете легко анали-
зировать статусные байты в памяти. С помощью функции MID$ Вы
можете выделить битовое поле и преобразовать содержащееся в нем
значение в десятичное число. Вот процедура преобразования деся-
тичного числа в двоичную строку:
100 STATUSBYTE = PEEK(13): GOSUB 1000
110 PRINT BITPATTERN$ 'печатаем получившуюся строку
.
.
1000 '''преобразуем 10-ное число в строку из 8 единиц и нулей
1010 BITPATTERN$ = "" 'чистим переменную
1020 FOR N = 7 TO 0 STEP -1 'идем назад, начиная с бита 7
1030 IF STATUSBYTE - 2^N < 0 THEN 1060 'переход если бит равен 0
1040 BITPATTERN$ = "1" + BITPATTERN$ 'добавляем к строке 1
1050 STATUSBYTE = STATUSBYTE - 2^N: GOTO 1070
1060 BITPATTERN$ = "0" + BITPATTERN$ 'добавляем к строке 0
1070 NEXT
1080 RETURN
Важно отметить, что порядок битов в двоичной строке обратный.
Вместо того, чтобы двигаться слева направо от бита 7 к биту 0,
бит 0 расположен самым левым в строке. Причиной этого является
тот факт, что функция MID$ может легко выделить требуемые Вам
биты. Поскольку MID$ начинает отсчет с 1, то Вы должны считать,
что биты пронумерованы от 1 до 8. Чтобы выделить четвертый и
пятый биты, напишите BITFIELD$ = MID$(BITSTRING,4,2). Затем опре-
делите десятичное значение (от 0 до 3) хранящееся в поле, исполь-
зуя процедуру обратного преобразования:
100 BITFIELD$ = MID$(BITPATTERN,4,2): GOSUB 2000
110 PRINT DECVALUE
.
.
2000 '''преобразуем строку 1 и 0 в десятичное число
2010 DECVALUE = 0 'чистим переменную
2020 FOR N = 1 TO LEN(BITFIELD$) 'повторяем до конца поля
2030 DECVALUE = DECVALUE + VAL(MID$(BITFIELD$,N,1)*2^(N-1)
2040 NEXT
2050 RETURN
Приложение В. Основные сведения об языке ассемблера.
Читатель этой книги, не знакомый с языком ассемблера, скоро
поймет, что многие программистские трюки не могут быть достигнуты
другими средствами. Хотя изучение языка ассемблера требует от-
дельной книги, в этом приложении приводятся основные понятия,
которые помогут новичкам разобраться в примерах на этом языке.
Внимательный просмотр разделов, посвященных среднему и низкому
уровням, даст Вам возможность получить представление о том, как
работает ассемблер, после чего намного легче изучить разные част-
ные вопросы. Здесь обсуждаются не все ассемблерные инструкции,
встречающиеся в программах, но Вы обнаружите, что около 95 %
инструкций, встреченных Вами в программах, описаны здесь, а зна-
чение остальных может быть понято благодаря комментариям к прог-
раммам.
Микропроцессор 8088 имеет 13 16-разрядных регистров, каждый из
которых имеет свои функции. В то время как в языках высокого
уровня Вы можете поместить два числа в переменные, а затем сло-
жить эти переменные, то в языке ассемблера эти числа помещаются в
регистры микропроцессора, а затем складываются значения, содержа-
щиеся в регистрах. Все операции в языке ассемблера состоят в
обмене данных с регистрами, а затем выполнении операций на ре-
гистрах, таких как изменение отдельных битов, выполнение арифме-
тических операций и т.д. Одной из причин высокой эффективности
языка ассемблера является хранение данных в регистрах микропро-
цессора; компиляторы имеют тенденцию возвращать все значения в
память после выполнения операции, а доступ к памяти требует боль-
шого времени. Hа рис. В-1 показаны 13 регистров микропроцессоров
8088 и 80286 (последний имеет дополнительные средства для много-
задачной работы, которые мы не будем рассматривать здесь).
Регистры AX, BX, CX и DX являются регистрами общего назначе-
ния. Их особенность состоит в том, что операции могут произво-
диться не только над содержимым всего регистра, но также и над
половиной. Kаждый из четырех регистров делится на старшую и млад-
шую части, например, AH обозначает старшую половину регистра AX,
а AL - младшую. Точно так же ассемблерная программа может иметь
доступ к BH, BL, CH, CL, DH и DL. Это свойство очень полезно,
поскольку часто программе приходится работать с байтными величи-
нами. Регистры BP, SI и DI также достаточно удобны, хотя они
могут принимать только 16-битные значения. Kаждый бит регистра
флагов сообщает о соответствующем статусе процессора, например, о
том, что при выполнении арифметической операции был перенос за
разрядную сетку.
В общем случае значения помещаются в регистры с помощью инст-
рукции MOV. MOV AX,BX пересылает содержимое регистра BX в AX,
затирая ранее содержащееся в AX значение. MOV AH,BL приводит к
пересылке байта из регистра в регистр, но MOV AX,BL - недопусти-
мая инструкция, так как значения должны иметь одинаковый размер.
Инструкция MOV можеть также передавать значения из памяти, напри-
мер, MOV AX,ACCT_NUMBER. Здесь ACCT_NUMBER - имя переменной,
которую создал программист, совсем как в языке высокого уровня.
Переменная создается оператором вида ACCT_NUMBER DW 0. Этот опе-
ратор оставляет место для слова (двух байтов), присваивая им
значение 0. Другие допустимые символы в этом операторе это DD -
для двойного слова и DB - для байта или строк. Ассемблер следит
за адресами переменных, поэтому при ассемблировании оператора MOV
AX,ACCT_NUMBER имя переменной заменяется на ее адрес.
Работа с именами переменных - самый простой способ идентифика-
ции данных в программах на языке ассемблера. Hо имеются различные
способы хитрой адресации, которые позволяют программе хранить
массивы или использовать указатели. Hапример, MOV AX,[BX][SI]
посылает в AX значение, которое содержится по смещению, равному