ГЛАВА 8. СИМВОЛЬНАЯ ИНФОРМАЦИЯ

8.1. ПРЕДСТАВЛЕНИЕ СИМВОЛЬНОЙ ИНФОРМАЦИИ

ВВЕДЕНИЕ

До сих пор обсуждались вопросы, касающиеся того, как выполняются числовые вычисления, и ничего не было сказано о символьной информации, хотя совершенно очевидно, что какой-то способ обработки символьных данных должен существовать. В конце концов, и ассемблер, и операционная система, и компиляторы Паскаля и Фортрана - все имеют дело с операторами, представляющими собой строки текстовых символов. Более того, поскольку все эти обрабатывающие программы на самом деле являются обычными машинными программами, то любая программа может быть способна выполнять обработку символьных данных. Сразу же возникает вопрос о том, каким образом символьная информация представляется в машине. Ответ заключается в следующем: текстовые символы кодируются в виде двоичных чисел, дающих уникальное представление каждого символа алфавита. Поскольку следует иметь в виду ещё цифры и знаки пунктуации, нередко используют термин алфавитно-цифровые символы. Фактически используемое кодирование может быть произвольным. Другими словами, интерпретация символьного кода полностью определяется разработчиком устройства ввода-вывода, применяемого для считывания и распечатки символов. В ЭВМ VAX используется код, который первоначально был стандартным кодом для телеграфных аппаратов-телетайпов, но в настоящее время применяется для всех типов устройств ввода-вывода. Этот код называется ASCII, что означает Американский стандартный код для обмена информацией. Другой символьный код называется EBCDIC, он произошёл от кода для перфокарт Холлерита и будет рассмотрен позднее.

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

СИСТЕМА КОДОВ ASCII

Как только что было сказано, система кодов 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 символов.

Таблица 8.1. Набор символов кода ASCII

^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 НА КЛАВИАТУРЕ

Из-за большого размера алфавита кода 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

В ЭВМ семейства 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

8.2. ОБРАБОТКА СИМВОЛЬНОЙ ИНФОРМАЦИИ

БАЙТЫ И ИНСТРУКЦИИ РАБОТЫ С БАЙТАМИ

Во внутреннем представлении ЭВМ 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, в которой требуется, чтобы первые два операнда имели формат длинного слова.

УПРАЖНЕНИЯ 8.1

  1. Используя телетайп или видеотерминал, работающий в автономном режиме (т.е. не подключенный к ЭВМ, а передающий символы самому себе), выясните действие всех его клавиш. Используйте также клавиши смены регистра (SHIFT) или управления (CTRL) совместно со всеми другими клавишами и опишите, что происходит. В частности, обратите внимание на следующие вопросы:
    • а)  Имеет ли ваш терминал прописные и строчные буквы? Если нет, то что происходит при смене регистра? Возможно ли напечатать все буквы при нажатой клавише смены регистра? Если есть строчные буквы, то имеется ли клавиша блокировки смены регистра или клавиша верхнего регистра? Какое между ними различие?
    • б)  Каково действие клавиши управления? Какие управляющие символы вызывают какое-либо видимое действие, а если вызывают, то какое именно? Всегда ли вы замечаете какой-то видимый эффект?
    • г)  Некоторые терминалы реагируют на символ управляющей последовательности кода (ESC), сопровождаемый другими символами. Посмотрите, сможете ли вы заметить какую-либо реакцию.
  2. Повторите все шаги п. 1 упр. 8.1 на терминале, подключённом к вычислительной системе VAX. Как вы объясните различия в получаемых результатах при выполнении п.1 и п.2?

8.3. УПРОЩЁННЫЙ ВВОД-ВЫВОД

Тема ввода-вывода в целом весьма сложна и в существенной мере выходит за пределы настоящего изложения. Некоторые детали ввода-вывода рассматриваются в гл. 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 произойдёт следующее:

  1. Введённые символы будут помещены в байтовый массив BUFFER. Поскольку эта подпрограмма работает построчно, никаких управляющих символов типа возврата каретки или перевода строки в буфер не заносится.
  2. Число фактически прочитанных символов будет помещено в формате слова в регистр R1. Оно может быть равно 0, если была нажата только клавиша "Возврат каретки", или любому числу вплоть до 80, если было напечатано столь много символов. Но ни в коем случае это число не будет превосходить то значение, которое было в регистре R1 первоначально, независимо от фактического числа вводимых символов.
  3. Код ошибки передаётся вызывающей программе в формате байта через регистр R0.

Множество причин может вызывать ошибки при вводе. Три наиболее частые из них: было введено больше символов, чем может вместить буфер; была попытка чтения после достижения признака конца вводимых данных (особенно вероятно в системе пакетной обработки); какой-либо тип программной ошибки, например не была вызвана подпрограмма 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.

8.4. ИНСТРУКЦИИ ПОБИТОВОЙ ОБРАБОТКИ

НЕОБХОДИМОСТЬ РАБОТЫ С БИТАМИ

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

  1. Бит 31 в длинном слове, бит 15 в слове или бит 7 в байте определяют знак числа.
  2. Бит 0 слова или байта сообщает о том, является ли представляемое число чётным или нечётным.
  3. Биты 5 и 6 символьного кода ASCII сообщают, является ли данный символ прописной или строчной буквой, управляющим символом или же входит в основную группу знаков пунктуации и цифр.

СЕМЕЙСТВА ИНСТРУКЦИЙ BIS, BIC И BIT

Для упаковки и распаковки информации на ЭВМ 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

}

mask, dst

dst ← dst AND (NOT mask)

BICW2

АА

BICL2

СА

BICB3

}

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

Похожим на предыдущие семейства инструкций побитовой обработки является семейство 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

}

mask, dst

dst ← dst XOR mask

XORW2

АС

XORL2

CC

XORB3

8D

}

mask, src, dst

dst ← src XOR mask

XORW3

AD

XORL3

CD

ИНСТРУКЦИИ MCOM

Последнее множество инструкций побитовой обработки представляет семейство 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. Это и другие улучшения мы оставляем читателям в качестве упражнений.

ИНСТРУКЦИИ BLBS И BLBC

Как видно из примера с умножением в конце гл. 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$:    . . .

8.5. ДРУГИЕ СПОСОБЫ ПРЕДСТАВЛЕНИЯ СИМВОЛЬНОЙ ИНФОРМАЦИИ

КОД ХОЛЛЕРИТА

Хотя наиболее распространённым символьным кодом, применяемым на ЭВМ 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 предназначены для обеспечения совместимости с использовавшимися ранее кодами, являющимися производными от перфокарточного кода Холлерита.

УПРАЖНЕНИЯ 8.2

  1. Задано, что содержимым ячеек A, B, C и D является следующее:

    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

  2. Напишите и выполните программу, которая осуществляет ввод символов с терминала и распечатывает каждый символ семь раз на строчке. Программа завершает работу, когда считывает пустую (нулевой длины) строку.
  3. Напишите программу, которая считывает строку и затем распечатывает её попеременно в прямом и обратном порядке семь раз. Длина строки может изменяться, но не должна превышать 80 символов.
  4. Напишите программу, предназначенную для выполнения в режиме пакетной обработки, которая считывает символы с перфокарты. Она распечатывает символы, заменяя все управляющие символы (включая "Возврат каретки" и "Перевод строки") символом "звёздочка" и литерой, клавишу которой надо было бы нажать вместе с клавишей управления CTRL для получения данного управляющего символа. Каждую комбинацию CTRL/J, или *J, сопроводите символами "Возврат каретки" и "Перевод строки" Какие управляющие символы вставляются системой пакетной обработки в ваши данные? Можно ли управляющие символы перфорировать на перфокартах? Если да, то как?
  5. Напишите программу, которая считывает 32-битовые двоичные числа как последовательности из 32 единиц и нулей в коде ASCII, сопровождаемой символами возврата каретки и перевода строки. Подпрограмма передаёт двоичное значение через регистр R0.
  6. Напишите подпрограмму, которая берёт значение из регистра R0 и распечатывает его как 32-битовое двоичное число. Печатайте по одному числу на строке при каждом вызове подпрограммы.
  7. Скомбинируйте подпрограммы из пп. 5 и 6 с основной программой, которая проверяет работу подпрограмм, вызывая их несколько раз.
  8. Напишите подпрограмму, которая распечатывает десятичные числа со знаком. Модифицируйте подпрограмму на рис. 8.6 для считывания десятичных чисел со знаком. Напишите основную программу, которая проверяет эти подпрограммы, вызывая их несколько раз. Подпрограмма вывода на печать должна обеспечивать подавление незначащих нулей в начале числа.
  9. Перепишите программы на рис. 8.4, 8.5 и 8.6 так, чтобы они стали более эффективными, воспользовавшись следующими рекомендациями в дополнение к любым другим, которые вы в состоянии придумать:
    • а)  на рис. 8.4 задачу решите с помощью индексации массива из шестнадцати символов из строки "0123456789ABCDEF";
    • б)  на рис. 8.5 используйте инструкции BLBS и BLBC для проверки битов в байте. Если важнее быстродействие, чем занимаемая память, то можно ли получить выигрыш, используя таблицу из 256 байтов?
    • в)  на рис. 8.6 воспользуйтесь инструкцией EMUL для замены инструкций MULL2 и ADDL2.
  10. Каждый из следующих программных сегментов выполняет некоторую простую операция. Однако применяются логические инструкции и выполняемая операция не обязательно очевидна. Определите, что выполняется в каждом сегменте?
    а.  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).

  11. * Напишите программу, которая считывает некоторый фрагмент текста. Затем программа распечатывает число печатных символов, встретившихся в тексте. Числа должны распечатываться в десятичном представлении с помощью такой подпрограммы, какая была написана для п. 8.   Ваша распечатка должна выглядеть примерно так:
    А ВСТРЕТИЛОСЬ  129     РАЗ
    В ВСТРЕТИЛОСЬ  17      РАЗ
    С ВСТРЕТИЛОСЬ  18      РАЗ

 

< НАЗАД ОГЛАВЛЕНИЕ ВПЕРЁД >


[1] Некоторые терминалы обладают способностью обрабатывать одновременное нажатие клавиш так, что оно воспринимается как последовательность одиночных нажатий.

[2] Некоторые устройства не могут выполнять эту функцию.

[3] Большинство устройств не в состоянии выполнять эту функцию.

[4] American National Standards Institute - Американский национальный институт стандартов. - Прим. ред.

[5] Из этого правила существует несколько исключений, касающихся перфокарт специального назначения, таких как перфокарты конца файла, перфокарты с двоичным кодом, а также некоторых кодов расширенного алфавита, которые могут включать строчные буквы и управляющие символы.