До сих пор обсуждались вопросы, касающиеся того, как выполняются числовые вычисления, и ничего не было сказано о символьной информации, хотя совершенно очевидно, что какой-то способ обработки символьных данных должен существовать. В конце концов, и ассемблер, и операционная система, и компиляторы Паскаля и Фортрана - все имеют дело с операторами, представляющими собой строки текстовых символов. Более того, поскольку все эти обрабатывающие программы на самом деле являются обычными машинными программами, то любая программа может быть способна выполнять обработку символьных данных. Сразу же возникает вопрос о том, каким образом символьная информация представляется в машине. Ответ заключается в следующем: текстовые символы кодируются в виде двоичных чисел, дающих уникальное представление каждого символа алфавита. Поскольку следует иметь в виду ещё цифры и знаки пунктуации, нередко используют термин алфавитно-цифровые символы. Фактически используемое кодирование может быть произвольным. Другими словами, интерпретация символьного кода полностью определяется разработчиком устройства ввода-вывода, применяемого для считывания и распечатки символов. В ЭВМ VAX используется код, который первоначально был стандартным кодом для телеграфных аппаратов-телетайпов, но в настоящее время применяется для всех типов устройств ввода-вывода. Этот код называется ASCII, что означает Американский стандартный код для обмена информацией. Другой символьный код называется EBCDIC, он произошёл от кода для перфокарт Холлерита и будет рассмотрен позднее.
Настоящая глава посвящена основным способам обработки символьной информации, а также обработке отдельных битов, составляющих эту информацию. Символьные строки рассматриваются как массивы байтов, а для их обработки используются способы, описанные в предыдущих главах. В гл. 13 будет показано, что в архитектуру ЭВМ VAX включены инструкции, которые могут за одну операцию выполнять обработку целиком всей символьной строки.
Как только что было сказано, система кодов ASCII возникла при использовании телетайпов. Поэтому каждый код представляет нажатие некоторой комбинации клавиш на клавиатуре, подобной клавиатуре пишущей машинки. (Заметим, что, как и на большинстве пишущих машинок, одновременное нажатие нескольких клавиш обычно является запрещённым или бессмысленным[1]. Исключение составляют клавиши SHIFT ("Смена регистра") и CONTROL ("Управление"), которые сами кода не порождают, но используются в комбинации с другими клавишами. Каждое нажатие клавиши пишущей машинки заставляет печатающий механизм выполнить какое-то действие, т.е. напечатать знак, сделать пробел, перевести каретку и т.п. Аналогично этому каждый отрабатываемый код ASCII вызывает подобное действие печатающего механизма телетайпа. Чтобы напечатать на телетайпе какое-либо сообщение, необходимо последовательно нажимать на клавиши. Чтобы напечатать сообщение, программа должна выдать на телетайп некоторую последовательность кодов ASCII.
В настоящее время общепринятым широко распространённым стандартом является 7-битовая система ASCII. В ней используются 27, или 128, кодов, из которых 95 кодов - для печатных символов, 33 - для операций управления, таких как "Возврат каретки" или "Перевод строки". В число 95 печатных символов входят:
К знакам пунктуации относятся следующие символы:
! " # $ % & ' ( ) * + ,- . / < > : ; = ? @ [ \ ] _ ^ `{ | } ~
Здесь следует заметить, что некоторые более старые или менее дорогие телетайпы и устройства печати способны распечатывать только 64 символа:
Хотя подобные терминалы устарели, многие из них всё ещё продолжают существовать. Именно поэтому в командном языке операционной системы VAX/VMS не делается различия между прописными и строчными буквами и не используются пять знаков пунктуации, отсутствующих на этих терминалах.
В новейшем стандарте ASCII вместо семи используются восемь битов: 8-битовый код ASCII способен представлять 256 различных кодов, что открывает возможность представления значительно более богатого алфавита. Дополнительные 128 кодов позволяют представлять алфавиты иностранных языков и графические символы.
Из 33 управляющих символов 7-битового кода ASCII обычно используются всего лишь несколько. Наиболее употребительными являются:
Читатель вправе удивиться, для чего же предназначены остальные 26 управляющих символов? В большинстве случаев они не находят применения в оборудовании вычислительной техники (многие из них используются как управляющие коды при коммутации сообщений и передаче телеграмм, т.е. для выполнения тех функций, для которых первоначально применялись телетайпы). Неиспользуемые управляющие символы обычно аппаратурой игнорируются. Например, телетайп, в котором не реализована возможность горизонтальной табуляции, не производит никакого действия, когда получает символ горизонтальной табуляции. Как следствие, такие символы могут использоваться для программных функций. Например, операционная система для ЭВМ VAX может заменять символ горизонтальной табуляции знаками пробела, что позволяет программам эффективно имитировать операцию табуляции. Такая функция является факультативной; она может быть выбрана теми пользователями, устройства вывода которых не обладают встроенной функцией горизонтальной табуляции.
В 7-битовом коде ASCII каждый символ представляется 7-битовым двоичным числом. Первые 32 числа используются для 32 из 33 символов управления; это коды ^X00 - ^X1F. Пробел имеет код ^X20; десятичные цифры 0-9 - коды ^X30 - ^X39; прописным буквам назначены коды ^X41 - ^X5A; строчным буквам - коды ^X61 - ^X7A; под знаки пунктуации некоторым специальным образом выделяются остальные неиспользованные коды ^X21 - ^X7E. Один управляющий символ, называемый Rub Out или DEL (забой, удаление), ассоциируется с кодом ^X7F. Этот символ имеет особую значимость, когда вы вручную набиваете, например, текст статьи, с тем чтобы он оказался занесённым на перфоленту. Если вы делаете ошибку при вводе текста с клавиатуры, то можете передвинуть перфоленту на шаг назад, однако стереть уже пробитые на ленте отверстия вы не сможете. Но вместо этого можно пробить отверстия во всех позициях кадра перфоленты, и такие кадры обычно игнорируются при обработке вводимой информации. Именно по этой причине и был введён специальный символ Rub Out или DEL, код которого состоит из всех единиц (^X7F), в результате чего пробиваются отверстия во всех позициях кадра перфоленты. Учитывая такое традиционное применение, в операционной системе для ЭВМ VAX этот символ используется для возврата на шаг назад с целью исправления ошибок. В табл. 8.1 показано шестнадцатеричное представление всего кода ASCII, состоящего из 128 символов.
^X00 |
NUL |
^X10 |
DLE |
^X20 |
SP |
^X30 |
0 |
^X40 |
@ |
^X50 |
P |
^X60 |
|
^X70 |
P |
^X01 |
SOH |
^X11 |
DC1 |
^X21 |
! |
^X31 |
1 |
^X41 |
A |
^X51 |
Q |
^X61 |
a |
^X71 |
q |
^X02 |
STX |
^X12 |
DC2 |
^X22 |
" |
^X32 |
2 |
^X42 |
В |
^X52 |
R |
^X62 |
b |
^X72 |
r |
^X03 |
ЕТХ |
^X13 |
DC3 |
^X23 |
# |
^X33 |
3 |
^X43 |
C |
^X53 |
S |
^X63 |
c |
^X73 |
s |
^X04 |
EOT |
^X14 |
DC4 |
^X24 |
$ |
^X34 |
4 |
^X44 |
D |
^X54 |
T |
^X64 |
d |
^X74 |
l |
^X05 |
ENQ |
^X15 |
NAK |
^X25 |
% |
^X35 |
5 |
^X45 |
E |
^X55 |
U |
^X65 |
e |
^X75 |
u |
^X06 |
АСК |
^X16 |
SYN |
^X26 |
& |
^X36 |
6 |
^X46 |
F |
^X56 |
V |
^X66 |
f |
^X76 |
V |
^X07 |
BEL |
^X17 |
ЕТВ |
^X27 |
' |
^X37 |
7 |
^X47 |
G |
^X57 |
W |
^X67 |
g |
^X77 |
w |
^X08 |
BS |
^X18 |
CAN |
^X28 |
( |
^X38 |
8 |
^X48 |
H |
^X58 |
X |
^X68 |
h |
^X78 |
X |
^X09 |
HT |
^X19 |
ЕМ |
^X29 |
) |
^X39 |
9 |
^X49 |
I |
^X59 |
Y |
^X69 |
i |
^X79 |
у |
^X0A |
LF |
^X1A |
SUB |
^X2A |
* |
^X3A |
: |
^X4A |
J |
^X5A |
Z |
^X6A |
j |
^X7A |
z |
^X0В |
VT |
^X1В |
ESC |
^X2B |
+ |
^X3B |
; |
^X4B |
К |
^X5B |
[ |
^X6B |
k |
^X7B |
{ |
^X0C |
FF |
^X1C |
PS |
^X2C |
, |
^X3C |
< |
^X4C |
L |
^X5C |
\ |
^X6C |
l |
^X7C |
| |
^X0D |
CR |
^X1D |
GS |
^X2D |
- |
^X3D |
= |
^X4D |
M |
^X5D |
] |
^X6D |
m |
^X7D |
} |
^X0E |
SO |
^X1E |
RS |
^X2E |
. |
^X3E |
> |
^X4E |
N |
^X5E |
` |
^X6E |
n |
^X7E |
~ |
^X0F |
SI |
^X1F |
US |
^X2F |
/ |
^X3F |
? |
^X4F |
O |
^X5F |
_ |
^X6F |
о |
^X7F |
DEL |
Из-за большого размера алфавита кода ASCII (128 символов) было бы непрактично выделять отдельную клавишу под каждый символ. Поэтому обычно для получения некоторых символов используют определённые комбинации клавиш. Например, клавишу смены регистра SHIFT используют для переключения между прописными и строчными буквами. Клавиша SHIFT служит также для переключения между некоторыми знаками пунктуации и цифрами, как это делается на большинстве обычных пишущих машинок. Поэтому, если даже терминал имеет только лишь прописные буквы, у него все равно будет клавиша смены регистра.
На рис. 8.1 показан вид клавиатуры обычного терминала. Обратите внимание, что для управляющих символов эта клавиатура располагает всего лишь несколькими клавишами; обычно это клавиши возврата каретки (RETURN), перевода строки (LINE FEED), табуляции (TAB), возврата на шаг (BACK SPACE), управляющей последовательности кода (ESC) и удаления (DEL). Первые четыре необходимы, так как выполняют функции, постоянно используемые при обычной работе на пишущей машинке. Последние две не выполняют аппаратно-реализованных функций, но широко используются программным обеспечением, например, как упоминалось ранее, для удаления или забоя.
Читатель может задать вопрос, каким же образом порождаются другие управляющие символы. Обратите внимание, что в левом нижнем углу рис. 8.1 имеется клавиша, помеченная буквами CTRL (от CONTROL - управление). Эта клавиша очень похожа на клавишу смены регистра SHIFT тем, что сама по себе не выполняет какой-то определённой функции, по нажимается совместно с какой-либо другой клавишей. Основная функция клавиши управления CTRL заключается в принудительном сбросе в нуль двух определённых битов знакового кода. Примеры:
CTRL/@ эквивалентно ^X00 или NUL;
CTRL/А эквивалентно ^X01 или SOH;
CTRL/B эквивалентно ^X02 или STX;
CTRL/C эквивалентно ^X03 или ЕТХ.
Обратите внимание, что таким способом могут быть получены все управляющие символы, исключая символ DEL. Сюда входят и возврат каретки (CTRL/M), и перевод строки (CTRL/J). Символ DEL, код которого состоит из всех единиц, очевидно, не может быть получен с помощью клавиши управления, сбрасывающей биты в нуль. Поэтому обычно под этот символ отводится отдельная клавиша.
Рис. 8.1. Типовое расположение символов кода ASCII на клавиатуре
Многие клавиатуры располагают также специальной клавишей верхнего регистра, которая может иметь маркировку ALL CAPS. Нажатие на эту клавишу приводит к тому, что клавиатура ведёт себя как имеющая лишь прописные буквы. Большая часть существующего ныне программного обеспечения разработана так, чтобы сохранить совместимость со старыми терминалами, способными обрабатывать только 64 печатных символа. Клавиша верхнего регистра позволяет использовать терминал с 95-символьной клавиатурой так, как если бы он имел 64-символьную клавиатуру (без этой возможности пришлось бы удерживать клавишу смены регистра большую часть времени при работе с программным обеспечением, ориентированным на старые терминалы). Из табл. 8.1 можно заметить, что код каждой строчной буквы больше кода соответствующей прописной буквы на величину ^X20. Например, код для буквы а есть ^X61, а код для буквы А - ^X41. При нажатии на клавишу верхнего регистра фактически происходит вычитание ^X20 из кодов строчных букв. Вычитание выполняется обычно сбросом бита 5 в двоичном коде строчных букв. Операционная система для ЭВМ VAX способна имитировать действие клавиши верхнего регистра программными средствами, что во многих случаях освобождает от необходимости использования этой клавиши.
Код ASCII является последовательным (символ за символом) кодом, так как первоначально он предназначался для работы с телетайпами, которые представляют собой посимвольные печатающие устройства. Но, даже будучи таковым, он также пригоден для построчно-печатающих устройств и видеотерминалов или дисплеев. Последние иногда называют электронно-лучевыми терминалами (CRT), поскольку основным компонентом в них служит электронно-лучевая трубка.
Даже если подобные устройства разрабатываются для быстродействующей групповой обработки символов, для упрощения их подключения информация обычно передаётся к ним посимвольно. Как правило, построчно-печатающие устройства и видеотерминалы выводят информацию одновременно по целой строке или странице и в отличие от телетайпов не имеют, как таковой, каретки, которую надо возвращать на начало строки. Но тем не менее для совместимости с обыкновенным кодом ASCII символы возврата каретки и перевода строки традиционно используются для завершения одной строки и продвижения к следующей. Поэтому в большинстве случаев подобные устройства можно рассматривать как обычные телетайпы.
Некоторые устройства обладают особыми возможностями. Устройства печати могут позволять программисту запрограммировать продвижение бумаги на начало новой страницы. Видеотерминалы могут иметь такие функции, как стирание экрана, управление курсором, использование режима прокрутки вместо страничного режима. Курсор - это мерцающий маркер, который показывает на экране место, где должен вводиться текст. Обычно по мере ввода текста курсор перемещается вправо, но некоторые видеотерминалы позволяют позиционировать курсор в любой точке экрана. Прокрутка (scrolling) даёт возможность пользователю добавлять строки в нижней части экрана, прокручивая остальной текст вверх с потерей верхних строк. Такие специальные возможности достигаются обычно с помощью некоторого протокола, построенного на основе управляющих символов. Например, строка из семи символов
(esc) [5;39f
заставляет терминал типа VT-100 или любой другой терминал, реализующий то, что называют стандартной управляющей последовательностью ANSI[4], переместить курсор в 39-ю знаковую позицию 5-й строки экрана. Конкретная информация о таких последовательностях приводится в руководствах для подобных терминалов.
В ЭВМ семейства VAX отдельные символы хранятся как байты. При использовании 7-битового кода ASCII операционная система VAX/VMS, как правило, сбрасывает в нуль старший бит каждого байта. Строка символов обычно размещается в памяти как массив байтов. Следовательно, если желательно распечатать на одной строчке сообщение HELLO!!!, то потребуются символы Н, Е, L, L, О, !, ! и !. Для этих восьми символов нужен массив из восьми байтов. Если этот массив называется STRING и расположен, начиная с адреса ^X00000400, то он будет выглядеть так, как показано на рис. 8.2. В программу на языке ассемблера эти данные могут быть введены с помощью директивы .BYTE следующим образом:
STRING: .BYTE ^X48,^X45,^X4C,^X4C,^X4F,^X21,^X21,^X21
Специального замечания по поводу байтовых массивов заслуживает ситуация, когда байты или ещё меньшие элементы данных упаковываются в элементы данных большего объёма, такие как длинные слова; при этом в первую очередь заполняются их младшие биты. Вследствие этого байты представляются упорядоченными в обратном порядке. Например, если приведённый массив из 8 байтов рассматривать как упакованный в массив из двух длинных слов, то он выглядел бы так:
Содержимое | Адрес | Символический адрес |
---|---|---|
^X4C4C4548 |
^X0400 |
STRING |
^X2121214F |
^X0404 |
STRING+4 |
Именно по этой причине представление этих байтов в объектном коде на листинге ассемблера читается справа налево. Таким образом, порождённый по предыдущей директиве .BYTE список байтов имел бы в листинге ассемблера следующий вид:
21 21 21 4F 4С 4С 45 48 0400.
Всем понятно, что такое представление неудобно, а для работы с ним требуются определённые навыки. Но подобная проблема в той или иной форме существует у всех байт-ориентированных ЭВМ, и нотация, принятая для ЭВМ VAX, вероятно, сбивает с толку в меньшей степени, чем многие другие представления.
Содержимое | Адрес | Символ | Символический адрес |
---|---|---|---|
^X48 |
^X0400 |
Н |
STRING |
^X45 |
^X0401 |
Е |
STRING+1 |
^X4C |
^X0402 |
L |
STRING+2 |
^X4C |
^X0403 |
L |
STRING+3 |
^X4F |
^X0404 |
0 |
STRING+4 |
^X21 |
^X0405 |
! |
STRING+5 |
^X21 |
^X0406 |
! |
STRING+6 |
^X21 |
^X0407 |
! |
STRING+7 |
Рис. 8.2. Строка символов в коде ASCII
Во внутреннем представлении ЭВМ VAX коды символов - это просто множество чисел от 0 до 255 (если они интерпретируются как числа без знака), ничем не отличающихся от каких-либо других байтов. Обратясь к рис. 8.2, можно увидеть, что для обработки символов в строке могут быть применены различные байтовые инструкции, такие как MOVB, INCB, DECB, ADDB2 и т.п. Например, по инструкции
MOVB STRING+5,STRING+2
код ^X21, расположенный по адресу STRING+5 или ^X405, будет помещён по адресу STRING+2 или ^X402. После этого содержимое байтов массива строки стало бы таким, как показано на рис. 8.3. При выводе на печать была бы сформирована строка HE!LO!!!. Аналогично выполнение инструкции
ADDB2 #5,STRING+3
приводит к тому, что вместо числа ^X4C по адресу STRING+3 будет находиться число ^X51. Это число является кодом ASCII для буквы Q. Следовательно, если снова распечатать строку, то она будет иметь вид HE!QO!!!.
Важно не забывать о том, что когда речь идёт о процессоре ЭВМ VAX, то любая символьная строка есть не что иное, как массив 8-битовых чисел. Интерпретация этих чисел как кодов символов является функцией устройств ввода-вывода и программного обеспечения. Следовательно, ответственность за корректность операций, выполняемых над символьными строками, ложится на программиста. Отличить корректные операции от не имеющих смысла бывает не так уж легко. Всё зависит от конкретной ситуации. Вернёмся к предыдущему примеру. Число 5 было прибавлено к букве L, что дало букву Q. В этой ситуации такая операция не представляется полезной, но фактически к этому и не стремились. Однако операции подобного рода могут пригодиться при шифровании или при преобразовании кодов символов из кода ASCII в какой-то другой код для устройства ввода-вывода, не способного работать с кодом ASCII. (См. раздел "Другие представления символов" в конце данной главы.)
Содержимое | Адрес | Символический адрес |
---|---|---|
^X48 |
^X0400 |
STRING |
^X45 |
^X0401 |
STRING+1 |
^X21 |
^X0402 |
STRING+2 |
^X4C |
^X0403 |
STRING+3 |
^X4F |
^X0404 |
STRING+4 |
^X21 |
^X0405 |
STRING+5 |
^X21 |
^X0406 |
STRING+6 |
^X21 |
^X0407 |
STRING+7 |
Рис. 8.3. Представление символьной строки в памяти
В предыдущем разделе было показано, как можно вставить символьную строку в текст программы с помощью директивы .BYTE. Чтобы ввести символьную строку таким образом, необходимо отыскать каждый символ вводимого текста в табл. 8.1. Ясно, что это может стать обременительным, если программа содержит большое число символьных строк. Для упрощения задания символьных строк в программе, в ассемблере VAX-MACRO имеется директива .ASCII. Эта директива позволяет вставлять в программу символьные строки. Вставляемые символьные строки заключаются в кавычки, как это обычно делается во многих языках высокого уровня. Например, символьная строка, которая ранее вводилась как
STRING: .BYTE ^X48,^X45,^X4C,^X4C,^X4F,^X21,^X21,^X21
может быть введена так:
STRING: .ASCII "HELLO!!!"
В обоих случаях содержимое памяти получается одним и тем же.
Если строка, которую необходимо ввести, содержит непечатные символы, такие как символ табуляции (^X09), возврата каретки (^X0D), звонка (^X07) и т.п., возникает проблема. Хотя некоторые непечатные символы возможно включить в определяемую строку, это плохая программистская практика, так как в таком случае трудно бывает определить, какие именно символы в действительности находятся в строке. Для разрешения этой проблемы в директиве .ASCII допускаются байтовые выражения, заключённые в угловые скобки < и >. Предположим, например, что строке HELLO!!! должны предшествовать два символа табуляции, а вслед за ней должен располагаться символ "звонок". При передаче такой строки на терминал слово HELLO!!! появится, начиная со второй позиции табуляции, а после вывода слова прозвенит сигнальный звонок терминала. Для создания этой строки может быть использована следующая директива .ASCII:
ALARM_STRING: .ASCII <^X09><^X09>"HELLO!!!"<^X07>
Обратите внимание, что массив ALARM_STRING содержит 11 символов кода ASCII.
Конечно, лучший стиль программирования состоял бы в определении для символов табуляции и звонка символических имён и в использовании этих имён вместо фактических шестнадцатеричных чисел. Это могло бы выглядеть так:
TAB=^X09 BEL=^X07 ALARM_STRING: .ASCII <TAB><TAB>"HELLO!!!"<BEL>
Ещё одна очевидная проблема заключается в том, что невозможно включить знак " между кавычками, ограничивающими строку. Ассемблер VAX - MACRO даёт возможность решить эту проблему, позволяя использовать альтернативные знаки кавычек. Считается, что первый печатный символ, отличный от знака <, определяет начало кавычек. Второе появление того же самого символа означает окончание кавычек. Следовательно, предыдущая строка может быть введена и следующим образом:
ALARM_STRING: .ASCII <TAB><TAB>/HELLO!!!/<BEL>
или
ALARM_STRING: .ASCII <TAB><TAB>%HELLO!!!%<BEL>
Ясно что при выборе какого-то конкретного символа для кавычек в строку можно вводить любые другие символы, кроме него самого. Однако для стандартизации многие программисты ограничиваются применением только нескольких символов в качестве кавычек, таких как ", ' или /.
При передаче на терминал последовательных строк необходимо отделять их друг от друга управляющими символами возврата каретки (^X0D) и перевода строки (^X0A). В большинстве случаев операционная система VAX/VMS сама вместо пользователя вставляет эти символы. Поэтому чаще всего не нужно включать их в символьные строки. Но во многих других вычислительных системах пользователи должны явным образом вставлять эти управляющие символы. Форматирование строк подробно рассматривается в гл. 11.
Чтобы увидеть, как могут использоваться символьные строки, рассмотрим некий программный сегмент, в котором каждый символ строки STRING передаётся подпрограмме PROCESS. Символы передаются каждый раз по одному через регистр R0. Не обязательно знать, что именно делает подпрограмма PROCESS, за исключением того, что она принимает по одному символу при каждом обращении к ней и выполняет какое-то действие. Возможно, она выводит символ на печать, возможно, помещает его в буфер. Используя полученные значения, можно написать следующую программу:
STRING: .ASCII "HELLO!!!" . . . PASS: CLRL R1 10$: MOVB STRING[R1],R0 JSB PROCESS AOBLSS #8,R1,10$
Одна проблема, связанная с этой программой, состоит в том, что инструкция AOBLSS содержит непосредственный операнд #8, который сообщает, сколько символов содержится в массиве STRING. Это порождает трудности, если программист когда-либо модифицирует символьную строку, ибо при этом он обязан всегда помнить о необходимости замены операнда #8 при указании длины новой строки.
Одно решение этой проблемы состоит в использовании символического выражения для вычисления длины строки. Обратите внимание. Если вычесть адрес начала массива строки из адреса следующей за массивом доступной ячейки, то разность даст число байтовых ячеек в массиве. Например, возвращаясь к рис. 8.3, можно увидеть, что байтовый массив расположен в памяти, занимая адреса ^X400 - ^X407. Значит, следующей доступной ячейкой является ячейка с адресом ^X408, а разность между адресом этой ячейки и адресом первой ячейки ^X400 равна 8, что и есть длина массива. В предыдущую программу всё это можно включить следующим образом:
STRING: .ASCII "HELLO!!!" STRINGEND: . . . PASS: CLRL R1 10$: MOVB STRING[R1],R0 JSB PROCESS AOBLSS #STRINGEND-STRING,R1,10$
В результате ассемблирования этой программы будет получен такой же двоичный код, как и в её предыдущем варианте. Однако, поскольку цифра 8 заменена выражением STRINGEND-STRING, ассемблер автоматически занесёт длину строки в инструкцию, AOBLSS. Следовательно, можно модифицировать массив STRING, не внося какие-либо изменения в программу. Например, если подлежащая обработке строка заменяется на GOOD BYE!!!,то две первых строки станут такими:
STRING: .ASCII "GOOD BYE!!!" STRINGEND:
Теперь строка имеет длину 11. Если метке STRING был назначен, как и прежде, адрес ^X400, метке STRINGEND будет назначен адрес ^X40B. Тогда их разность равна ^XB, или 11 в десятичном представлении.
Другой способ решения этой проблемы заключается в использовании какого-либо специального признака для обозначения конца строки. Для символьных строк в качестве такого признака подходит значение 0, поскольку 0 - это код символа NULL кода ASCII, который в большинстве систем просто игнорируется. В ассемблере ЭВМ VAX-11 данную возможность обеспечивает директива .ASCIZ, равносильная директиве .ASCII, за исключением того, что к концу строки добавляется нулевой байт. Следовательно, оператор
STRING: .ASCIZ "HELLO!!!"
эквивалентен оператору
STRING: .ASCII "HELLO!!!"<0>
В следующей программе показано, как можно использовать признак конца строки - нулевой байт, достигая при этом такого же результата, как и в предыдущем примере, вызывая подпрограмму PROCESS и передавая ей каждый символ строки через регистр R0 как байт:
STRING: .ASCIZ "HELLO!!!" . . . PASS: MOVAL STRING,R1 10$: MOVB (R1)+,R0 BEQL 20$ JSB PROCESS BRB 10$ 20$:
Этот способ обладает очевидными преимуществами, когда используются строки переменной длины или строки, порождаемые внутри программы. Кроме того, он позволяет применять режим адресации с автоувеличением в инструкции MOVB.
Существует всё же ещё один способ работы с символьными строками. Хотя данный способ несколько сложен, он имеет преимущества при передаче символьных строк подпрограммам и применяется на ЭВМ VAX для языков высокого уровня как стандартный способ обработки символьных строк. Более детальные пояснения даются в гл. 9. Этот способ требует присоединения к символьной строке дескриптора. Дескриптор состоит из двух длинных слов. Первое длинное слово разбивается на два слова. Первое из этих слов задаёт длину строки в байтах. Второе слово содержит некоторые коды, которые для системного программного обеспечения идентифицируют информацию как символьную строку. Во второе длинное слово помещается адрес первого байта символьной строки. Формат дескриптора порождается с помощью директивы .ASCID. Например, такая директива:
STRING: .ASCID "HELLO!!!"
эквивалентна следующему фрагменту программы, составленному из ранее рассмотренных директив:
STRING: .WORD 20$-10$ ; ДЛИНА = 8 .WORD ^X010E ; СПЕЦИАЛЬНЫЙ КОД .ADDRESS 10$ ; АДРЕС СТРОКИ 10$: .ASCII "HELLO!!!" ; СИМВОЛЬНАЯ СТРОКА 20$:
Директива .ADDRESS используется для занесения некоторого адреса в длинное слово памяти. По своему действию эта директива очень близка директиве .LONG, за исключением того, что директиву .LONG следует использовать только для определения констант, а директива .ADDRESS может применяться для задания переместимых адресов. Обе директивы поместили бы в длинное слово адрес 10$, но здесь для переместимых адресов следует использовать директиву .ADDRESS, так как она обеспечивает дополнительную информацию для ассемблера и компоновщика.
В следующей программе показано, как можно переписать предыдущий пример, адресуя элементы строки через дескриптор вместо непосредственного обращения к ним:
STRING: .ASCID "HELLO!!!" . . . PASS: CLRL R1 MOVZWL STRING,R2 10$: MOVB @STRING+4[R1],R0 JSB PROCESS AOBLSS R2,R1,10$
Обратите внимание, что уровень косвенности при адресации массива STRING в инструкции MOVB на один уровень выше, чем в предыдущем примере. Выражение STRING+4 определяет адрес длинного слова, содержащего адрес начала строки в коде ASCII, а @STRING+4 - адрес первого байта строки. Поэтому первый операнд инструкции MOVB имеет вид @STRING+4[R1]. Следует также отметить, что инструкция MOVZWL применяется для преобразования формата 16-битового слова, расположенного по адресу STRING, в формат длинного слова, пересылаемого в регистр R2 при интерпретации их содержимого как числа без знака. Это преобразование необходимо для инструкции AOBLSS, в которой требуется, чтобы первые два операнда имели формат длинного слова.
Тема ввода-вывода в целом весьма сложна и в существенной мере выходит за пределы настоящего изложения. Некоторые детали ввода-вывода рассматриваются в гл. 11. Поскольку необходимо все же каким-то образом осуществлять взаимодействие с выполняемыми программами, материал данного раздела предназначается для того, чтобы дать простейшую схему, по которой пользователи смогут считывать информацию с терминала и выводить её на него. Такие операции выполняются с помощью трёх подпрограмм, называемых IOINIT, RLINE и PLINE.
Эти подпрограммы входят в пакет подпрограмм, введённый в гл. 5 и описанный в приложении Б. Фактически IOINIT - это та самая подпрограмма, которая использовалась для инициализации подпрограмм RNUM и PNUM. Она инициализирует также подпрограммы RLINE и PLINE и должна быть вызвана перед вызовом любой из них. Подпрограмма инициализации не должна вызываться более одного раза, даже если вызываются все четыре подпрограммы ввода-вывода.
Точно так же, как подпрограммы RNUM и PNUM используются для ввода и распечатки чисел, подпрограммы RLINE и PLINE применяются для ввода и распечатки символьных строк. Обе подпрограммы считают, что основная программа имеет буфер или массив для хранения символов. При работе с подпрограммой RLINE в этот массив будут пересылаться вводимые символы, в случае подпрограммы PLINE этот массив содержит символы, подлежащие распечатке. Кроме того, подпрограмме PLINE необходимо знать, сколько должно распечатываться символов, а подпрограмма RLINE должна знать объём доступного для ввода пространства в массиве. Наконец, после возврата из подпрограммы RLINE основная программа должна быть в состоянии определить, сколько фактически было введено символов. Эта информация передаётся между программами через регистры R0 и R1. Регистр R0 содержит адрес буфера символов, a R1 - информацию о длине введённой строки.
Для распечатки, например, строки HELLO! можно использовать следующую последовательность операторов:
STRING: .ASCII "HELLO!!!" STRINGEND: . . . MOVAL STRING,R0 MOVZWL #STRINGEND-STRING,R1 JSB PLINE
Обратит внимание, что длина строки символов загружается в регистр R1 как слово. Делается это таким образом, потому что именно в таком формате она используется в подпрограмме PLINE. Инструкция MOVZWL применяется по двум причинам. Во-первых, оставлять мусор в старшей половине регистра - это плохая программистская практика, а по инструкции MOVZWL происходит очистка старших 16 битов регистров. Во-вторых, ассемблер выдаст предупреждающее сообщение, если значение STRINGEND-STRING окажется больше, чем 65535 или 216-1.
Обратите также внимание, что к выводимой строке не добавляются символы возврата каретки или перевода строки. Поскольку подпрограмма PLINE распечатывает только целиком строку, управление переводом строк автоматически поддерживается обычными средствами ввода-вывода операционной системы VAX/VMS. Если всё же символы возврата каретки и перевода строки включаются в текст, это приводит к выводу дополнительных строк.
Вызывающая последовательность подпрограммы PLINE хорошо согласуется с использованием дескрипторов строк, что можно увидеть из альтернативного способа распечатки той же самой строки:
DSTRING: .ASCID "HELLO!!!" . . . MOVL DSTRING+4,R0 ;СОДЕРЖИМЫМ DSTRING+4 ЯВЛЯЕТСЯ АДРЕС MOVZWL DSTRING,R1 ;B СЛОВЕ DSTRING СОДЕРЖИТСЯ ДЛИНА JSB PLINE
Подпрограмма RLINE работает аналогично. Основное отличие состоит в том, что она ожидает, что ей будет сообщена максимальная длина буфера. Это необходимо, чтобы программа была защищена от приёма строк, превосходящих по длине размер буфера и перекрывающих программную область. Примером работы подпрограммы RLINE может служить следующий фрагмент, предназначенный для считывания вводимой строки в буфер, вмещающий 80 символов:
BSIZE=80 BUFFER: .BLKB BSIZE . . . MOVAL BUFFER,R0 ;ПОМЕСТИТЬ АДРЕС БУФЕРА В R0 MOVZWL #BSIZE,R1 ;ПОМЕСТИТЬ ДЛИНУ БУФЕРА В R1 JSB RLINE
После возврата из подпрограммы RLINE произойдёт следующее:
Множество причин может вызывать ошибки при вводе. Три наиболее частые из них: было введено больше символов, чем может вместить буфер; была попытка чтения после достижения признака конца вводимых данных (особенно вероятно в системе пакетной обработки); какой-либо тип программной ошибки, например не была вызвана подпрограмма IOINIT. Поскольку сообщается код ошибки, то программа может проверить, не случилось ли что-либо подобное. Например, некоторые программы разрабатываются так, что выполняют чтение до тех пор, пока не кончатся все данные. Код ошибки, порождаемой при достижении конца данных, может использоваться для завершения работы программы. Код ошибки распознаётся так: если в регистре R0 содержится число 1 (в формате слова), то программа работает нормально и ошибки нет; любое другое значение обозначает какую-то ошибку. Это является стандартной процедурой обработки ошибок для программ, выполняемых в среде операционной системы VAX/VMS. Число различных кодов ошибок весьма значительно, поэтому лучшее, что можно сделать, - это позволить операционной системе самой описать для вас ошибку. Это можно сделать, передав системе код ошибки, поместив его в строку $EXIT_S. В данном случае это выглядит как $EXIT_S R0. Код ошибки следует проконтролировать в программе с помощью таких инструкций:
JSB RLINE CMPW R0,#1 BEQL 10$ $EXIT_S R0 10$: СЛЕДУЮЩАЯ ИНСТРУКЦИЯ
Заметим, что пара инструкций CMPW R0,#1 и BEQL 10$ может быть заменена одной инструкцией, которая будет рассмотрена в следующем разделе. Заметим также, что подпрограмма RLINE - единственная из пяти подпрограмм ввода-вывода в этом пакете, возвращающая код ошибки. Все другие подпрограммы ошибки игнорируют. Более подробное описание кодов ошибок и того, что с ними делать, приведено в гл. 11.
Зачастую бывает удобно упаковывать информацию в словах или байтах в виде битовых групп разного размера. Ниже приводится несколько примеров отдельных элементов слов или байтов, ранее уже обсуждавшихся, имеющих особое значение.
Для упаковки и распаковки информации на ЭВМ VAX имеются три семейства инструкции. Эти инструкции предназначены для установки или очистки определённых битов слова или же для проверки состояния отдельных битов. Это следующие три семейства инструкций:
Мнемоника |
Назначение |
---|---|
BIS |
Установить биты (BIt Set) |
BIC |
Сбросить биты (BIt Clear) |
BIT |
Проверить биты (BIt Test) |
Каждое семейство инструкций включает инструкции для работы с операндами различного формата, а инструкции семейства BIS и BIC могут работать с двумя или тремя операндами. Полный набор инструкций:
Мнемоника | Код операции | Операнды | Операция | |
---|---|---|---|---|
BISB2 |
88 |
} |
mask, dst |
dst ← dst OR mask |
BISW2 |
А8 |
|||
BISL2 |
С8 |
|||
BISB3 |
89 |
} |
mask, src, dst |
dst ← src OR mask |
BISW3 |
А9 |
|||
BISL3 |
С9 |
|||
BICB2 |
8А |
} |
mask, dst |
dst ← dst AND (NOT mask) |
BICW2 |
АА |
|||
BICL2 |
СА |
|||
BICB3 |
8В |
} |
mask, src, dst |
dst ← src AND (NOT mask) |
BICW3 |
АВ |
|||
BICL3 |
СВ |
|||
BITB |
93 |
} |
mask, src |
src AND mask |
BITW |
В3 |
|||
BITL |
D3 |
Первый операнд каждой инструкции - это маска, используемая для выделения некоторых битов во втором операнде. В инструкциях семейств BIS и BIC, работающих с двумя операндами, результат запоминается во втором операнде. В инструкциях с тремя операндами результат помещается в третий операнд. Это похоже на инструкцию ADDL2 в сравнении с инструкцией ADDL3. Например, инструкция BISW2 приводит к тому, что каждый бит второго операнда, имеющего формат слова, устанавливается в 1, если установлен в 1 соответствующий бит слова маски. Биты во втором операнде, которым соответствуют нулевые биты маски, не изменяются. Как видно из описания, это представляет собой побитовую операцию ИЛИ, выполняемую над маской и вторым операндом. Рассмотрим, например, 16-битовые двоичные числа до и после выполнения инструкции BISW2:
X = 1101 1110 0001 0001 |
|
Y = 1100 0101 0110 1000 |
До выполнения BISW2 X,Y |
Х = 1101 1110 0001 0001 |
|
Y = 1101 1111 0111 1001 |
После выполнения BISW2 X,Y |
Обратите внимание, что маска не меняется. Для варианта инструкции с тремя операндами - BISW3 X,Y,Z значение Y остаётся неизменным, а результат помещается в Z.
Инструкция BICW2 подобна приведённой выше, за исключением того, что происходит очистка (сброс) битов второго операнда, если установлены в 1 соответствующие биты маски. Эта операция совпадает с логической операцией И, выполняемой над поразрядным дополнением маски (операция НЕ) и вторым операндом (см. гл. 2). Повторим теперь предыдущий пример, используя инструкцию BICW2:
X = 1101 1110 0001 0001 |
|
Y = 1100 0101 0110 1000 |
До выполнения BICW2 X,Y |
X = 1101 1110 0001 0001 |
|
Y = 0000 0001 0110 1000 |
После выполнения BICW2 X,Y |
Семейство инструкций BIT отличается от двух рассмотренных семейств; оно схоже с семейством инструкций CMP в том, что вычисленный результат не сохраняется. Оба семейства используются лишь для установки кодов условий. Для семейства инструкций BIT вычисленный результат определяется так: единица в данной битовой позиции результата будет только тогда, когда оба операнда имеют 1 в соответствующей позиции. Это логическая операция И. Если в вычисленном результате не окажется ни одной 1, будет установлен код условия Z, который может быть проверен с помощью инструкций BEQL и BNEQ. Если равен 1 старший бит вычисленного результата, устанавливается код условия N, который можно проверить инструкциями BLSS и BGEQ. Поскольку ни переполнения, ни переноса произойти не может, эти коды условий обычно не используются. Однако, чтобы сохранить совместимость, так как многие программисты могут захотеть воспользоваться инструкциями переходов, бит V всегда очищается, а бит С остаётся без изменения. Инструкции семейств BIC и BIS устанавливают коды условий аналогично, за исключением того, что биты N и Z зависят от значения, сохраняемого в операнде получателя, а не от значения временного результата.
Покажем пример работы инструкции BITW, используя прежние значения X и Y:
X = | 1101 1110 0001 0001 |
|
Y = | 1100 0101 0110 1000 |
До и после выполнения BITW X,Y |
врем. = |
1100 0100 0000 0000 |
После BITW X,Y (врем., не сохраняется) |
Инструкция BITW X,Y оставляет значения X и Y без изменения. Временный результат, который не сохраняется, используется для установки следующих кодов условий:
N = 1
Z = 0
V = 0
С = Сохраняет предыдущее значение до выполнения инструкции BITW
Обратите внимание, что использование бита Z может приводить к путанице. Этот бит устанавливается в 1 (значение "истина"), если равен 0 временный результат, но это означает лишь то, что в исходном операнде выделенные по маске биты равны 0.
Похожим на предыдущие семейства инструкций побитовой обработки является семейство XOR Инструкции этого семейства вычисляют побитовое ИСКЛЮЧАЮЩЕЕ_ИЛИ двух операндов и запоминают результат или во втором, или в третьем операнде в зависимости от того, используется ли инструкция с двумя или тремя операндами. Результат операции ИСКЛЮЧАЮЩЕЕ_ИЛИ, выполняемой над двумя битами, равен 1 тогда, и только тогда, когда равен 1 какой-либо один из битов, но не оба вместе. Это можно увидеть из следующей таблицы:
0 ИСКЛЮЧАЮЩЕЕ_ИЛИ 0 = 0 0 ИСКЛЮЧАЮЩЕЕ_ИЛИ 0 = 1 1 ИСКЛЮЧАЮЩЕЕ_ИЛИ 0 = 1 1 ИСКЛЮЧАЮЩЕЕ_ИЛИ 1 = 0
Используя прежние значения X и Y из примеров предыдущего раздела, можем посмотреть, как работает инструкция XORW2:
X = 1101 1110 0001 0001 |
|
Y = 1100 0101 0110 1000 |
До выполнения XORW2 X,Y |
X = 1101 1110 0001 0001 |
|
Y = 0001 1011 0111 1001 |
После выполнения XORW2 X,Y |
Рекомендуем обратить внимание на сходство и различие двух семейств BIS и XOR, инструкции семейства BIS выполняют операцию ИЛИ, тогда как инструкции семейства XOR выполняют операцию ИСКЛЮЧАЮЩЕЕ ИЛИ.
Семейство инструкций XOR представлено ниже полностью:
Мнемоника | Код операции | Операнды | Операция | |
---|---|---|---|---|
XORB2 |
8С |
} |
mask, dst |
dst ← dst XOR mask |
XORW2 |
АС |
|||
XORL2 |
CC |
|||
XORB3 |
8D |
} |
mask, src, dst |
dst ← src XOR mask |
XORW3 |
AD |
|||
XORL3 |
CD |
Последнее множество инструкций побитовой обработки представляет семейство MCOM (Move COMplement - переслать дополнение), инструкции которого просто инвертируют все биты исходного операнда и запоминают их в операнде назначения. Другими словами, единичные биты заменяются нулевыми и наоборот. Например, инструкция MCOMW X,Y произведёт следующее:
X = 1101 1110 0001 0001 |
До выполнения MCOMW X,Y. |
Y = 0010 0001 1110 1110 |
После выполнения MCOMW X,Y. |
Семейству MCOM аналогично семейство инструкций MNEG, используемое для изменения знака числа, представленного в дополнительном коде. Инструкции семейства MCOM могут использоваться для изменения знака числа, представленного в обратном коде, как описано в гл. 2. Однако в VAX, где числа со знаком представляются в дополнительном, а не в обратном коде, инструкции семейства MCOM обычно используются только для работы с битами. В действительности отрицательное число в дополнительном коде можно получить, прибавив единицу к представлению этого числа в обратном коде. Три инструкции этого семейства выглядят так:
Мнемоника | Код операции | Операнды | Операция | |
---|---|---|---|---|
MCOMB |
88 |
} |
src, dst |
dst ← NOT src |
MCOMW |
А8 |
|||
MCOML |
С8 |
На рис. 8.4 и 8.5 показаны два программных сегмента, в которых используются инструкции побитовой обработки. В первом, на рис. 8.4, из длинного слова N извлекается третья шестнадцатеричная цифра, которая помещается при возврате управления как один байт символьного кода в регистр R0.
В программном сегменте на рис. 8.5 вычисляется то, что называют битом чётности. Как отмечалось ранее, код ASCII, которым мы пользуемся, является 7-битовым. Однако для обнаружения ошибок при передаче символов кода ASCII между ЭВМ и устройствами ввода-вывода иногда добавляют восьмой бит. Этот бит называется битом чётности, он устанавливается в 1 или 0 так, чтобы при этом общее число единиц в 8-битовом коде стало чётным. Это называют проверкой на чётность (even parity). Если теперь из-за помех или неисправности произойдёт изменение одной из единиц в нуль или одного из нулей в единицу, общее число единиц станет нечётным (проверка на нечётность - odd parity), и эта ошибка обнаруживается. (Замечание. В некоторых системах производится проверка на нечётность, и тогда чётное число единиц означает ошибку. Технические и программные средства ЭВМ VAX позволяют осуществлять связь с терминалами, производящими проверку на чётность, нечётность или не выполняющими проверки по чётности вообще. Поскольку биты чётности обрабатываются техническими и программными средствами ЭВМ VAX без участия пользователя, пользователям обычно даже нет нужды знать об их существовании.
В программном сегменте на рис. 8.5 7-битовый код ASCII выбирается из регистра R0 и определяется, является ли нечётным или чётным общее число единиц в этом коде. Битом чётности является дополнительный бит в позиции 7, с помощью которого производится проверка байта на нечётность.
N: .BLKL 21 C_DIG_3:MOVL N,R0 ;ПОМЕСТИТЬ ЧИСЛО В R0 BICL2 #^XFFFFF0FF,R0 ;ОЧИСТИТЬ ВСЕ РАЗРЯДЫ, КРОМЕ РАЗРЯДА 3 ASHL #-8,R0,R0 ;СДВИНУТЬ ВПРАВО НА 8 BISB2 #^X30,R0 ;ВСТАВИТЬ КОД ASCII CMPB R0,#^X39 ;ДЕСЯТИЧНАЯ ЦИФРА ? BLEQ 10$ ;ДА, ОСТАВИТЬ КАК ЕСТЬ ADDB2 #^X41-^X30-10,R0 ;НЕТ,ПРЕОБРАЗОВАТЬ В ШЕСТНАДЦАТЕРИЧНУЮ RSB ;ВОЗВРАТ ИЗ ПОДПРОГРАММЫ
Рис. 8.4. Программа для распечатки третьей шестнадцатеричной цифры слова
; ; ПОДПРОГРАММА ODDGN: СОПРОВОЖДЕНИЕ БАЙТА КОНТРОЛЬНЫМ БИТОМ ЧЁТНОСТИ ; MASK: .BLKB 1 TEST: .BLKB 1 ODDGN: BICB2 #^X80,R0 ;СБРОСИТЬ БИТ ЧЁТНОСТИ MOVB #1,MASK ;СФОРМИРОВАТЬ ПЕРВУЮ МАСКУ MOVL #7,R1 ;В КОДЕ 7 БИТ CLRB TEST ;TEST БУДЕТ ПОКАЗЫВАТЬ ЧЁТНОСТЬ 10$: BITB MASK,R0 ;ПРОВЕРИТЬ ЗНАЧЕНИЕ БИТА BEQL 20$ ;ПРОПУСТИТЬ, ЕСЛИ 0 INCB TEST ;УВЕЛИЧИТЬ TEST НА ЕДИНИЦУ 20$: ASHB #1,MASK,MASK ;СДВИНУТЬ БИТ МАСКИ SOBGTR R1,10$ ;ОТСЧИТАТЬ 7 БИТ BITB #1,TEST ;ПРОВЕРИТЬ ЧЁТНОСТЬ BNEQ 30$ ;ЕСЛИ КОД ЧЁТНЫЙ,ЗАВЕРШИТЬ ОБРАБОТКУ BISB2 #^X80,R0 ;ИНАЧЕ УСТАНОВИТЬ БИТ ЧЁТНОСТИ 30$: RSB ;ВОЗВРАТ УПРАВЛЕНИЯ,РЕЗУЛЬТАТ В R0
Рис. 8.5. Подпрограмма формирования байта с проверкой по нечётности
; ; ЧТЕНИЕ ДЕСЯТИЧНОГО ЧИСЛА,РЕЗУЛЬТАТ ПОМЕЩАЕТСЯ В R0 ; BSIZE=80 BUFFER: .BLKB BSIZE DATA: .BLKB 1 READ: MOVAL BUFFER,R0 ;ВВЕСТИ СТРОКУ ТЕКСТА MOVZWL #BSIZE,R1 ;C ПОМОЩЬЮ RLINE JSB RLINE ;B R1 БУДЕТ ДЛИНА СТРОКИ MOVAL BUFFER,R2 ;ПОЛУЧИТЬ АДРЕС ВХОДНОЙ СТРОКИ CLRL DATA ;НАЧАЛЬНЫЙ РЕЗУЛЬТАТ 0 10$: DECL R1 ;ПРОВЕРИТЬ ЧИСЛО СИМВОЛОВ BLSS 20$ ;ЕСЛИ ДОСТИГНУТ КОНЕЦ СТРОКИ,ЗАКОНЧИТЬ MOVB (R2)+,R0 ;ПОЛУЧИТЬ СИМВОЛ BICL #^XFFFFFFF0,R0 ;ОТСЕЧЬ ЛИШНИЕ БИТЫ MULL2 #10,DATA ;DATA=DATA*10 ADDL2 R0,DATA ;СЛОЖИТЬ СО ЗНАЧЕНИЕМ ЦИФРЫ BRB 10$ ;ПОЛУЧИТЬ СЛЕДУЮЩУЮ ЦИФРУ (ЕСЛИ ЕСТЬ) 20$: MOVL DATA,R0 ;ЗАПОМНИТЬ РЕЗУЛЬТАТ RSB ;ВОЗВРАТИТЬ УПРАВЛЕНИЕ
Рис. 8.6. Подпрограмма ввода десятичного числа
В последнем примере используются различные особенности инструкций побитовой обработки и сдвига при работе с символьными строками. Эта программа (показанная на рис. 8.6) считывает десятичное число без знака, ввод числа выполняется с помощью подпрограммы ввода строки RLINE. Значение десятичного числа помещается в ячейку DATA. Поскольку число вводимых символов не контролируется, результат может переполнять длинное слово. Не выполняется также и контроль ошибок. Если пользователь вводит с терминала символы, отличные от десятичных цифр, результат будет бессмысленным.
Обратите внимание на то, как работает эта программа. В начале работы содержимое ячейки DATA равно 0. Затем каждый раз, когда берётся очередная цифра введённого числа, число в ячейке DATA умножается на десять и к нему прибавляется значение этой цифры. Предположим, например, что вводится число 573. Число в DATA есть 0, а первая цифра есть 5. Число из DATA, умноженное на 10, остаётся по-прежнему 0, плюс 5 даёт 5. Следующая цифра 7. Число в DATA, умноженное на 10, даёт 50, прибавив 7, получаем 57. Последняя цифра это 3. Умножение на 10 числа в DATA даёт 570, а после прибавления 3 получаем 573. После достижения последней цифры числа процесс завершается. Описание этого алгоритма можно найти в гл. 2. Примеры, показанные на рис. 8.4 - 8.6 могут быть переписаны более эффективно. Например, на рис. 8.6 инструкции
MULL2 #10,DATA ;DATA=DATA*10
ADDL2 R0,DATA ;СЛОЖИТЬ СО ЗНАЧЕНИЕМ ЦИФРЫ
можно заменить одной инструкцией EMUL, которая уже приводилась в гл. 6. Это и другие улучшения мы оставляем читателям в качестве упражнений.
Как видно из примера с умножением в конце гл. 6 и примера на рис. 8.5, нередко возникает желание осуществить проверку младшего разряда числа. Поэтому с этой целью введены две специальные инструкции:
Мнемоника | Значение |
---|---|
BLBS |
Перейти, если младший бит установлен (Branch if Low Bit is Set) |
BLBC |
Перейти, если младший бит сброшен (Branch if Low Bit is Clear) |
Эти инструкции проверяют младший бит байта, слова, длинного слова или квадраслова; переход по некоторому адресу осуществляется, если бит установлен в 1 - в случае инструкции BLBS или если он сброшен в 0 - в случае инструкции BLBC. Обычно не имеет значения, является ли адресуемый элемент словом или байтом или чем-то иным, поскольку младший бит находится в младшем байте, а байты, слова, длинные слова и квадраслова адресуются заданием адреса их младшего байта. (Однако, если используется для индексации регистр или режим адресации с автоувеличением, операнд рассматривается как длинное слово в том смысле, что содержимое регистра умножается или увеличивается на четыре). Пример использования инструкции BLBS: по инструкции
BLBS X,50$
будет осуществлён переход на метку 50$, если самый младший бит операнда X равен 1. Инструкция BLBS может быть применена в программе на рис. 8.5, что даст экономию одной инструкции. Заметим, что последние четыре инструкции программы таковы:
BITB #1,TEST BNEQ 30$ BISB #^X80,R0 30$: RSB
Их можно изменить так:
BLBS TEST,30$ BISB #^X80,R0 30$: RSB
И это экономит одну инструкцию. Обратите внимание, что в этой инструкции применяется 8-битовое смещение, ограничивающее диапазон переходов так же, как в других инструкциях переходов.
Другое применение этих инструкций связано с кодами системных ошибок, описанными в разделе, который посвящён подпрограмме RLINE. Там отмечалось, что кодом нормального завершения является 1. Однако все другие коды ошибок представляют собой чётные числа. Это позволяет использовать инструкции BLBS и BLBC для проверки ошибок. Таким образом, текст программы контроля ошибок, показанный ранее, может быть упрощён следующим образом:
Первоначальный вариант |
Вариант с использованием BLBS |
---|---|
. . . JSB RLINE CMPW R0,#1 BEQL 10$ $EXIT_S R0 10$: . . . |
. . . JSB RLINE BLBS R0,10$ $EXIT_S R0 10$: . . . |
Хотя наиболее распространённым символьным кодом, применяемым на ЭВМ VAX, является код ASCII, имеет смысл упомянуть и другие способы кодирования. Особое значение имеет кодирование, применяемое в перфокарточном оборудовании. Оно известно также как код Холлерита, получивший своё название в честь Германа Холлерита, который ввёл в употребление перфокарты для табуляторов во время переписи населения США в 1890 г.
Носителем информации, представленной в коде Холлерита, является бумажная карта, совпадающая по высоте и ширине с долларовой банкнотой тех времён. На карте (см. рис. 8.7) могут пробиваться прямоугольные отверстия в любой из 80 позиций (колонок) по горизонтали и в любой из 12 позиций (рядов) по вертикали.
Каждая колонка предназначена для перфорации одного символа; следовательно, перфокарта может содержать 80 закодированных символов. Поскольку имеется 12 рядов и так как предположительно может быть составлена любая возможная комбинация из 12 пробивок, перфокарточный код способен вместить алфавит, содержащий до 212=4096 различных символов. На практике, однако, если пробить слишком много отверстий, перфокарта становится похожа на сетку и теряет физическую жёсткость. Это вынудило ограничить кодовые комбинации лишь такими, в которых обычно в одной колонке содержится не более трёх пробивок[5].
Рис. 8.7. Перфокарта Холлерита
А |
1-12 |
J |
1-11 |
Не использ. |
1-0 |
В |
2-12 |
К |
2-11 |
S |
2-0 |
С |
3-12 |
L |
3-11 |
Т |
3-0 |
D |
4-12 |
М |
4-11 |
U |
4-0 |
Е |
5-12 |
N |
5-11 |
V |
5-0 |
F |
6-12 |
О |
6-11 |
W |
6-0 |
G |
7-12 |
Р |
7-11 |
X |
7-0 |
Н |
8-12 |
Q |
8-11 |
Y |
8-0 |
I |
9-12 |
R |
9-11 |
Z |
9-0 |
Рис. 8.8. Код Холлерита для алфавита
Ряды перфокарты нумеруются сверху вниз в следующем порядке: 12, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8 и 9. Коды цифр от 0 до 9 представляются одной пробивкой в соответствующем ряду (т.е. код цифры 5 - это пробивка в ряду 5, или пробивка пять). Коды букв формируются из комбинации одной пробивки в рядах 1-9 (цифровая пробивка) и второй пробивки в ряду 12, 11 или 0 {зонная пробивка). Это даёт 9x3 = 27 возможных комбинаций, из которых используются 26. Таблица кодов алфавита дана на рис. 8.8. Обратите внимание, что единственный незадействованный код 0-1 расположен в середине таблицы. Холлерит не задействовал этот код, поскольку опасался, что его машина могла бы порвать перфокарты с двумя пробивками в соседних рядах. Помимо этого одиночные пробивки в рядах 12 и 11 и отсутствие перфорации вообще используются для представления знаков &, -, пробела.
По мере усложнения обработки данных возникла потребность в большем разнообразии знаков пунктуации. Она была удовлетворена путём расширения поля цифровых пробивок с 9 до 15 при использовании пробивки 8 в комбинации с одной из пробивок от 2 до 7. Например, перфорация 8-5 имеет числовое значение 13. Кроме того, более развитое оборудование позволило использовать комбинацию 0-1. Это обеспечило возможность представления 64 символов.
Соответствие расположения пробивок знакам пунктуации весьма произвольно, и фактически существуют несколько различных назначений. На рис. 8.9 показан наиболее распространённый перфокарточный код. Его называют кодом 029, поскольку он появился на модели 29 перфоратора фирмы IBM. Однако название "код 29" вводит в некотором смысле в заблуждение, поскольку фирма IBM производит перфораторы модели 29 с целым рядом различных назначений. В частности, приведённое назначение кодов свойственно для перфоратора модели 029-EH. Следует, однако, заметить, что назначения кодов для цифр и букв стандартны, меняются только коды знаков пунктуации.
Пробел |
Нет пробивки |
& |
12 |
_ |
11-0 |
0 |
|
1 |
1 |
A |
12-1 |
J |
11-1 |
/ |
0-1 |
2 |
2 |
B |
12-2 |
К |
11-2 |
S |
0-2 |
3 |
3 |
C |
12-3 |
L |
11-3 |
T |
0-3 |
4 |
4 |
D |
12-4 |
М |
11-4 |
U |
0-4 |
5 |
5 |
Е |
12-5 |
N |
11-5 |
V |
0-5 |
6 |
6 |
F |
12-6 |
O |
11-6 |
W |
0-6 |
7 |
7 |
G |
12-7 |
Р |
11-7 |
X |
0-7 |
8 |
8 |
H |
12-8 |
Q |
11-8 |
Y |
0-8 |
9 |
9 |
I |
12-9 |
R |
11-9 |
Z |
0-9 |
: |
8-2 |
¢ |
12-8-2 |
! |
11-8-2 |
Не использ. |
0-8-2 |
# |
8-3 |
. |
12-8-3 |
$ |
11-8-3 |
, |
0-8-3 |
@ |
8-4 |
< |
12-8-4 |
* |
11-8-4 |
% |
0-8-4 |
| |
8-5 |
( |
12-8-5 |
) |
11-8-5 |
- |
0-8-5 |
= |
8-6 |
+ |
12-8-6 |
; |
11-8-6 |
> |
0-8-6 |
" |
8-7 |
| |
12-8-7 |
^ |
11-8-7 |
? |
0-8-7 |
Рис. 8.9. Код перфоратора 029-EH фирмы IBM
Младшие 5 битов |
Старшие 3 бита |
|||||||
---|---|---|---|---|---|---|---|---|
|
000 |
001 |
010 |
011 |
100 |
101 |
110 |
111 |
00000 |
NUL |
DS |
Sp |
|
|
|
|
|
00001 |
SOH |
SOS |
|
|
a |
|
A |
|
00010 |
STX |
FS |
|
|
b |
s |
B |
S |
00011 |
ETX |
|
|
|
c |
t |
C |
T |
00100 |
PF |
BYP |
|
|
d |
u |
D |
U |
00101 |
HT |
LF |
|
|
e |
V |
E |
V |
00110 |
LC |
ETB |
|
|
f |
w |
F |
W |
00111 |
DEL |
ESC |
|
|
g |
X |
G |
X |
01000 |
|
|
|
|
h |
y |
H |
Y |
01001 |
|
|
|
|
i |
z |
i |
Z |
01010 |
SMM |
SM |
¢ |
|
|
|
|
|
01011 |
VT |
CU2 |
. |
, |
|
|
|
|
01100 |
FF |
|
< |
% |
|
|
|
|
01101 |
CR |
ENQ |
( |
- |
|
|
|
|
01110 |
SO |
ACK |
+ |
> |
|
|
|
|
01111 |
SI |
BEL |
| |
? |
|
|
|
|
10000 |
DLE |
|
& |
|
|
|
|
0 |
10001 |
DC1 |
|
|
|
j |
|
J |
1 |
10010 |
DC2 |
SYN |
|
|
k |
|
K |
2 |
10011 |
TM |
|
|
|
l |
|
L |
3 |
10100 |
RES |
PN |
|
|
m |
|
M |
4 |
10101 |
NL |
RS |
|
|
n |
|
N |
5 |
10110 |
BS |
UC |
|
|
o |
|
O |
6 |
10111 |
IL |
EOT |
|
|
p |
|
P |
7 |
11000 |
CAN |
|
|
|
q |
|
Q |
8 |
11001 |
EM |
|
|
|
r |
|
R |
9 |
11010 |
CC |
|
! |
: |
|
|
|
|
11011 |
CU1 |
CU3 |
$ |
# |
|
|
|
|
11100 |
IFS |
DC4 |
* |
@ |
|
|
|
|
11101 |
IGS |
NAK |
) |
' |
|
|
|
|
11110 |
IRS |
|
; |
= |
|
|
|
|
11111 |
IUS |
SUB |
^ |
" |
|
|
|
|
Рис. 8.10. Код EBCDIC
Код 029, содержащий 64 символа, является подмножеством 8-битового кода EBCDIC (расширенный двоично-десятичный код обмена информацией). Восемь битовых позиций обеспечивают 28, или 256, возможных кодовых комбинаций. В дополнение к 64 символам кода 029 код EBCDIC содержит строчные буквы, а также целый ряд управляющих символов. Код EBCDIC показан на рис. 8.10 в виде таблицы с 32 рядами и 8 колонками. Ряд определяет 5 правых битов, колонка - 3 левых. Например, прописная буква А занимает вторую позицию в 7-й колонке. Таким образом, двоичный код буквы A состоит из битов 110 (из колонки), за которыми следуют биты 00001 (из ряда), что даёт код 11000001. Обратите внимание, что многие комбинации не назначены каким-либо символам или управляющим символам. Код EBCDIC является стандартным на больших ЭВМ фирмы IBM, а также на ЭВМ, выпускаемых другими производителями. Промежутки между кодами букв I и J и кодами букв R и S предназначены для обеспечения совместимости с использовавшимися ранее кодами, являющимися производными от перфокарточного кода Холлерита.
A |
^X0F0F0F0F |
В |
^XF0F0F0F0 |
С |
^X12345678 |
D |
^X87654321 |
Что произойдёт с содержимым ячеек и кодами условий N, Z, С и V в результате выполнения следующих инструкций над первоначальным содержимым этих ячеек ?
а. | BISL2 A,B |
б. | BICL2 A,C |
в. | BICL2 C,A |
г. | BISL2 B,C |
д. | BITL2 C,D |
е. | BITL2 C,C |
а. XORL2 A,B XORL2 B,A XORL2 A,B
(сопоставьте новые значения A и B с первоначальными);
б. MOVL A,B XORL2 B,B
(значение B);
в. MOVL A,B DECL B COML B
(сопоставьте B и A);
г. MOVL A,B XORL2 #-1,B INCL B
(сопоставьте B и A);
д. 10$: XORL3 A,B,C MCOML A,A BICL2 A,B MOVL C,A ASHL #1,B,B BNEQ 10$
(сопоставьте C с первоначальным значением A и B).
А ВСТРЕТИЛОСЬ 129 РАЗ В ВСТРЕТИЛОСЬ 17 РАЗ С ВСТРЕТИЛОСЬ 18 РАЗ
< НАЗАД | ОГЛАВЛЕНИЕ | ВПЕРЁД > |
[1] Некоторые терминалы обладают способностью обрабатывать одновременное нажатие клавиш так, что оно воспринимается как последовательность одиночных нажатий.
[2] Некоторые устройства не могут выполнять эту функцию.
[3] Большинство устройств не в состоянии выполнять эту функцию.
[4] American National Standards Institute - Американский национальный институт стандартов. - Прим. ред.
[5] Из этого правила существует несколько исключений, касающихся перфокарт специального назначения, таких как перфокарты конца файла, перфокарты с двоичным кодом, а также некоторых кодов расширенного алфавита, которые могут включать строчные буквы и управляющие символы.