Text
                    Э.	3. Любимский
В. В. Мартынюк
Н. П. Трифонов
ПРОГРАММИРОВАНИЕ

'Э: 3. ЛЮБИМСКИЙ В. В. МАРТЫНЮК Н. П. ТРИФОНОВ ПРОГРАММИРОВАНИЕ Под редакцией а з. любимского Допущено Министерством высшего и среднего специального образования СССР в качестве учебного пособия для студентов вузов, обучающихся по специальности «Прикладная математика» МОСКВА «НАУКА» ГЛАВНАЯ РЕДАКЦИЯ ФИЗИКО-МАТЕМАТИЧЕСКОЙ ЛИТЕРАТУРЫ 1980
22.18 Л 93 УДК 519.6 Программирование. Л ю б и м с к и й Э. 3.» Мартынюк В.В.,Трифонов Н. П.— М.: Наука. Главная редакция физико- математической литературы, 1980 Книга содержит изложение двухгодич- ного курса лекций для студентов факуль- тета вычислительной математики и кибер- нетики МГУ. Курс соответствует новой программе для университетов. В книге нашли свое отражение те существенные изменения, которые произошли за послед- нее время как в практике использования ЭВМ, так и в общих подходах к вопросам программирования. Книга предназначена в качестве учеб- ного пособия для студентов факультетов прикладной математики университетов и вузов с повышенной математической под- готовкой. - 20204—141 Л ~053(02)-80 13-*°- 1702070000 © Издательство «Наука». Главная редакция физико-математической литература,
ОГЛАВЛЕНИЕ Предисловие........................................................... 7 Введение ...»......................................................... 9 Глава 1. Элементы теории алгоритмов................................ 13 1.1. Интуитивное понятие алгоритма и необходимость его уточнения . 13 1.2. Машины Тьюринга............................................. 17 1.2.1. Определение машины Тьюринга (17). 1.2.2. Примеры машин Тьюринга (22), 1.2.3. Возможности машин Тьюринга. Основная гипотеза теории алгоритмов (25). 1.3. Нормальные алгоритмы Маркова . . ........................... 29 1.4. Сравнение различных алгоритмических схем.................... 34 1.5. Понятие алгоритмической неразрешимости...................... 36 Г л а в а 2. Программирование на алгоритмических языках. Алгол-60 . . 40 2.1. Общая характеристика алгоритмических языков.............. 2.2. Способ описания алгоритмических языков . ,............... 2.2.1. Нормальная форма Бэкуса (45). 2.3. Алфавит алгола ......................................’ . . 2.4. Неформальное рассмотрение фрагментов алгол-программ . ... 2.5. Операнды................................................. 2.5.1. Классификация операндов (57). 2.5.2. Числа (59), 2.5.3. Переменные (61). 2.5.4. Операнды-функции (63). 2.6. Арифметические и логические выражения............... . . 2.6.1. Простые арифметические выражения (65). 2.6.2. Простые логические выражения (68). 2.6.3. Условные выражения (72). 2.7. Основные операторы....................................... 2.7.1. Оператор присваивания (76). 2.7.2. Оператор перехода и . именующие выражения (78). 2.7.3. Оператор процедуры. Опера- торы ввода и вывода (83). 2.7.4. Пустой оператор (86). 2.8. Условные операторы ... .................................. 2.9. Составные операторы...................................... 2.9.1. Примеры с использованием составных операторов, содержа- щие анализ фрагментов алгольного текста (90). 2.10. Операторы цикла.......................................... 2.10.1. Некоторые примеры с использованием операторов цикла. Оптимизация циклов (96). 2.11. Описания в алголе........................................ 2.12. Блоки и локализация...................................... 2.13. Процедуры и функции...................................... 2.13.1. Простейшие процедуры (112). 2.13.2. Локализация про- цедур (114). 2.13.3. Ограничения на фактические параметры (115). 2.13.4. Спецификации (116). 2.13.5. Параметры-значения (118). 2.13.6. Процедуры-функции (120). 2,13.7. Полный синтак- сис описания процедуры (122). 2.14. Рекурсивные процедуры.................................... 2.15. Использование комментариев............................... 2.16. Методы разработки программ............................... 2.16.1. Программирование по принципу сверху вниз (126). 2.16.2. Блок-схемы (130). 2.16.3. Программирование по принципу снизу (131), 2.16.4, Принципы структурного программирования 40 44 48 52 57 65 76 86 88 93 101 106 111 122 125 126 вверх (132),
4 ОГЛАВЛЕНИЕ Г л а в а 3. Структуры данных..................................... 133 3.1. Очереди.................................................. 3.2. Стеки . •.....................................‘........... 138 3.3. Строки.................................................... 144 3.3.1. Векторное представление строк (144). 3.3.2. Представ- ление строки в виде цепочки (145). 3.3.3. Включение симво- ла в строку и исключение символа (147). 3.3.4. Более сложные операции над строками (149). 3.3.5. Реализация универсаль- ного нормального алгоритма (152). 3.4. . Списки.................................................. 156 3.4.1. Однонаправленные списки (157). 3.4.2. Двунаправленные списки (160). 3.4.3. Кольцевые списки (162). 3.4.4. Иерархиче- ские списки (164). 3.4.5. Ассоциативные списки (Ц>5). 3.4.6. При- мер информационной сети с иерархическими и ассоциативными связями (166). 3.5. Таблицы................................................... 173 3.5.1. Простая цепочка (174). 3.5.2. Цепочка с упорядоченными записями (175). 3.5.3. Хранение ключей в отдельном массиве (176). 3.5.4. Дихотомический поиск по таблице в векторном пред- ставлении (177). 3.5.5. Двоичное дерево (180). 3.5.6. Перемешан- ные таблицы (186). 3.6. Отображение многомерных массивов на вектор................. 192 Глава 4. Электронно-вычислительные машины.......................... 196 4.1. Общая характеристика ЭВМ................................... 198 4. 1.1. Принципы фон Неймана (199). 4.1.2. Машинные операции (200 ). 4.1.3. Ячейки памяти (201). 4.2. Структура ЭВМ и ее работа.................................. 203 4.2.1. Формализованное описание работы ЭВМ (209). 4,3. Характеристика реальных ЭВМ................................ 212 4.3.1. Системы счисления (212). 4.3.2. Представление чисел (218). 4.3.3. Характеристика набора машинных операций (225). 4.3.4. Форматы команд (227). 4.4. Разнообразие систем команд ЭВМ. Примеры программ........... 228 4.4.1. Четырехадресная машина (УМ-4) (228). 4.4.2. Трехадресная машина (УМ-3) (238). 4.4.3. Двухадресная машина (УМ-2) (240). 4.4.4. Одноадресная машина (УМ-1) (245). 4.4.5. Дробно-адресная машина (УМ-Д) (249). 4.4.6. Машина с переменным форматом ко- манд (УМ-П) (251). 4.4.7. Безадресная (стековая) машина (УМ-С) (255). 4,4.8. Машины с индексными регистрами (УМИР) (263). Глава 5. Основные приемы программирования для ЭВМ............... 274- 5.1. Символическое программирование........................... 275 5.2. Характеристика УМИР-3.................................... 281 5.2.1. Общая характеристика УМИР-3 (281). 5.2.2. Основные арифметические операции (284). 5.2.3, Операции над порядками (285). 5.2.4. Операции над индексным регистром (286). 5.3. Программирование вычислений по формулам.................. 287 5.3.1. Экономия команд (289). 5.3.2. Выбор машинных операций (290). 5.3.3. Экономия рабочих ячеек и констант (291). 5.4. Программирование простых логических выражений............ 293 5.4.1. Представление логических значений (293). 5.4.2. Операции УМИР-3 логического типа (294). 5.4.3. Реализация логических операций (295). 5.4.4. Программирование простых логических выражений (296). 5.5, Программирование условных выражений и операторов......... 300
ОГЛАВЛЕНИЕ 5 5.6. Действия над кодами....................................... 301 5.6.1. Операции над кодами (302). 5.6.2. Преобразования кодов (303). 5.7. Программирование циклов................................... 307 5.7.1. Общие вопросы программирования циклов (307). 5.7.2. Цик- лы итерационного типа (309). 5.7.3. Циклы с известным числом повторений (312). 5.7.4. Управление переменными адресами (315). 5.8. Подпрограммы.............................................. 326 5.8.1. Понятие и назначение подпрограммы (326). 5.8.2. Переда- ча подпрограмме значений (328). 5.8.3. Передача подпрограмме адресов (331). 5.9. Стандартные подпрограммы.................................. 336 5.9.1. Интерпретирующая система ИС-2 (339). Г л а в а 6. Системы программирования............................. 344 .6.1. Модульное программирование.................................. 348 6.1.1. Понятие и назначение модуля (348). 6.1.2. Составление мо- дулей (354). 6.1.3. Использование модулей (359). 6.1.4. Общие объекты и их использование] (362). 6.1.5. Схемы трансляции и за- грузки (365). 6.2. Загрузчики и редакторы связей............................. 367 6.3. Трансляторы............................................... 373 6.4. Отладчики программ........................................ 378 6.4.1. Отладка программы (378). 6.4.2. Реализация отладчика. Прокрутка (385). 6.5. Редакторы текстов программ................................ 393 6.5.1. Назначение редактора текстов (393). 6.5.2. Пример работы с редактором текстов (397). 6.6. Схема функционирования системы программирования.......... 399 Глава 7. Язык программирования фортран............................ 402 7.1. Общая характеристика фортран-программы . ♦................ 403 7.2. Основные понятия.......................................... 406 7.2.1. Алфавит (406). 7.2.2. Типы данных (407). 7.2.3. Констан- ты (407). 7.2.4. Переменные (409). 7.2.5. Выражения (409). 7.3. Операторы................................................. 410 7.3.1. Операторы присваивания (410). 7.3.2. Операторы управле- ния (411). 7.4. Объявления................................................ 417 7.4.1. Объявления спецификаций (418). 7.4.2. Объявление началь- ных данных (422). 7.5. Процедуры и модули........................................ 423 7.5.1. Встроенные функции (423). 7.5.2. Внутренние функции (424). 7.5.3. Внешние функции (424). 7.5.4. Внешние подпрограм- мы (426). 7.5.5. Модуль-блок данных (427). . , 7.6. Ввод/вывод .... .......................................... 428 7.6.1. Оператор форматного вывода (429). 7.6.2. Объявление фор- мата (430). 7.6.3. Взаимодействие форматного управления со спис- ком вывода (435). 7.7. Структура программы и модулей............................ 437 7.8. Пример программирования на фортране....................... 438 Глава 8. Автокоды . . ............................................ 447 8.1. Общие сведения............................................ 447 8.2. Основные понятия.......................................... 449 8.2.1. Алфавит (449). 8.2.2. Идентификаторы (450). 8.2.3. Формат предложений (450), 8,2,4, Имена (452), 8,2.5, Счетчик размеще-
6 ОГЛАВЛЕНИЕ * ния (454). 8.2.6. Самоопределенные величины (455). 8.2.7. Адрес- ные выражения (456). 8.3. Задание команд............................................ 457 8.4z Задание констант.......................................... 460 8.5. Команды транслятору....................................... 464 8.5.1. Команды организационного характера (465). 8.5.2. Резер- вирование памяти (465). 8.5.3. Объявление эквивалентности (466). 8.5.4. Управление размещением литералов (467). 8.5.5. Дублиро- вание команд (468). 8.5.6. Управление счетчиком размещения (469). 8.5.7. Определение межмодульных связей (471). 8.5.8. Управ- ление листингом (473). 8.6. Условное ассемблирование.................................. 474 8.7. Макросредства............:................................ 478 . 8.7.1. Макроопределения (479). 8.7.2. Макрокоманды (480). 8.7.3. Способы задания параметров (480). 8.7.4. Макроопределения и условное ассемблирование (482). 8.7.5. Локализация объектов макроопределений (485). 8.7.6. Макрооперации в макроопределе- ниях (48/). 8.7.7. Системные макроопределения (488). 8.7.8. Мак- . ро в языках высокого уровня (489). 8.8. Ассемблер............................................... 492 8.8.1. Первая фаза работы ассемблера (495). 8.8.2. Вторая фаза работы ассемблера (497). Глава 9. Мультипрограммный режим работы ЭВМ....................... 502 9.1. Причины возникновения..................................... 502 9.1.1. Быстродействие и производительность ЭВМ (502). 9.1.2. . Виды внешней памяти (503). 9.1.3. Внешняя память и языки прог- раммирования (508). 9.1.4. Эффективность использования обору- дования (510). 9.2. Совмещение работы устройств ЭВМ. Мультипрограммный режим 511 9.3. Требования к аппаратуре................................... 516 9.3.1. Прерывания (516). 9.3.2. Привилегированный режим (520). ’ 9.3.3. Защита памяти (521). 9.3.4. Перемещение (релокация) про- грамм (524). 9.3.5. Машинные часы (528). 9.4, Появление операционных систем............................. 528 Глава 10. Операционные системы.................................... 531 10.1. Работа с данными.......................................... 531 10.1.1. Виртуальная оперативная память (531). 10.1,2. Виртуаль- ные устройства ввода/вывода (532). 10.1.3. Виртуальная внеш- няя память (534). 10.1.4. Архивная служба (539). 10.2. Управление процессами..................................... 540 10.3. Управление взаимодействием задач.......................... 548 10.3.1. Множество задач (549). 10.3.2. Последовательное взаимо- действие (550). 10.3.3. Параллельное взаимодействие (564). 10.4. Обеспечение диалога с ЭВМ................................. 568 10.4. L Две формы общения человека с машиной (568). 10.4.2. Терминалы (571). 10.4.3. Простой пример диалога с ЭВМ (573). 10.4.4. Основные виды услуг при диалоговой форме общения (578). 10.4.5. Развернутый пример диалога с ЭВМ (583). 10.5. Организационные функции................................... 593 " 10.5.1. Режимы использования ЭВМ (593). 10.5.2. Административ- ные функции (598). 10.5.3. Обеспечёние Надежности функциони- рования вычислительной системы (598). Предметный указатель ........................................... 603
ПРЕДИСЛОВИЕ В основу настоящего пособия положен курс лекций, который его авторы читают на факультете Вычислительной математики и кибер- нетики МГУ им. М. В. Ломоносова на протяжёнии последних че- тырех лет. После того, как была утверждена новая программа курса «ЭВМ и программирование», рассчитанная на два учебных года и пре- дусматривающая совместное изучение вычислительных машин, программирования и математического обеспечения, стала особенно очевидной потребность в создании единого учебного пособия, со- держащего связное изложение всех этих вопросов с достаточно сов- ременной точки зрения. Такое пособие тем более нужно, что, ввиду большого значения, придаваемого нашим государством развитию и применению ЭВМ, за последнее время резко увеличилось количе- ство вузов, готовящих специалистов по вычислительной технике и прикладной математике во всех концах страны. Поставленная зада- ча обусловила некоторые характерные особенности в отборе и из- ложении материала. Прежде всего, в качестве первичного понятия выбрана не вы- числительная машина, а программирование как процесс разработки алгоритма. В соответствии с этим сначала излагаются абстрактные алгоритмические языки (машины Тьюринга и нормальные алгорит- мы), затем язык алгол и наконец языки вычислительных машин. При этом подчеркивается, что разработка программ на различных языках проводится в принципе одинаково, хотя она, безусловно, су- щественно зависит и от специфики языков. Основные приемы и методы разработки программ объясняются по ходу изучения * языка алгол. Затем алгол становится инстру- ментом для изложения остального материала: на нем записывается большинство алгоритмов (как вычислительных, так и невычисли- тельных), с его помощью описываются устройство и работа различ- ных типов вычислительных машин и выполнение различных ма- шинных операций. При этом знание алгола для изучения ЭВМ оказалось значительно полезнее, чем оказывалось знание ЭВМ для изучения алгола. В порядке иллюстрации различных методов раз- работки программ составляются алгол-программы, описывающие ра- боту универсальной машины Тьюринга и универсального нормаль- ного алгоритма. Конечно, использование алгола в качестве инстру-
8 ПРЕДИСЛОВИЕ ментального языка вызвало ряд вполне понятных трудностей. Од- нако для авторов решающим было то обстоятельство, что алгол — это широко распространенный язык, который хорошо знают все преподаватели прикладной математики. Примерно по таким же соображениям авторами был сделан толь- ко первый небольшой шаг в направлении структурного программи- рования. Главное внимание при изучении математического обеспечения уделено возможностям, которые оно предоставляет программисту. Некоторые сведения о структуре математического обеспечения и алгоритмы работы отдельных его компонентов приводятся только для лучшего понимания его функций или как иллюстрация к материалу предшествующей части курса. В основу описания системы программирования положено поня- тие модуля. Таким образом, система программирования рассматрива- ется как комплекс программ, под управлением которого ЭВМ ис- пользуется для изготовления, хранения и редактирования модулей, для их объединения в рабочие программы и для отладки. Для описа- ния операционной системы используется понятие виртуальной ма- шины, представляющей собой объединение физической вычислитель- ной машины и программ операционной системы. Возможности, пре- доставляемые программисту операционной системой, описываются через свойства такой виртуальной машины. Вполне понятно, что пособие по базовому курсу программиро- вания не могло быть привязано к какой-либо одной вычислитель- ной машине, системе программирования или операционной систе- ме — оно должно было отражать типичные или перспективные черты различных машин и систем. Авторы выражают глубокую благодарность декану факультета ВМиК МГУ академику А. Н. Тихонову, который был инициатором создания такого пособия и оказывал авторам поддержку на разных этапах, его разработки; коллективу преподавателей программиро- вания факультета ВМиК МГУ, чей творческий труд способствовал становлению и совершенствованию курса, положенного в основу этой книги; чл-корр. АН УССР профессору Е. Л. Ющенко, профессору А. Н. Костовскому, доценту А. И. Кардашу и доценту И. В. Людкевичу, которые ознакомились с рукописью и сделали ряд ценных замечаний. Главы 1—3 подготовлены Э. 3. Любимским и В. В. Мартынюком, главы 4—10 — Э. 3. Любимским и Н. П. Трифоновым. Э. 3. Любимский, В. В. Мартынюк, И. П. Трифонов
ВВЕДЕНИЕ Что такое программирование. Предмет нашего изучения — про- граммирование для электронных вычислительных машин, или ЭВМ, как их принято называть сокращенно. ЭВМ — это устройства для решения задач. Не обязательно задач по алгебре или геометрии, к которым мы привыкли в школе. И даже вообще не обязательно задач чисто математического характера. Это могут быть и шахмат- ные задачи, и задачи управления станками или ракетами, и задачи планирования производства или информационно-справочного об- служивания. Чтобы решить какую-либо задачу на некоторой ЭВМ, необходимо сначала придумать, как вообще можно решить эту задачу, т. е. придумать алгоритм ее решения. Затем следует представить этот алгоритм в таком виде, чтобы данная ЭВМ могла его выполнить. Для этого нужно, во-первых, разбить алгоритм на элементарные операции, которые умеет выполнять эта ЭВМ, и, во-вторых, записать каждую такую операцию на языке, понятном ЭВМ. Такая запись алгоритма на языке некоторой ЭВМ называется программой для этой ЭВМ. Описанный выше процесс разработки программы представляет собой процесс программирования, а чело- век, его выполняющий, называется программистом. Почему программирование — это научная дисциплина. Если бы процессы программирования решения разных задач на разных ЭВМ не имели между собой ничего общего, то программирование, как таковое, не было бы научной дисциплиной, и наш учебник можно было бы на этом закончить. Однако, дело обстоит совсем не так. Существуют общие методы, которые” позволяют, постепенно расчленяя задачи на подзадачи, сводить их решение, в конечном сче- те, к некоторым типовым фрагментам алгоритмов, подобно тому как, разбирая совершенно непохожие сложные механизмы, мы обнаруживаем, что они состоят из одинаковых деталей и узлов, только по-разному соединенных. Из этого, конечно, не следует, что процесс программирования не содержит в себе элементов твор- чества. Составление программы — это такой же творческий процесс, как и конструирование механизма с заданными свойствами из за- данного наббра деталей. Что же касается ЭВМ, то, при всем их разнообразии, с точки Зрения разработки алгоритмов решения задач все они очень похожи
10 ВВЕДЕНИЕ друг на друга. Каждая из них имеет процессор и память. Все про- цессоры выполняют примерно одинаковые наборы операций, а память любой ЭВМ состоит из перенумерованных ячеек, способных хранить числа или слова. Таким образом, тип выбранной ЭВМ в принципе сказывается только на последней стадии разработки про- граммы. Более того, те же самые методы программирования приме- няются и в том случае, когда предполагаемый исполнитель алгорит- ма вообще не есть ЭВМ и даже совсем на нее не похож. Мы убедимся в этом, когда рассмотрим два абстрактных исполнителя алгоритмов и напишем для них несколько программ. Общность методов разработки программ для самых разных ис- полнителей не случайна. Она вытекает из того фундаментального свойства, что все эти. исполнители практически сводимы друг к другу. Иными словами, если есть два исполнителя И1 и И2, то можно, например, составить для И1 алгоритм, который будет моде- лировать И2, т. е. выполнять на И1 программы, составленные для И2. И наоборот. Правда, при этом теоретически возникает одно ограничение. Если И1 — физический, реально существующий ис- полнитель (например, какая-нибудь ЭВМ), а И2 — абстрактный исполнитель, то можно представить себе программу для И2, при выполнении которой исполнителю И1 не хватит памяти для хране- ния промежуточных данных. Ведь абстрактный исполнитель мы мо- жем «снабдить» неограниченной памятью (как «снабжаем» прямую неограниченной длиной), а физический имеет столько ячеек, сколько их на самом деле изготовили на заводе. Однако, если какую-либо задачу можно решить при помощи исполнителя И1, то это можно сделать двумя способами: или разработать соответствующую про- грамму непосредственно для И1, или разработать программу для И2 и выполнить ее на И1 при помощи моделирующего алгоритма. А сле- довательно, методы, пригодные для решения задачи на И2, в прин- ципе пригодны и для ее решения на И1. Какое значение имеет исполнитель алгоритма. Предположим теперь, что И1 и И2 оба есть физические исполнители (например, разные ЭВМ). Если мы разрабатываем программу на языке испол- нителя И2, то какая нам разница, будет она выполняться непосред- ственно на И2 или на И1 под управлением моделирующего алгорит- ма? В принципе — никакой разницы нет. Чтобы это подчеркнуть, говорят, что И2 виртуально реализован на И1, или что на базе фи- вичёского исполнителя И1 реализован виртуальный исполнитель И2. ; Однако с практической точки зрения нам совсем не безразлично, где будет выполняться наша программа. Ведь физический исполни- тель И2 й виртуальный исполнитель И2 могут иметь совершенно разные эксплуатационные характеристики. Например, они могут иметь разные соотношения скоростей выполнения различных опе- раций. А поскольку мы всегда заинтересованы, чтобы задачи ре- шались быстрее, нужно постараться на каждом исполнителе пре-
ВВЕДЕНИЕ 11 имущественно использовать те операции, которые он выполняет с наибольшей скоростью. Таким образом, может оказаться, что для эффективного решения одной и той же задачи на физическом и на виртуальном исполнителях И2 потребуется составить разные про- граммы. И вообще, наше утверждение о существовании общих методов разработки программ вовсе не означает, что разработка программы должна проводиться независимо от ее будущего исполнителя. Мето- ды разработки программ — общие, а сами программы могут корен- ным образом отличаться друг от друга, даже если они предназначены для решения одной и той же задачи, но на разных исполнителях. И если мы хотим составлять эффективные программы, а исполнители существенно различаются, то и программы обязательно будут от- личаться друг от друга. Таким образом, нас должны интересовать не только общие ме- тоды разработки программ, но и свойства ЭВМ как исполнителей алгоритмов и то, как нужно учитывать эти свойства, чтобы сос- тавлять эффективные программы. О программном обеспечении. Одна из важнейших особенностей ЭВМ состоит в том, что они способны воспринимать и накапливать знания. В самом деле, написать для некоторой ЭВМ, например, программу решения системы линейных уравнений — это значит научить эту ЭВМ решать системы линейных уравнений. Теперь можно сколько угодно раз предлагать этой ЭВМ системы линейных уравнений, и она будет их решать. Истинное богатство современной ЭВМ — это не столько аппаратура, из которой она состоит, сколько совокупность заложенных в нее знаний, т. е. написанных для нее программ. Все программы можно, хотя и несколько условно, разделить на четыре группы. К первой группе относятся программы для решения отдельных, самостоятельных задач. Эти программы выполняются независимо друг от друга и представляют собой набор разрозненных, не свя- занных между собой знаний. Из этого, конечно, не следует, что отдельные конкретные задачи, которые может решать ЭВМ при по- мощи таких программ, не могут быть весьма сложными или,очень нужными задачами. Ко второй группе относятся системы программ для решения классов задач из различных специализированных областей науки, техники, промышленности и т. д. Часто такие системы называют пакетами прикладных программ. Пакеты представляют собой уже систематизированные знания в узких предметных областях. Про4 граммы, входящие в пакет, выполняются не отдельно друг от друга, а совместно, в различных комбинациях, в зависимости от конкрет- ной решаемой задачи. Разрабатывать программы, составляющие па- кет, т. е. передавать ЭВМ систематизированные знания, труднее,
12 ВВЕДЕНИЕ чем разрабатывать отдельные, независимые программы. Но и поль- зы от таких программ гораздо больше — число задач, которые можно решить при помощи пакета, как правило, намного превосхо- дит число программ, входящих в этот пакет. Третья группа — это система программ, предназначенная для автоматизации самого процесса разработки программ. Эта система представляет собой квалификацию ЭВМ в области программирова- ния и называется системой программирования. По существу, она тоже есть пакет, предметная область которого — разработка про- грамм. Все программы первой и второй групп разрабатываются с помощью программ третьей группы, и в этом смысле система про- граммирования является системой общего пользования. Употреб- ляя промышленную терминологию, можно сказать, что назначение системы программирования состоит в производстве средств произ- водства. Современная ЭВМ — это комплекс, состоящий из большого числа различных взаимодействующих устройств, и управление им представляет собой весьма сложную задачу. Решением этой задачи занимается специальная система программ, называемая операцион- ной системой. Программы, входящие в эту систему, и образуют чет- вертую группу программ. Помимо управления работой ЭВМ, one- ' рационная система выполняет и ряд других функций. Из них наи- более для нас интересная — это пополнение набора операций, которые умеет выполнять ЭВМ, новыми операциями, весьма удобны- ми для программистов, но слишком сложными для непосредственной реализации на ЭВМ. Таким образом, операционная система вместе с ЭВМ образуют новую, виртуальную машину, существенно более удобную для программирования. Все программы первой, второй и третьей групп пишутся уже для этой виртуальной машины (оче- видно, что программы самой операционной системы должны писать- ся непосредственно для ЭВМ). Программы первых двух групп образуют специализированное, программное обеспечение ЭВМ, а последних двух — ее стандарт- ное программное обеспечение. Поскольку первые ЭВМ применялись Почти исключительно для математических расчетов, за программ- ным обеспечением закрепилось также название математическое обеспечение. Для того чтобы грамотно использовать современную ЭВМ, про- граммист должен хорошо знать структуру ее стандартного програм- много обеспечения и возможности, которые оно предоставляет для разработки программ.
Глава 1 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ 1.1. Интуитивное понятие алгоритма и необходимость его уточнения Работа вычислительной машины состоит в выполнении алгорит- ма, поэтому общие возможности применения вычислительных ма- шин определяются тем, что можно и что нельзя представить в виде алгоритма. Понятие алгоритма, являющееся одним из основных понятий математики, возникло задолго до появления вычислительных машин. На протяжении многих веков люди пользовались интуитивным поня- тием алгоритма, которое можно выразить так: Алгоритм — это строгая и четкая конечная система правил, которая определяет последовательность действий над некоторыми объектами и после конечного числа шагов приводит к достижению поставленной цели. В частности, система правил является алгоритмом, если ее можно вручить в качестве инструкции разным людям, не знакомым с сутью дела, и они, следуя этой системе правил, будут действовать одина- ково. Например, можно предложить такой простейший алгоритм подсчета людей в зрительном зале: пройди по всем рядам и для каж^ дого присутствующего человека прибавляй единицу к общему счет- чику, в котором сначала был нуль. Объекты этого алгоритма — люди в зале и числа. Над людьми выполняется действие «найти следующего», а над числами — дей- ствие «прибавить единицу». Древнегреческий математик Евклид предложил алгоритм вычис- ления общего наибольшего делителя двух натуральных чисел А и В. Суть этого алгоритма в том, чтобы вычитать из большего числа меньшее, занося результат на место большего, и действовать так до тех пор, пока числа не станут равны между собой. Эти равные числа и будут общим наибольшим делителем исходных двух чисел. В алгоритме Евклида используется тот факт, что общий наибольший делитель чисел А и В является также общим наибольшим делителем их разности и любого из чисел А, В. Поэтому можно в паре чисел. (А, В) заменить большее число на эту разность, а затем искать об- щий наибольший делитель для новой пары, в которой одно число уменьшилось. Мы описали идею алгоритма Евклида, но этому описанию не- хватает четкости, и поэтому его нужно уточнить. Настоящий алго- ритм Евклида выглядит так:
14 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I Г. Рассмотреть А как первое число и В как второе число. Перей- ти к п. 2°. 2°. Сравнить первое и второе числа. Если они равны, то перейти к п. 5°. Если нет, то перейти к п. 3°. 3°. Если первое число меньше второго, то переставить их. Пе- рейти к п. 4°. 4°. Вычесть из первого числа второе и рассмотреть полученную разность как новое первое число. Перейти к п. 2°. 5°. Рассмотреть первое число как результат. СТОП. _ Этот набор правил является алгоритмом, потому что, следуя ему, любой человек, умеющий вычитать, может получить общий наи- больший делитель для любой пары чисел. Математики долго пользовались такими описаниями алгоритмов и довольствовались интуитивным понятием алгоритма. В этих рам- ках формулировались и успешно применялись на практике различ- ные вычислительные алгоритмы. Например, были получены алго- ритмы поиска корней квадратных и кубических уравнений. Посте- пенно математики увлекались все более трудными задачами. Напри- мер, стали искать алгоритм вычисления корней алгебраических уравнений любых степеней. Г. Лейбниц в семнадцатом веке даже пытался найти общий алго- ритм решения любых математических задач. Уже в нашем веке этот грандиозный замысел приобрел более конкретную форму: найти ал- горитм проверки правильности любой теоремы при любой системе аксиом, т. е. алгоритм, который отвечал бы на вопрос, верна ли тео- рема, и в случае положительного ответа давал бы вывод ее доказа- тельства. Построит^.такие алгоритмы не удавалось, и постепенно возникло мнение, что это вообще невозможно, т. е. что рассматри- ваемые задачи алгоритмически неразрешимы. Но нельзя было дока- зывать невозможность алгоритмического решения задач, пока не было строго определено понятие алгоритма. Поэтому возникла на- сущная проблема: построить формальное определение алгоритма, аналогичное известному интуитивному понятию. Одна из причин расплывчатости интуитивного понятия алгорит- ма состоит в том, что объектом алгоритма может оказаться все, что угодно. Поэтому естественно было начать с формализации понятия объекта. В вычислительных алгоритмах объектами работы являются чис- ла. В алгоритме шахматной игры объекты — это фигуры и позиции, и нужно выбрать очередной ход. При алгоритмизации производст- венных процессов объектами служат показания приборов и управ- ляющие клавиши, и нужно найти такое нажатие клавишей, при ко- тором процесс пошел бы лучшим образом. Это только несколько при- меров разнообразия алгоритмов. Однако во всех случаях можно счи- тать, что алгоритм имеет дело не с объектами реального мира, а а изображениями этих объектов. •
t.l] ИНТУИТИВНОЕ ПОНЯТИЕ АЛГОРИТМА 13 Например, когда алгоритм сложения работает над числовыми объектами 26 и 57, он вырабатывает числовой результат 83. Но мы можем считать, что объектом алгоритма является изображение, со- стоящее из пяти знаков: 26+57 а результат — это изображение, состоящее из двух знаков: 83 При этом мы исходим из того, что имеется набор из одиннадцати различных знаков: {О, 1, 2, 3, 4, 5, 6, 7, 8, 9, +} Знаки мы называем буквами, а их набор — алфавитом. В общем случае буквами могут служить любые знаки. Требуется только, что- бы они были различимы между собой и чтобы в алфавите не было бесконечного разнообразия различных букв. Итак, буквы — это любые знаки; алфавит — это конечная совокупность различимых букв. Любая конечная последовательность букв из некоторого алфа- вита называется словом в этом алфавите. Например, можно сказать, что алгоритм сложения перерабатывает слово, которое состоит из изображений слагаемых, разделенных знаком +, в слово, изобра- жающее сумму. Заметим, что важен порядок букв в слове, но не имеет значения порядок букв в алфавите. Алфавит {А, Б}ничем не отличается от алфавита {Б, А}, но слова А Б и БА разные. Алфавит русского языка удовлетворяет нашему определению алфавита, а русские слова — нашему определению слов. Однако, любые бессмысленныё комбинации русских букв также являются словами в этом алфавите. Если к русскому алфавиту добавить знак пробела, а также знаки препинания (кроме точки, вопросительного и восклицательного знака), то любые предложения русского языка становятся .словами в этом алфавите. А если добавить и эти знаки препинания и цифры, то любая книга станет словом в нашем алфавите (при условии, что в ней не встречаются иностранные буквы и другие специальные зна- ки). Ведь каждая книга — это последовательность букв, цифр и знаков препинания. Количество букв в слове называется длиной слова. Иногда нужно рассматривать так называемое пустое слово, в котором нет букв; мы будем обозначать его символом А. Итак, объекты реального мира можно изображать словами в раз- личных алфавитах. Эго позволяет считать, что объектами работы алгоритмов могут быть только слова. Получается такое уточненное (но еще не окончательное) определение: Алгоритм есть четкая конечная система правил для преобразова- ния слов из некоторого алфавита в слова из этого же алфавита,-
16 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I Слово, к которому применяется алгоритм, называется входным, а слово, вырабатываемое в результате применения алгоритма, назы- вается еыходяели. Совсем не обязательно, чтобы удавалось применить алгоритм ко всем словам из алфавита. Некоторые слова нельзя использовать в качестве входных слов алгоритма, и мы говорим, что к этим сло- вам алгоритм не применим. Совокупность слов, к которым алгоритм применим, называется областью применимости этого алгоритма. Мы еще вернемся к понятию области применимости; теперь для нас важно убедиться в том, что мы ничего не потеряли, сузив поня- тие алгоритма и перейдя от любых объектов к словам. К сожале- нию, нельзя доказать, что все возможные объекты удается описать словами, так как само понятие объект^ не было формально (т. е. строго) определено. Но это можно проверить, если брать наугад различные алгоритмы не над словами и каждый раз выражать их так, чтобы объектами становились слова, а суть дела при этом не менялась. Для алгоритма сложения мы уже продемонстрировали, что мож- но работать со словами. Точно так же, можно считать, что алгоритм шахматной игры имеет дело не с реальным расположением фигур на доске, а с записью этого расположения в привычных шахматистам обозначениях, например: Белые: Кре5, Фс12, Ла7, КП. Черные: КрЬЗ, Лс4, КЬ2, КЬ4, пс2. При этом результатом применения шахматного алгоритма будет не физическое перемещение фигуры на доске, а слово, представляющее собой запись выбранного хода: Ф(12—сЗ+. Известно, что можно в таких обозначениях описывать любые шахматные ситуации, а потом по имеющимся записям безошибочно воспроизводить эти ситуации на шахматной доске. Эти й другие многочисленные примеры позволяют считать, что мы ничего не потеряем, выбирая для каждой задачи подходящий алфавит и ограничиваясь рассмотрением алгоритмов, действующих со словами из выбираемых алфавитов. (Если алгоритм работает со словами в русском алфавите, то может оказаться, что входным словом служит исходная постановка задачи на русском языке, а выходное слово представляет собой описание того, как решается эта задача.) Любой алфавит можно заменить другим. Такая замена называется кодировкой. Каждой букве из первого алфавита ставится в соответ- ствие ее код, представляющий собой слово во втором алфавите. На самом деле, всегда достаточно иметь дело с алфавитом из двух букв, потому что любое слово из любого алфавита можно закодировать в алфавите из двух букв. Например, телеграммы на русском языке
1.21 МАШИНЫ ТЬЮРИНГА 17 передаютс'я азбукой Морзе, где алфавит состоит только из знаков «точка» и «тире». Проще всего перейти от произвольного алфавита к алфавиту ив двух букв, закодировав все буквы исходного алфавита различными словами в двухбуквенном алфавите. Вот один из самых простых способов такой кодировки в алфавите {0, 1}: нумеруем все буквы, исходного алфавита и кодируем первую букву словом 101, вторую — словом 1001, третью — словом 10001 и т. д. При этом номер каждой буквы равен числу нулей между единицами в ее коде. Если так закодировать русский алфавит, то слово А Б будет кодироваться словом 1011001; слово ГА будет кодироваться как 100001101, а слово БАВ как 100110110001. Конечно, для кодировки последних букв алфавита потребуется много нулей. Но в принципе: можно закодировать таким образом любое русское слово, а когда понадобится, то восстановить исходное слово, отделяя в имеющейся комбинации нулей и единиц одну за другой группы нулей, заклю- ченные между единицами, и заменяя их на соответствующие буквы русского языка. Поскольку от любого алфавита можно перейти к алфавиту из двух букв с гарантией однозначного обратного восстановления за- кодированных слов, то без потери общности можно-свести любой алгоритм к алгоритму над словами в алфавите из знаков 0 и 1. Для этого достаточно в правилах алгоритма заменить обрабатываемые слова в исходном алфавите на их коды. Перед применением алго- ритма потребуется закодировать входное слово, а после выполнения алгоритма нужно будет раскодировать выходное слово. 1.2. Машины Тьюринга 1.2.1. Определение машины Тьюринга. Теперь, когда мы формально описали объекты применения алго- ритмов как слова в некотором алфавите, осталось формализовать действия над объектами-словами и порядок этих действий. Формальные определения алгоритма появились в тридцатых — сороковых годах нашего века. Одним из первых было определение, английского математика А. Тьюринга, который в 1936 году описал схему некоторой гипотетической (абстрактной) машины и предложил' называть алгоритмами то, что умеет делать такая машина. При этом' определении, если что-то не может быть сделано машиной Тьюрин-. га, это уже не алгоритм. Иначе говоря, Тьюринг формализовал пра- вила выполнения действий при помощи описания работы некоторой конструкции.
ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I Вычислительные машины — это тоже конструкции для выпол- нения алгоритмов, но это реальные устройства, тогда как машина Тьюринга является абстракцией, которая никогда не была реали- зована. (Как мы увидим дальше, ее и нельзя реализовать.) Поэтому алгоритмы Для машины Тьюринга должны выполняться другими средствами. Например, за нее должен работать человек, действуя так как действовала бы машина Тьюринга, если бы она существо- вала. Польза от машины Тьюринга в том, что, говоря об этой во- ображаемой конструкции, мы можем доказывать существование или несуществование алгоритмов решения различных задач. Исходя из этого, Тьюринг искал как можно более простую, «бедную» алгоритмическую схему, лишь бы она была универсальной. Важно чтобы было удобно доказывать, что она не может решить задачу, которая, как нам кажется, вообще неразрешима. Когда же Речь идет о вычислительной машине, то, наоборот, мы больше всего ценим ее удобство, богатство ее возможностей; требуем, чтобы человеку было легко с ней работать. В этом нас сдерживает только цена желаемых удобств и трудность разработки обору- дования. Принципиальное отличие машины Тьюринга от вычислительных машин состоит в том, что ее запоминающее устройство представля- ет собой бесконечную ленту, тогда как у вычислительной машины может быть очень большое, но уж во всяком случае не бесконечное запоминающее устройство. Машину Тьюринга нельзя физически реализовать именно из-за ее бесконечной ленты. В этом смысле она мощнее любой вычислительной машины. Лента машины Тьюринга разделена на ячейки. В каждой ячей- ке может находиться одна из букв какого-нибудь алфавита. Если ячейка пустая, то мы говорим, что в ней находится специальная буква А. Алфавиты могут быть разные, но для каждой конкретной машины Тьюринга выбирается какой-либо один алфавит. Кроме лен- ты, у машины Тьюринга есть автомат, который может двигаться вдоль ленты и по очереди «обозревать» содержимое ячеек. Входное слово размещается на ленте по одной букве в расположенных подряд ячейках и занимает конечное число ячеек. Слева и справа от вход- ного слова на ленте находятся только пустые ячейки. Ниже нари- сована машина Тьюринга, у которой на ленте занято буквами пять ячеек, прячем автомат находится против ячейки, содержащей бук- ву С.’ лента... А А С Л О в 0 л л •.. автомат
1.2] МАШИНЫ ТЬЮРИНГА ф Итак, автомат каждый раз «видит» одну ячейку. Кроме того, он может находиться в одном из нескольких состояний qu q2, . . . .. ,, qk. В зависимости от того, какую букву s( автомат «видит» в- очередной ячейке, а также в зависимости от своего состояния qj, т. е. в зависимости от пары ($г, qj), автомат может выполйять раз* личные действия: запись новой буквы в обозреваемую ячейку, сдвиг по ленте на одну ячейку влево или вправо; переход в новое состояние. Это три вида операций машины Тьюринга, и каждый раз для очередной пары («г, qj) машина Тьюринга может выполнить по од- ной операции каждого вида. Все задание для ее работы можно изо- бразить программой, приведенной в табл. 1.1. В каждой клетке программы нужно указать, какие операции должен выполнить автомат, если, находясь в данном состоянии. Таблица 1.1 Что «видит» автомат Л «1 • • ♦ si ... Состояние автомата <71 Яг 4J (Л) Н (Ят 1/7 J * Як [Л] Запись < Н > означает, что вместо нее может 1/7 J стоять одна из букв Л, Н, П.
20 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. 1 он «видит» данную букву. В общем случае, находясь в состоянии и видя букву sh он записывает в ту же ячейку заданную букву (которая может в частном случае совпасть с буквой sf); затем он сдвигается по ленте на шаг влево, или остается неподвижным, или сдвигается на шаг вправо; чтобы различать эти три возможности, мы записываем в клетку одну из трех букв: Л (означает шаг влево) Н (означает неподвижность) П (означает шаг вправо). После этого автомат переходит в состояние qm (которое может в частном случае совпасть с прежним состоянием </,). Теперь следую- щее действие автомата нужно искать в /n-й строке программы (эта строка соответствует состоянию на пересечении со столбцом, соответствующим той букве, которую автомат «увидит» после сдвига. (Если автомат остается неподвижным, он «увидит» ту самую букву которую только что записал, и в этом случае следующее действие нужно искать на пересечении со столбцом, соответствующим букве $Р) Итак, автомат движется по ленте то вправо, то влево, иногда ос- тается неподвижным и каждый раз принимает три решения: что писать, куда двигаться и в какое состояние переходить. Если сле- дить только за лентой, не обращая внимания на автомат, то мы уви- дим, что каждый раз одна буква заменяется на другую. В частности, какие-то буквы стираются и вместо них остаются пустые ячейки, а в каких-то пустых ячейках появляются новые буквы. Иначе говоря, укорачиваются или удлиняются слова, записанные на ленте. Несмотря на свое простое устройство, машина Тьюринга может выполнять самые сложные преобразования слов. Сначала, когда лента содержит входное слово, автомат находится против какой-то ячейки и в каком-то состоянии. Необходимо договориться, какая это будет ячейка: например, самая левая непустая, третья от конца слова или какая-нибудь еще. В зависимости от выбора начальной ячейки, получатся разные результаты работы машины Тьюринга. Что касается начального состояния, то всегда для удобства можно считать, что это состояние № 1, т. е. qr. Ведь строку программы, соответствующую начальному состоянию, мы всегда можем написать первой. В процессе своей работы машина Тьюринга будет как бы перескакивать из одной клетки программы в другую в соответствии с информацией на ленте и указаниями в клетках программы, пока не дойдет до клетки, в которой будет написано, что автомат должен не менять в очередной ячейке находящуюся там букву, остаться не- подвижным и сохранить свое прежнее состояние. Если, например, это клетка на пересечении строки для q* и столбца для буквы s7, то это значит, что в ней написано s7, Н, q*. Дойдя до такой клетки, машина Тьюринга уже никогда никуда из нее не уйдет и ничего но- вого на ленту не запишет. Считается, что на этом она завершит свою работу, т. е. остановится.
1.21 МАШИНЫ ТЬЮРИНГА 21 Входным является то слово, которое имелось первоначально на ленте (от самой левой непустой ячейки до самой правой). То, что получилось на ленте к моменту останова,— выходное слово. Таких клеток останова в программе может не быть; тогда машина Тьюринга никогда не остановится. Даже, если такие клетки есть, машина может никогда до них не дойти (ведь ее переходы зависят от программы и от входного слова). Если машина Тьюринга никогда не остановится, то считается, что она неприменима к данному входному слову. Она применима к слову только в том случае, если, начав работу над этим входным словом, рано или поздно дойдет до клетки останова. Прослеживая работу машины Тьюринга, мы узнаем, что она применима к данному слову, но если она неприменима, то такое прослеживание нам ничего не докажет, так как в любой: момент мож- но надеяться на то, что она скоро остановится. Итак, неприменимость не может быть выяснена прямым способом; в ней можно убедиться только косвенными рассуждениями. Например, если в имеющейся программе нет клетки останова, то данная машина Тьюринга не применима ни к одному слову. Даже при наличии клеток остано- ва в нижних строках программы, машина не применима ни к одному слову, если первая строка имеет, например, такой вид Л 0 1 <71 Л, П, <7, о, п, 1, п, В этом случае, если автомат в состоянии qx видит пустую ячейку, он оставляет ее пустой и сдвигается вправо, сохраняя состояние qt. Аналогично, видя в обозреваемой ячейке знак 0 или 1, он оставляет в ячейке тот же знак и тоже сдвигается вправо, сохраняя состояние 4i. Итак, что бы автомат ни «увидел» на ленте, он ничего не меняет и сдвигается по ленте вправо, оставаясь всегда в состоянии qu а поскольку лента бесконечна, он никогда не остановится. Может оказаться, что машина Тьюринга применима ко всем словам из своего алфавита. Например, если программа имеет вид Л 0 1 Qi Л, Н, <7, Л, П, Л, П, 9i то автомат, увидев пустую ячейку, оставляет ее пустей и не сдвига- ется, а, увидев знак 0 или 1, стирает этот знак и сдвигается вправо. Таким образом, получается, что автомат будет шаг за шагом стия
22 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. t рать содержимое непустых ячеек и двигаться вправо, пока не дой- дет до пустой ячейки, после чего машина остановится. Эта машина Тьюринга применима ко всем словам, потому что каждое слово име- ет конечную длину, а после него следуют пустые ячейки. Она будет стирать это слово, если условиться, что когда машина начинает ра- ботать, автомат находится против самой левой непустой ячейки. 1.2.2. Примеры машин Тьюринга. Начнем с рассмотрения машины Тьюринга, которая прибавляет единицу к числу на ленте. Входное слово состоит из цифр этого числа, записанных в после- довательные ячейки ленты. В начальный момент автомат находится против самой правой цифры числа. Машина должна прибавить еди- ницу к последней цифре, а если это была цифра 9, то заменить ее на О и аналогично поступить с предыдущей цифрой. Предлагается про- грамма, представленная в табл. 1.2. Таблица 1.2 Состо- яние л 0 1 8 9 Qi 1, Н, qt 1, н, qt 2, Н, q2 9, Н, q2 0, Л, <?! Qi Л, Н, q2 0, Н, q2 1. Н, q2 •. • 8, Н, q2 9, Н, q2 Здесь qi— состояние изменения цифр, a q2— состояние оста- нова. Вся вторая строка схемы заполнена клетками останова. Если в состоянии qt автомат видит цифру 0, или 1.или 8, он заменяет ее на 1 или 2, . . . или 9 соответственно и переходит в состояние q2, т. е. машина останавливается. Если же он видит цифру 9, то заме- няет ее на 0 и сдвигается к предыдущей цифре числа, оставаясь в том же состоянии qlt т. е. продолжая руководствоваться первой строкой программы. Так продолжается до тех пор, пока не встре- тится цифра, которая меньше, чем 9. Если все цифры были девят- ками, то автомат последовательно заменит их нулями; записав же О на место самой старшей цифры, он сдвинется влево, увидит пустую ячейку, запишет туда 1 (как это указано в клетке на пересечении строки (/1 и столбца Л) и перейдет в состояние qa, т. е. остановится. В результате, например, число 999 будет заменено на число 1000. Чтобы короче и нагляднее записывать программы машин Тьюрин- га, договоримся о следующем: 1) Вместо указания перехода в состояние останова, пишем в клетках программы знак !. 2) Опускаем в программах букву Н, т. е. пишем Л или П, или же ничего не пишем.
1.2] МАШИНЫ ТЬЮРИНГА 25' 3) Если согласно данной клетке нужно сохранять букву в ячейке, то не пишем в клетку эту букву. С учетом этих поправок та же программа будет записана так: А 0 1 •. • 8 9 Qi 1, I 1, I 2, I 9, ! 0» Л, Теперь рассмотрим несколько более сложную машину Тьюринга для подсчета на ленте штрихов, которые располагаются подряд и образуют входное слово. Нужно стереть все штрихи и написать на ленту их число, представленное в десятичной системе. Будем формировать это число на ленте слева от штрихов. В на* чальный момент машина Тьюринга обозревает любой из штрихов и находится в состоянии q^ В программировании принято прежде чем приступать к состав- лению алгоритма, фиксировать его план в виде схемы, состоящей из пунктов, или в виде графической блок-схемы. (Подробно об этом говорится в главе 2.) Для рассматриваемой задачи схема программы может выглядеть так: Г. Найти правый конец слова на ленте. 2° . Если слово оканчивается штрихом, то стереть этот штрих, иначе остановить машину. 3° . Прибавить к числу единицу и перейти к п. 1°. Каждый раз стирается самый правый штрих и к числу прибавля- ется единица. Выполнение этих трех пунктов повторяется до тех пор, пока не будет стерт последний штрих, после чего, согласно условию из п. 2°, машина Тьюринга остановится. Каждый из этих пунктов может быть реализован одним состоя- нием машины Тьюринга. Итак, нам понадобятся три состояния ма- шины Тьюринга. В состоянии <71 автомат будет искать правый ко- нец слова; qa будет состоянием стирания штрихов; q, будет состоя- нием прибавления к числу единицы. В табл. 1.3 приводится программа предлагаемой машины Тью- ринга. Машина «видит» на ленте цифры, которые она писала сама, и штрихи, находящиеся там с самого начала. В состоянии признаком Достижения правого конца слова служит пустая обозреваемая ячей- ка; при этом автомат сдвигается по ленте на шаг влево (т. е.начинает обозревать самый правый непустой символ) и переходит в состоя- ние qa. Находясь в состоянии qa и увидев штрих, автомат стирает «го, сдвигается натпаг влево и переходит в состояние qa прибавления единицы. Если же автомат в состоянии qa видит цифру, то машина
24 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I Таблица 1.Э Л 0 1 2 ... 8 9 / <71 Л, q3 п, qi п, qi п, <71 п, qi п, п, <71 <72 ! ! ! ! 1 ! Л, л, q3 <7з 1.П, <z, 1. П, qt 2, П, 3, П, <7! 9, П, <7, 0, Л, q3 /> Л, q3 останавливается, так как это означает, что все штрихи уже стерты. В состоянии q3 автомат двигается по ленте влево, минуя оставшиеся штрихи, пока не дойдет до числа, и прибавляет к числу единицу аналогично тому, как это делалось в предыдущем примере. В начальный момент автомат в состоянии может находиться против любого штриха из входного слова. Например, пусть входное слово состоит из трех штрихов и автомат первоначально находится против среднего штриха: Л / / / Л t <7i Начав работать, автомат сдвинется два раза вправо в состоянии q19 после чего возникнет ситуация: Л / / / Л t <71 В этот момент автомат сдвигается влево и переходит в состояние Л / / / Л t <h Затем обозреваемый штрих стирается, автомат сдвигается влево и переходит в состояние q9: Л / / Л Л t <7з Затем он движется влево, оставаясь в состоянии q39 пока не уви- дит пустую ячейку, после чего записывает туда цифру 1, сдвигается вправо и переходит в состояние ft:
j' 2] МАШИНЫ ТЬЮРИНГА 25 1 / / Л Л t <?1 Далее в состоянии ft автомат движется вправо до первой пустой ячейки, увидев которую, сдвигается влево и переходит в состояние ft: 1 / / Л Л t Очередной штрих стирается, автомат сдвигается влево и пере- ходит в состояние q3. 1 / Л Л Л t Яз Еще один сдвиг влево в состоянии qs, и автомат заменяет цифру 1 на 2, сдвигается вправо и переходит в состояние qi. 2 / Л Л Л t Я1 Снова сдвиг вправо, и следующая смена состояния со сдвигом влево: 2 / Л Л Л t Й Стирание штриха (последнего), сдвиг влево и переход в состоя- ние q3: 2 АЛЛА t Яз После этого цифра 2 заменяется на 3, и автомат сдвигается впра- во, переходя в состояние qi'. 3 Л Л Л Л t Я1 Далее следует сдвиг влево с переходом в состояние q2, и машина останавливается, оставив на ленте выходное слово. 1.2.3. Возможности машин Тьюринга. Основная гипотеза тео- рии алгоритмов. Богатство возможностей конструкции Тьюринга проявляется в том, что если какие-то алгоритмы А и В реализуются машинами Тьюринга, то можно строить программы машин Тьюринга, реализу- ющие различные композиции алгоритмов А и В, например, «выпол-
26 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. t нить А, затем выполнить В» или «Выполнить А. Если в результате получилось слово да, то выполнить В. В противном случае не выполнять В» или «Выполнять поочередно А, В, пока В не даст ответ О». В интуитивном смысле такие композиции являются.алгоритмами. Поэтому их реализация посредством машины Тьюринга служит од- ним из способов обоснования универсальности конструкции Тью- ринга. Реализуемость таких композиций доказывается в общем виде, независимо от особенностей конкретных алгоритмов А и В. Дока- зательство состоит в том, что указывается способ построения из программ А и В программы нужной композиции. Пусть, например, нужно построить машину А-В, эквивалентную последовательному выполнёнию алгоритмовЛ и В. Машина А имеет т состояний ..., qm\ машина В имеет k состояний ft....qk. Переименовываем сос- тояния машины В, заменяя qx на qm+i, Яг на qm+2, . . ., qk на qm+k. Соответственно заменяем и все ссылки на состояния в клетках про- граммы В. В программе А всюду знак 1 заменяем на указание состоя- ния qm+t. Записываем полученную программу А, а под ней програм- му В с переименованными состояниями. Вместе они образуют ис- комую программу А’В. Пока выполняется алгоритм А, в программе А-В работает часть А без учета части В. Когда алгоритм А дойдет до конца, то вместо останова произойдет переход в первое состояние части В, и затем часть В будет работать обычным образом, как будто части Л и не было. Например, если Л — алгоритм подсчета штрихов на ленте, а В — алгоритм прибавления единицы к числу на ленте, то мы можем воспользоваться уже рассмотренными программами машин Тью- Еинга, реализующими эти алгоритмы. В данном случае т=3 и k=\. [3 программы Л получаем первые три строки программы Л-В (табл. 1.4). Таблица 1.4 Л 0 1 2 ... 8 9 / Я1 Л, q2 п, 91 п, 9г п, 91 п, 91 П.91 п, 91 Яг 1 4 Я* я^ <74 <?4 <74 А, Л, q3 Яг 1.П.С 1. п, 91 2, П, 9, з, п, 91 9, П, 9, 0, Л, q2 /» Л, <73 Я* 1» 1 1, 1 2, 1 3, I 9, 1 0, Л, q^
12] МАШИНЫ ТЫрРИНГА 27 Последняя, четвертая строка программы Л-В получается из программы В. Полученная программа А-В описывает машину Тьюринга, ко- торая сначала подсчитывает число штрихов на ленте, стирая их, а затем прибавляет к этому числу единицу. Заметим, что в програм- ме Л-В осталась незаполненной клетка на пересечении строки и столбца /, но эта клетка никогда не будет использоваться, так как алгоритм В не имеет дела со знаком /. Аналогично конструируются и другие композиции машин Тью- ринга; каждый раз строятся общие правила: что на что менять в исходных программах. Описывая различные алгоритмы для машин Тьюринга и дока- зывая реализуемость всевозможных композиций алгоритмов, Тью- ринг убедительно показал разнообразие возможностей предложен- ной им конструкции, что позволило ему выступить со следующим тезисом: Всякий алгоритм может быть реализован соответствующей ма- шиной Т ьюринга. Это основная гипотеза теории алгоритмов в форме Тьюринга (здесь не оговаривается «всякий алгоритм преобразования слов», . так как мы уже ранее договорились, что действие любого алгорит- ма сводится к преобразованию слов). Одновременно этот тезис является формальным определением алгоритма. Теперь можно дока- зывать существование или несуществование алгоритмов, описывая соответствующие машины Тьюринга или доказывая невозможность их построения. Этим для нас открывается общий подход к поиску алгоритмических решений. Если поиск решения наталкивается на препятствие, то мы пытаемся использовать это препятствие для до- казательства невозможности решения, опираясь на основную ги- потезу теории алгоритмов. Если же при доказательстве невозмож- ности возникает свое препятствие, то оно может помочь нам продви- нуться в поиске решения, хотя бы частично устранив прежнее пре- пятствие. Так, поочередно пытаясь доказать то существование, то несуществование решения, мы можем постепенно приблизиться к пониманию существа стоящей перед нами задачи. Доказать тезис Тьюринга нельзя, так как в его формулировке не определено понятие «всякий алгоритм», т. е. левая часть тождества. Его можно только обосновать, представляя различные известные ал- горитмы в виде машин Тьюринга. Дополнительное обоснование этого тезиса состоит в том, что позднее было предложено еще не- сколько общих определений понятия алгоритма и каждый раз уда- валось доказать, что, хотя' новые алгоритмические схемы и выгля- дят иначе, они в действительности эквивалентны машинам Тью- ринга: все, что реализуемо в одной из этих конструкций, можно сделать и в других. Эти утверледения доказываются строго, так как в них речь идет уже о тождественности формальных схем. Даже ког-
28 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. t да предпринимались специальные попытки выйти за рамки машин Тьюринга, строя алгоритмические схемы, казалось бы, противоре- чащие понятию машины Тьюринга, в конечном итоге все же оказы- валось, что эти схемы сводятся к машинам Тьюринга. До сих пор мы имели дело со специализированными машинами Тьюринга, предназначенными для решения конкретных задач. Од- нако, рассмотренный нами общий способ интерпретации работы машин Тьюринга сам является алгоритмом, а стало быть и ему долж- на соответствовать некоторая машина Тьюринга, в которой входное слово состоит из изображения программы и входного слова интер- претируемой машины. Такая машина называется универсальной^ так как она способна выполнять задания для любой машины Тью- ринга. Мы не будем описывать здесь программу универсальной ма- шины Тьюринга, а рассмотрим только, как можно представлять ин- формацию на ее ленте. Чтобы получить изображение программы интерпретируемой машины, нужно закодировать эту программу в алфавите универсаль- ной машины. Программа кодируется и записывается на ленту уни- версальной машины последовательно, строка за строкой. Алфавит универсальной машины Тьюринга содержит буквы, используемые при записи программ, включая и алфавит интерпре- тируемой машины. Кроме того, в него входят знаки препинания запятая (,) двоето- чие (:) и точка(.) для разделения частей линейного изображения программы; знак * отделяет изображение программы от входного слова для интерпретируемой машины, а знак | служит для указа- ния положения автомата интерпретируемой машины. Например, рассмотрим, как интерпретируется на универсаль- ной машине Тьюринга применение к числу 199 рассмотренной нами машины для прибавления единицы. Входное слово на ленте универ- сальной машины будет выглядеть так: Л', 0,1, - ..,8, 9. Ч1. 1, !. 1, !. 2, 1. .... 9, !. 0, Л, ^*1991 Здесь левее знака * изображена программа машины Тьюринга для прибавления единицы, причем сначала перечислены буквы алфавита этой машины. (Мы заменили Л на Л', чтобы этот знак пу- стой ячейки на ленте интерпретируемой машины не путать с обоз- начением пустой ячейки на ленте универсальной машины.) Предполагается, что автомат универсальной машины предвари- тельно установлен против самой левой буквы ее входного слова (в нашем случае против знака Л'). Мы не указываем, с какого состоя- ния начинается работа интерпретируемой машины, так как ранее договорились, что это всегда состояние На самом деле нет необходимости в том, чтобы алфавит уни- версальной машины Тьюринга включал все знаки алфавита интер- претируемой машины; вместо этого знаки интерпретируемого алфа-
I.3J НОРМАЛЬНЫЕ АЛГОРИТМЫ МАРКОВА 2» вита можно кодировать небольшим числом знаков алфавита универ- сальной машины. (Как мы уже знаем, для такой кодировки в прин- ципе достаточно двух знаков.) После завершения работы универсальной машины на ее ленте должно остаться то слово, которое получилось бы в результате работы интерпретируемой машины; в нашем случае это слово 200 Если интерпретируемая машина не применима к какому-то сло- ву, то универсальная машина тоже должна быть неприменимой, т. е. должна работать над кодировкой этого слова бесконечно долго. Такая интерпретация различных алгоритмов (в данном случае различных машин Тьюринга) посредством одного специально для этого предназначенного алгоритма (в данном случае посредством универсальной машины Тьюринга) называется в программирова- нии прокруткой. 1.3. Нормальные алгоритмы Маркова В 1954 г. советский математик А. А. Марков предложил алго- ритмическую схему, в которой, как и в машине Тьюринга, преобра- зуются слова, но на основе других принципов. В алгоритмической схеме Маркова нет понятия ленты и подразумевается непосредст- венный доступ к различным частям преобразуемого слова. А. А. Мар- ков назвал эту алгоритмическую схему нормальным алгоритмом. Обозначая большими буквами слова в некотором алфавите, можно записать нормальный алгоритм в таком виде: A/t -®n Запись {} означает, что вместо нее должна стоять одна из стрелок -> или t—>. Таким образом, нормальный алгоритм представляет собой упо- рядоченный набор пар слов, соединенных между собой стрелками Двух видов: -> и ь-». Каждая пара представляет собой формулу подстановки для за- мены подслов в преобразуемом слове. Выполнение нормального алгоритма распадается на такты. Каждый такт включает в себя поиск первой по порядку применимой Формулы подстановки и применение этой формулы. Первый такт на- чинается с проверки, является ли слово Ai частью входного слова. Иначе говоря, ищется вхождение слова в исходное слово. На- пример, в слове МАКАР есть вхождение слова МА (но нет вхожде-
30 ' ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. 1 ния слова МК9 так как буквы вхождения должны располагаться подряд). Если вхождение имеется, то оно заменяется на правую часть пары, т. е. на слово Таким образом, производится изме- нение входного слова путем подстановки вместо одного подслова другого подслова. На следующем такте снова ищется вхождение левой части первой пары уже в измененное слово. Если нет вхожде- ния слова Лх, то первый такт продолжается аналогичным рассмо- трением второй пары и т. д. Если при попытке применить формулу подстановки оказывается, что имеется несколько вхождений ее левой части, то всегда заменяется первое (самое левое) вхождение. Если удалось применить какую-то формулу подстановки, найдя в преобразуемом слове левую часть формулы и заменив ее на пра- вую часть, то всегда на следующем такте возвращаются к самому на- чалу нормального алгоритма и снова ищут вхождение левой части первой формулы в измененное слово. Если же какую-то формулу не удалось применить, то происходит попытка применить следующую за ней формулу. Процесс выполнения нормального алгоритма за- канчивается в одном из двух случаев: либо все формулы оказались неприменимыми, т. е. в обрабаты- ваемом слове нет вхождений ни одной левой части какой-либо фор- мулы подстановки; либо только что применилась завершающая формула, в которой левую и правую части разделяет знак н-». В любом из этих случаев считается, что нормальный алгоритм применим к данному входному слову. Если же в процессе выполне- ния нормального алгоритма бесконечное число раз применяются не завершающие формулы, то алгоритм неприменим к данному вход- ному слову. В левых и правых частях формул подстановок могут содержаться пустые слова. Для записи нормальных алгоритмов не требуется специального обозначения пустого слова, так как в этой конст- рукции нет ячеек, не зафиксирован носитель информации, а преоб- разуемое слово свободно раздвигается и сужается. Переход от иных способов описания алгоритмов к эквивалент- ным нормальным алгоритмам называется представлением в нормаль- ной форме, или нормализацией. В качестве примера мы опишем нормальную форму алгоритма кодировки знаками 0 и 1 слов из алфавита {а, Ь, с}: а->101 ft->1001 с-> 10001 Рассмотрим применение этого алгоритма к входному слову caab. Входное слово содержит букву а два раза. Поскольку всегда заме- няется первое вхождение, то в нашем случае первая буква а заме- нится на 101 и мы получим измененное слово clOlaft
1.3] НОРМАЛЬНЫЕ АЛГОРИТМЫ МАРКОВА 31 На следующем такте выполнения нормального алгоритма снова ищется вхождение левой части первой формулы и, найдя и заме- нив его на правую часть, получаем С1011016 Теперь первая формула оказывается неприменимой, применяется вторая формула и получается сЮ11011001 Опять ищется вхождение левой части первой формулы (вообще гово- ря, оно могло бы появиться в результате произведенного изменения, если бы буква а содержалась в правой части примененной формулы). Затем пытаемся применить вторую формулу, но в данном случае оказывается применимой только третья формула. После ее приме- нения получается слово 100011011011001 к которому нельзя применить ни одну формулу. Работа алгоритма завершилась, и мы получили искомую кодировку. Рассмотрим еще несколько простых нормальных алгоритмов. Алгоритм а^- Ь-+ с->- стирает во входном слове буквы а, Ь, с. По определению, вхождения пустого слова имеются слева и справа от каждой буквы в преобразуемом слове. Первое из них пред- шествует первой букве слова. Поэтому алгоритм и который заменяет вхождение пустого слова на букву а, будет бе- сконечно приписывать слева букву а к исходному слову, а это озна- чает, что он не применим ни к какому слову. Алгоритм ►—» а применим к любому слову, так как состоящие из одной завершающей формулы алгоритмы останавливаются (т. е. заканчивают свое вы- полнение) либо сразу, если формула неприменима к входному слову, либо, как в нашем случае, после однократного применения формулы, - хотя полученное слово и будет снова содержать вхождение левой части формулы. Алгоритм 101-*a 1001-*& 10001-*с тоже применим к любому слову. Легко видеть, что он выполняет обратную задачу по сравнению с рассмотренным выше алгоритмом Двоичной кодировки, так как позволяет по слову из знаков 0 и 1 получить слово из букв.
32 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. 1 Сформулируем два простых достаточных признака применимо- сти нормального алгоритма ко всем входным словам: 1) во всех формулах подстановок левые части не пустые, а в правых частях нет тех букв, которые входят в левые части; 2) в каждом правиле подстановки правая часть короче, чем ле- вая. Первый признак гарантирует от «зацикливания», поскольку при < замене вхождений левых частей на правые алгоритм с разными ал- фавитами левых и правых частей не может сам создавать для себя новые возможности выполнения. (Заметим, что этому признаку удов- летворяет рассмотренный нами алгоритм двоичной кодировки и алгоритм, решающий обратную задачу.) Если выполняется второй признак, то после каждого применения формулы подстановки длина слова уменьшается, поскольку боль- шее подслово заменяется на меньшее. Поэтому число замен не может превысить длину исходного слова. (Этому признаку удовлетворяет, например, алгоритм стирания букв а, Ь, с.) Заметим, что алгоритм, удовлетворяющий второму признаку, не может содержать формул подстановки с пустыми левыми частями, так как не бывает слов ко- роче пустого. Перейдем теперь к несколько более сложным примерам. Попро- буем построить нормальный алгоритм приписывания к любому •слову в алфавите {а, Ь, с} справа буквы а. В отличие от машины Тьюринга, свободно передвигающей свой автомат вдоль ленты, нор- мальный алгоритм не имеет непосредственного доступа к правому концу входного слова. Однако мы будем моделировать такой доступ, введя в алфавит специальную букву * для отметки интересующего нас места в слове. Будем реализовывать искомый нормальный ал- горитм по следующей схеме. Г. Приписать букву * к входному слову слева. 2°. Если буква * не последняя в слове, то поменять ее местами со следующей буквой и снова выполнить п. 2°. 3°. Заменить букву * на букву а и остановить алгоритм. Нормальный алгоритм имеет непосредственный доступ к левому концу слова: для того чтобы приписать букву * к входному слову слева, достаточно применить формулу подстановки -*-* Для выполнения п. 2° нужны три формулы подстановок: *с—»-с* Для замены знака * на букву а нужна одна формула подстановки Эта формула содержит стрелку •—>, потому что ее применением завершается выполнение алгоритма. Теперь, чтобы получить нуж- ный нам нормальный алгоритм, остается установить порядок этих
1.3] НОРМАЛЬНЫЕ АЛГОРИТМЫ МАРКОВА 33 пяти формул подстановок. Нельзя начинать запись нормального алгоритма формулой —► * так как эта формула оказывается применимой всегда, и поэтому она применялась бы снова и снова бесконечно долго, причем к сло- ву приписывались бы слева все новые и новые знаки *. Чтобы из- бежать такого бесконечного применения, поместим эту формулу в конец нормального алгоритма после завершающей формулы *<—>а: *а-*-а* *b—>b* *с-*-с* * 1-» а —>* (Для удобства рассмотрения алгоритма формулы подстановки про- нумерованы.) Если входное слово состоит из букв а, Ь, с, то к нему не приме-. нимы первые четыре формулы, левые части которых содержав знак *. Выполнение алгоритма начинается с применения формулы (5), что приводит к приписыванию знака * к слову слева. Затем, в за- висимости от порядка букв во входном слове, применяются форму- лы (1) — (3), и каждый раз знак * сдвигается на одну позицию вправо. Так продолжается до тех пбр, пока знак * не достигнет пра- вого конца слова. Сигналом об этом будет неприменимость формул (1) — (3) из-за того, что правее знака * нет буквы. Тогда применя- ется завершающая формула (4), в результате чего на правом конце слова знак * заменяется на букву а и выполнение алгоритма за- канчивается. Например, если входным словом было aabca то алгоритм будет выполняться в такой последовательности: Преобразованное слово Примененная формула подстановки (5) (1) (1) (2) (3) (1) (4) *aabca a*abca аа*Ьса aab*ca aabc*a aabca* aabcaa х., Можно доказать, что данный алгоритм применим к любому слову, хотя он и не удовлетворяет сформулированным нами достаточным признакам применимости. Если, имея этот алгоритм, мы захотим приписывать справа не букву, а люббе слово, то нам достаточно изменить только заверша- ющую формулу (4). 2 3. Любимский и др.
34 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I Например, если заменить ее на *»—>Ьас, мы будем выполнять ту же последовательность действий, но на последнем этапе перемещен- ный в правый конец слова знак * заменится на Ьас. Теперь продемонстрируем возможность применения нормальных алгоритмов для арифметических вычислений. В принципе, нормаль- ные алгоритмы могут работать с любой формой записи чисел, но для простоты мы будем изображать числа наборами вертикальных палочек, разделяя их знаком *. Например, число 5 изображается как |||||, азапись |||||*||| является изображением двух чисел 5 иЗ. Алгоритм вычисления модуля разности двух чисел а—b описы- вается двумя формулами подстановки: 1*1 -+* * —> При его выполнении последовательно применяется первая фор- мула, и каждый раз слева и справа от знака * исчезает по одной палочке. Когда меньшее из двух чисел исчерпается, первая формула окажется неприменимой и алгоритм завершится применением вто- рой формулы, которая сотрет знак *. Можно было бы снабдить вторую формулу признаком завершаемости i—>, но в этом нет не- обходимости, так как после ее применения обе формулы оказыва- ются неприменимыми из-за исчезновения знака *. Алгоритм получения остатка от деления числа на 5 реализу- ется одной формулой * НИН При каждом применении этой формулы число уменьшается на 5, и так продолжается до получения остатка, меньшего, чем 5. Если нужно получать не только остаток, но и частное, то можно воспользоваться таким нормальным алгоритмом: * —>* Входным словом является набор палочек без знака *. Поэтому сначала первая и вторая формулы не применимы, и мы применяем третью формулу, приписывая к числу слева знак *. Затем после- довательно применяется первая формула, и каждый раз остаток уменьшается на 5, а частное увеличивается на 1. Когда первая фор- мула оказывается неприменимой из-за того, что справа от знака * не нашлось пяти палочек, применяется вторая, завершающая фор- мула, которая введена только для того, чтобы алгоритм прекратил выполняться, а не «проскочил» на выполнение последней формулы. 1.4. Сравнение различных алгоритмических схем Доказано, что алгоритмические схемы Маркова и Тьюринга эквивалентны в том смысле, что все алгоритмы, описываемые в одной из этих схем, могут быть описаны и в другой. Основная ги- потеза теории алгоритмов в форме Маркова приобретает такой вид;
1.41 СРАВНЕНИЕ РАЗЛИЧНЫХ АЛГОРИТМИЧЕСКИХ СХЕМ 35 Всякий алгоритм нормализуем. Как и машина Тьюринга, алгоритмическая схема Маркова в общем случае не может быть физически реализована, так как она допускает неограниченно большую длину входных слов и слов, воз- никающих в процессе подстановок. Сравнивая алгоритмические схемы Тьюринга и Маркова, мы можем отметить, что у машины Тью- ринга сравнительно развито управление при слабых возможностях преобразования информации, тогда как в алгоритмической схеме Маркова богаче возможности преобразования при менее развитом управлении. В машине Тьюринга предусматривается управление последова- тельностью доступа к различным элементам обрабатываемого слова (сдвиг и влево и вправо). А алгоритмическая схема Маркова жёст- ко закрепляет последовательность доступа (каждый раз ищется первое вхождение левой части очередной формулы подстановки). Аналогично и последовательность действий в машине Тьюринга управляемая (за счет смены состояний), а в алгоритмической схеме Маркова жесткая (последовательный перебор формул подстановки, а после каждого применения незавершающей формулы возврат к самой первой формуле). В то же время элементарная операция по преобразованию ин- формации в машине Тьюринга очень проста (замена буквы в ячей- ке ленты), а в алгоритмической схеме Маркова весьма мощная (за- мена любого слова на любое другое слово). Авторы обеих алгоритмических схем старались простыми сред- ствами обеспечить возможность описания любых алгоритмов. Тью- ринг достиг этой цели, в первую очередь, за счет упрощения дей- ствий, а Марков за счет упрощения логики управления. Примерно одновременно с А. Тьюрингом английский математик Э. Пост предложил иную алгоритмическую схему, которая харак- теризуется еще большей простотой. В машине Поста можно записы- вать в ячейки бесконечной ленты всего два знака 0 и 1. Это ограни- чение не влияет на ее универсальность, потому что любой алфавит, как известно, может быть закодирован двумя знаками. Как и ма- шина Тьюринга, машина Поста может находиться в различных со- стояниях, но каждому состоянию соответствует не строка програм- мы с клетками, а некоторая команда одного из следующих шести типов: Первый тип: записать 1, перейти к I. Второй: записать 0, перейти к i. < Третий: сдвиг влево, перейти к i. Четвертый: сдвиг вправо, перейти к I. Пятый: останов. Шестой: если 1, то перейти к i9 иначе перейти к /. Как и в машине Тьюринга, все состояния машины Поста про- нумерованы. Указания «перейти к Ь> или «перейти к /> означают, 2*
36 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ [Гл. I что после выполнения данной команды машина должна перейти в i-e или /-е состояние, которому соответствует своя команда одного из перечисленных шести типов. Команды первых двух типов уста- навливают новое содержимое обозреваемой ячейки ленты, остав- ляя эту ячейку в «поле зрения» машины. Третий и четвертый типы команд обеспечивают сдвиг на одну позицию, т. е. перенесение поля зрения на соседнюю слева или справа ячейку. Первые пять типов команд не зависят от информации на ленте; поэтому, если бы алгоритм был составлен только из команд этих типов, то он запи- сывал бы на ленту всегда одно и то же слово, зависящее только от вида алгоритма, но не от входного слова. Однако добавление ше- стого типа команд делает алгоритмическую схему Поста такой же универсальной, как алгоритмические схемы Тьюринга и Маркова. Доказательство возможности перехода от любой машины Тьюринга к машине Поста основывается на том, что после кодировки исход- ного алфавита знаками 0 и 1 каждое состояние машины Тьюринга, работающей с этими знаками, описывается несколькими состояния- ми машины Поста. 1.5. Понятие алгоритмической неразрешимости В теории алгоритмов известны некоторые задачи, о которых доказано, что для их решения не существует алгоритма. Такие за- дачи называются алгоритмически неразрешимыми. Обычно алгорит- мическая неразрешимость новых задач доказывается методом све- дения к этим задачам известных алгоритмически неразрешимых задач. Тем самым доказывается, что если бы была разрешима но- вая задача, то можно было бы решить и заведомо неразрешимую задачу. В самом деле, если удается свести к новой задаче неразре- шимую задачу, то любой алгоритм решения новой задачи решал бы и ту задачу, что противоречило бы ее неразрешимости. Применяя метод сведения, обычно ссылаются на искусственные задачи, кото- рые не представляют самостоятельного интереса, но для которых легко непосредственно доказать их неразрешимость. К числу та- ких задач относится проблема распознавания самоприменимости. Говоря об универсальной машине Тьюринга, мы показали, как любую схему машины Тьюринга можно записать на ленту в закоди- рованном виде. Аналогично можно закодировать и любой нормаль- ный алгоритм, использовав для разделения формул подстановки какой-нибудь знак, заведомо не встречающийся в формулах (на- пример, запятую). Тогда сам нормальный алгоритм становится сло- вом и может служить входным словом для любого нормального ал- горитма и, в частности, для самого себя. Например, нормальный алгоритм c-+de ab*->k
1.5] ПОНЯТИЕ АЛГОРИТМИЧЕСКОЙ НЕРАЗРЕШИМОСТИ 37 будучи примененным к представляющему его запись слову a-*-b, c->de, ab*—>k станет выполняться так, как показано в табл. 1.5. Таблица 1.5 Преобразованное слово Примененная формула подстановки Ь —► Ь, с —► de, ab k b —► Ь, с —► de, bb н-» k b —► b, de —* de, bb»—> k a —► b a —► b c~+ de Работа закончена, так как ни од- на формула не применима к по- лученному слову. Сначала алгоритм заменит а на b согласно первой формуле, затем снова заменит а на b в другом месте, потом подставит de вместо с и остановится. Однако могло бы оказаться, что алго- ритм не применим к описывающему его слову в том смысле, что ни- когда не остановится, порождая для себя все новые и новые условия для применения формул подстановки. Простейшим примером этого может служить нормальный алгоритм ->а. Таким образом, все алгоритмы делятся на два класса. Само- применимыми. называются алгоритмы, которые, начав работу над собственным описанием, рано или поздно останавливаются. Если же алгоритм в таком случае зацикливается, он называется несамо- применимым. Аналогично можно говорить о самоприменимости машин Тьюринга, имея в виду их применение к своим линейным изображениям. И вообще, для любой формы записи алгоритмов мож- но ставить проблему такой классификации. Таким образом, возникает задача: как узнать, является ли са- моприменимым тот или иной алгоритм, т. е. остановится ли когда- нибудь заданный алгоритм, будучи примененным к собственному описанию. Выражаясь более точно, нужно найти общий алгоритм, который для любого алгоритма отвечал бы на вопрос, является ли он самоприменимым. Мы докажем, что такого общего алгоритма рас- познавания самоприменимости не существует. Доказательство будем вести от противного. Предположим, что искомый алгоритм сущест- вует и назовем его А. Алгоритм А применяется к записи любого алгоритма Р и вырабатывает букву С или букву Н, в зависимости °т того, самоприменим или нет алгоритм Р: С, если Р самоприменим, Н, если Р несамоприменим. итм А может быть применен к записи любого алгоритма, словом для него служит запись алгоритма Р, а выходное А (запись Р) =4> Алгор Входным
38 ЭЛЕМЕНТЫ ТЕОРИИ АЛГОРИТМОВ (Гл. I слово состоит из одной буквы. Построим еще один алгоритм В, ко- торый должен, увидев букву С, зациклиться, а увидев букву Н, остановиться. Ему соответствует машина Тьюринга с программой: А С н Qi С* <h A, gi 1 В самом деле, эта машина, увидев букву С, будет работать без конца, поочередно то стирая, то вновь записывая букву С в обозре- ваемой «мигающей» ячейке. Увидев же букву Н, она сразу останав- ливается. Таким образом, факт существования алгоритма В не вы- зывает сомнения, он доказан описанием этой машины Тьюринга. Мы знаем из предыдущего, что, если есть два алгоритма Л и В, то существует и их произведение К.-А-В, т. е. алгоритм, которой осуществляет последовательную работу этих алгоритмов, выполняя сначала Л, затем В. В разд. 1.2.3 мы показали, как строится про- грамма машины Тьюринга, реализующей такой алгоритм. Если мы докажем, что алгоритм К не может существовать, то либо не существует Л, либо не существует В, либо нельзя построить их произведение. Но В заведомо существует, а если бы существовал алгоритм Л, то можно было бы построить и произведение А-В. Тем самым мы опровергнем предположение о существовании алгорит- ма Л. Мы докажем, что алгоритм К не может существовать, доказав, что он не может быть ни самоприменимым, ни несамоприменимым. Действительно, рассмотрим применение алгоритма Л к его соб- ственной записи. По определению, сначала к ней применится ал- горитм Л, затем к полученному результату применится алгоритм В. Предположим, что алгоритм К самоприменим. Тогда Л (запись К)=>С. Но, получив на входе букву С, алгоритм В должен зациклиться, а это означает, что зацикливается сочетание Л и В. Значит, алгоритм К. оказывается несамоприменимым, что противоречит предположению. Если же К несамоприменим, то Л (запись и, получив на входе букву Н, алгоритм В остановится, т. е. алгоритм К оказывается самоприменимым, и мы снова приходим к противо- речию. Итак, алгоритм # не может быть ни самоприменимым, ни не- самоприменимым, а, следовательно, его вообще не существует. Поскольку он формально построен из алгоритмов Л и В, то не должно существовать какого-нибудь из этих двух алгоритмов.
16J ' ПОНЯТИЕ АЛГОРИТМИЧЕСКОЙ НЕРАЗРЕШИМОСТИ 39 Алгоритм В заведомо существует, значит, не может быть алго- ритма А. Тем самым мы доказали, что проблема распознавания са- моприменимости алгоритмически неразрешима. Отсюда вовсе не следует, что, какой бы мы ни взяли алгоритм, невозможно распоз- нать, самоприменим ли он. Например, очевидно, что нормальный алгоритм а->а несамоприменим, так как он никогда не останавливается, тогда как алгоритм а*-+а самоприменим, потому что он в любом случае остановится либо сразу, либо после одной замены буквы а на нее же. В принципе, может даже оказаться, что для любого отдельно взятого алгоритма удастся доказать, самоприменим ли он. Утверждается только, что нет массового алгоритма, который решал бы весь класс задач рас- познавания самоприменимости любых алгоритмов. Аналогично, этому, алгоритмически неразрешима сформулированная Лейб- ницем проблема проверки правильности любых математических утверждений, но из этого не следует, что есть теоремы, которые ни- когда не смогут быть доказаны. Алгоритмическая неразрешимость проблемы Лейбница означает, что бесполезно искать общий способ для доказательства всех теорем.
Глава 2 ПРОГРАММИРОВАНИЕ НА АЛГОРИТМИЧЕСКИХ ЯЗЫКАХ, АЛ ГОЛ-60 2.1. Общая характеристика алгоритмических языков Алгоритмические языки (называемые также языками програм* , мирования) — подобно машинам Тьюринга, Поста и нормальным , алгоритмам Маркова — также предназначены для представления различных алгоритмов. Однако в рамках этого общего назначения они преследуют существенно иные цели. Дело в том, что рассмотренные в главе 1 алгоритмические схемы преследовали прежде всего цели теоретического характера — обес- печить принципиальную возможность представления с их помощью любых алгоритмов, позволить теоретически исследовать те или иные алгоритмы (например, для установления эквивалентности различ- ных алгоритмов), служить для доказательства алгоритмической не- разрешимости отдельных проблем и т. п. Для достижения этих це- лей важно, чтобы сам. аппарат представления алгоритмов был мак- симально простым (этим объясняется элементарность действий на каждом шаге работы машины Тьюринга и Поста, элементарность управления в нормальных алгоритмах и т. п.). Основная же цель, которая ставится при разработке алгорит- мического языка, состоит как раз в том, чтобы предложить некое фор- мализованное средство общения между людьми, а также и главным образом между человеком и вычислительной машиной, предназна- ченное для изложения алгоритмов. Такой язык должен быть формальным, чтобы любая запись на нем однозначно определяла алгоритм. Например, приведенное в разд. 1.1 описание алгоритма Евклида нельзя считать вполне точ- ным, так как в нем почти каждую фразу можно понимать по-разному и получается, что оно не обеспечивает строго однозначной последо- вательности действий. Если тот же алгоритм записать на алгоритми- ческом языке, то эта запись однозначно определит все действия по применению алгоритма. Второе требование к алгоритмическому языку состоит в том, что он должен быть настолько простым, чтобы не возникало чрезмерных затруднений при разработке специальной программы-транслятора, осуществляющей автоматический перевод любых текстов с этого языка на язык вычислительной машины. В отличие от прежних алгоритмических схем, от которых тре- бовалась только универсальность и простота, такой язык должен
t l] ОБЩАЯ ХАРАКТЕРИСТИКА АЛГОРИТМИЧЕСКИХ ЯЗЫКОВ 41 быть и достаточно гибким, чтобы обеспечивать компактную и на- глядную запись алгоритмов. Он должен быть легко доступен для освоения широким кругом лиц, заинтересованных в решении задач определенного класса, а также достаточно удобен для его исполь- зования в публикациях. Иначе говоря, он должен быть настолько богат выразительными средствами, чтобы люди не испытывали не- удобств, передавая с его помощью алгоритмы друг другу или же вычислительной машине. Для этого нужно, -чтобы способ запи- си алгоритмов на таком языке был достаточно близок к традицион- ным способам описания алгоритмов в книгах, статьях и инструк- циях. К настоящему времени разработано много различных алгорит- мических языков, несколько из них получили широкое международ- ное признание. Каждый алгоритмический язык предназначается для достаточно широкой, но все же, в какой-то степени, ограни- ченной области приложения, для определенного круга задач. Подобно тому как профессиональные языки физиков, медиков, экономистов различаются своей терминологией, отражающей ос- новные понятия, характерные для той или иной области знания, каждый алгоритмический язык характеризуется своей ориентацией на определенные типы и структуры данных, а также на определенные действия над данными. При описании алгоритмов численной математики определенный тип указывает, являются ли элементы данных целыми, веществен- ными или комплексными числами, логическими значениями. Для описания алгоритмов аналитических преобразований математиче- ских формул требуются также такие типы, как буквы алфавита, спе- циальные математические знаки и т. д. Для каждого круга задач, отражающих некоторый круг реаль- ных процессов, характерны определенные структуры данных, т. е. формы связи между элементами данных. Например, в задачах ли- нейной алгебры элементы данных (числа) объединяются в векторы и матрицы; при автоматической обработке документации приходится иметь дело со структурой списка с заданным порядком следова- ния элементов, а также со структурой таблицы, к которой можно обратиться, указав, какая требуется строка; при составлении же- лезнодорожных расписаний нужно работать со структурой графа, в котором каждый элемент (вершина) может быть связан (ду- гами) с любым числом других элементов. Действия над элементами структур и целыми структурами опи- сываются операторами алгоритмического языка. Каждый алго- ритмический язык характеризуется своим набором основных видов операторов, ориентированных на конкретный круг задач. Как правило, один оператор алгоритмического языка позволяет выпол- вить существенно более крупные действия, чем, например, одна операция машины Тьюринга.
42 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Для обозначения объектов алгоритмического языка использу- ется широко распространенное в математике понятие переменной ве- личины. Однако по существу своему переменные алгоритмических языков отличаются от переменных, которыми мы оперируем, на- пример, в алгебре. Когда мы употребляем переменную в алгебре, то имеем в виду, что она будет иметь определенное значение, которо- го мы еще не знаем. В то же время в алгоритмическом языке каждая переменная в процессе выполнения алгоритма может- последова- тельно принимать разные значения. Скорее следовало бы сравнить переменную алгоритмического языка с ячейкой на ленте машины Тьюринга. Аналогично занесению новых значений в ячейки машины Тьюринга, можно в процессе выполнения алгоритма присвоить но- вое значение любой переменной и использовать его впоследствии сколько угодно раз, причем после каждого использования перемен- ная сохраняет свое прежнее значение. Забывается старое значение переменной только тогда, когда в эту переменную пишется новое значение. Однако в отличие от безымянных ячеек машины Тью- ринга, каждая переменная имеет свое имя, по которому к ней можно непосредственно обратиться. Это создает существенные удобства для программиста, так как избавляет его от необходимости орга- низовывать доступ к нужной переменной последовательным перебо- ром, как это приходится делать при доступе к ячейке на машине Тьюринга. Таким образом, фундаментальным действием в любом алгорит- мическом языке, как и в рассмотренных нами алгоритмических схемах, является присваивание, которое изменяет значение некото- рой переменной. В алгоритмических языках, предназначаемых для математических расчетов, оператор присваивания обеспечивает счет по формуле и присваивание некоторой переменной вычисленною значения. Выполнение операторов присваивания может включать в себя поиск по структуре и преобразование структуры данных. Например, возможны операторы «вставить новую строку в таблицу» или «умножить две матрицы и присвоить третьей матрице значение полученного результата». В алгоритмических языках предусматриваются определенные правила построения композиций операторов, т. е. объединения их в более крупные операторы. Каждое правило построения компо- зиции предписывает свой порядок выполнения операторов, входя- щих в композицию. Способы описания таких композиций в алгорит- мических языках более удобны и естественны для человека, чем управление посредством перехода в следующее состояние в машине Тьюринга или посредством обязательного перехода к следующей или возврата к первой формуле подстановки в нормальном ал- горитме. В основе управления алгоритмических языков лежат три ти- повых операторных конструкции.
J.1] ОБЩАЯ ХАРАКТЕРИСТИКА АЛГОРИТМИЧЕСКИХ ЯЗЫКОВ 43 Следование: начало 51; S2; . . .; Sk конец. Выбор: если В то 51 иначе 52. Повторение (цикл): пока В выполнять 5. Первая из этих конструкций означает, что нужно выполнить сначала оператор 51, потом 52 и т. д. и закончить выполнением опе- ратора Sk. Вторая конструкция означает, что нужно проверить условие В и, если оно верно, то выполнить оператор 51, а если не верно — оператор 52. Третья конструкция означает, что нужно по- вторять выполнение оператора 5, пока верно условие В. Условие В включает в себя одну или несколько переменных и является верным или нет на каждом этапе выполнения алгоритма в зависимости от того, каковы текущие значения этих переменных. Например, условие В: х>у верно при значениях х=5 и t/=3, но если эти значения изменятся так: х=4 и у=1, то условие В станет неверным. При выполнении конструкции цикла пока В выполнять 5 проверка верности условия В производится перед каждым очеред- ным выполнением оператора 5. Если при очередной проверке ока- зывается, что условие В не верно, то на этом заканчивается выпол- нение цикла. Каждая из рассмотренных трех операторных конструкций в свою очередь объявляется оператором и может быть подставлена в другую конструкцию любого типа. Например, возможна такая композиция: начало 51; 52; если 51 то 53 иначе пока В2 выпол- нять 54; 55 конец, которая тоже является оператором. Здесь задан следующий порядок действий: выполнить оператор 51, затем вы- полнить 52, потом, в зависимости от верности условия В1, выбрать (и выполнить) либо оператор 53, либо цикл пока В2 выполнять 54, а затем выполнить оператор 55. Помимо рассмотренных основных конструкций управления в алгоритмических языках обычно предусматривается также возмож- ность прямой передачи управления на любой оператор. С этой целью операторам даются имена (метки). Существуют и другие средства управления, с которыми мы познакомимся в последующих разделах этой главы. Первым развитым алгоритмическим языком, который получил общее признание и широкое применение, был язык алгол-60 *). Он удовлетворяет всем необходимым требованиям к алгоритмиче- ским языкам, и его структура продумана очень удачно. Поэтому он успешно выдержал испытание временем и до сих пор является од- ним из наиболее популярных языков программирования. За про- шедшие два десятилетия в научной литературе было опубликовано много алгоритмов на этом языке, причем часто такие публикации . *) Алгол (ALGOL) — сокращение английских слов Algorithmic Language (алгоритмический язык), а 60 означает год (1960) его официальной публикации.
44 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-бО (Гл. 2 предназначались просто для того, чтобы точно сообщить другим людям, как нужно решать те или иные задачи. Дело в том» что алгол достаточно удобен для описания определенного, весьма важ- ного класса так называемых вычислительных алгоритмов, т. е. ал- горитмов выполнения всевозможных вычислений. При изложении методов программирования на алгоритмических языках мы будем основываться на языке алгол. 2.2. Способ описания алгоритмических языков Именно потому, что алгоритмический язык имеет богатые выра- зительные возможности, четкое, недвусмысленное определение этого языка представляет собой сложную проблему. Развитым аппаратом для таких целей служат формальные грамматики, исследованием и разработкой которых занимаются многие-ученые. Для описания ал- гола и некоторых других алгоритмических языков была использо- вана грамматика, предложенная американским математиком Дж. Бэкусом (нормальная форма Бэкуса). Грамматическое описание любого языка (в том числе и языка, на котором мы говорим) включает в себя его алфавит, синтаксис и семантику. Алфавит языка представляет собой набор основных сим- волов («букв» языка), которые могут быть использованы при записи алгоритма. Синтаксис — это правила построения фраз, позволяю- щие определить, правильно или неправильно написана та или иная фраза. Точнее говоря, синтаксис языка представляет собой набор правил, устанавливающих, какие комбинации символов являются текстами на этом языке. Такой набор правил позволяет для любого текста получить ответ на вопрос, написан ли он на рассматриваемом языке. (Заметим, что привычная нам грамматика русского языка не формальна, как и грамматики других естественных языков. Имен- но поэтому лингвисты иногда не могут прийти к единому толкова- нию некоторых спорных грамматических проблем.) Метод Бэкуса позволяет’описать при помощи формул весь синтаксис алгоритми- ческого языка. Семантика определяет смысловое значение фраз языка. В част- ности, семантика алгола устанавливает, какие последовательности действий описываются теми или иными фразами этого языка, а в конечном итоге, какой алгоритм определен данным текстом на ал- голе. Алгоритмические схемы Тьюринга, Маркова и Поста тоже можно рассматривать как алгоритмические языки и они тоже имеют свои синтаксис и семантику. Например, у Тьюринга синтаксисом является форма задания программы в виде матрицы, каждая клетка которой содержит три знака (или менее). Этот синтаксис описыва- ется не формально потому, что он очень прост и не допускает дву- смысленных толкований даже в словесном изложении. Семантика машины Тьюринга — это слова о том, что автомат «видит» каждый
2.2] , СПОСОБ ОПИСАНИЯ АЛГОРИТМИЧЕСКИХ ЯЗЫКОВ 45' раз одну ячейку, может находиться в различных состояниях, ищет в матрице клетку, изменяет содержимое одной ячейки и т. д. Аналогично для алгоритмического языка А. А. Маркова син- таксис состоит в том, что нормальным алгоритмом является после- довательность формул подстановок, в каждой из которых содержится одна стрелка. Семантика здесь — это описание способа поиска вхождений, замены подслов по формулам подстановки с последую- щим возвратом к началу алгоритма и т. д. В отличие от этих алгоритмических схем развитый алгоритми- ческий язык весьма богат по изобразительным средствам. Например, синтаксис алгола настолько развит и многообразен, что допускает построение очень сложных и длинных фраз. Поэтому серьезную проблему представляет собой формальное определение правиль- ности тех или иных алгольных фраз. Для этого используется спе- циальный формализм Бэкуса, основанный на том, что язык описы- вается при помощи формул. 2.2.1. Нормальная форма Бэкуса. В нормальной форме Бэкуса (НФБ) каждая формула представ- ляет собой определение некоего термина языка. Название опреде-. ляемого термина, взятое в угловые скобки ( ), записывается в ле- вой части формулы; левая часть отделяется от правой служебным знаком :: = (что означает «это есть»); справа от знака :: = сообща- ется, какие конструкции могут являться определяемым термином. Правая часть формулы может содержать знаки алфавита описывае- мого языка или другие термины, входящие в определяемый термин как составные элементы. В качестве примера рассмотрим формулу, определяющую термин «цифра»: (цифра) ::=0|112|3|4|5|6|7|8|9 В этой формуле употреблен еще один служебный знак | (пря- мая черта), который читается как «или». В правой части сообщается, какие знаки являются цифрами. В данном случае 0, 1, . . ., 9 есть «значения» понятия «цифра». В правой части формулы знаками | (или) разделяются так называемые альтернативы, которыми описываются различные варианты значения определяемого термина. Аналогично, формула . (буква) ::^|B|C|D|£|F|G|^|/|J|K|£|M|A(|O1P|Q|^|S|TH7|F| W\X\Y\Z означает, что понятие «буква» может принимать 26 различных зна- чений, каждым из которых является заглавная буква латинского алфавита. Заметим, что служебные знаки (, ),::=* и | не входят в алфавит языка, описываемого с помощью НФБ. Рассмотрим теперь формулу (пара ):: = (буква) (цифра)
46 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Здесь правая часть содержит не знаки, а названия терминов, ко- торые также заключаются в угловые скобки ( ) и означают, что раз- решается подставлять вместо этих терминов любые их значения. Например, подставляя вместо (буква) любую заглавную латинскую букву, а вместо (цифра > любую цифру, мы можем получать любые комбинации Л О, Л1, . . ., Z8, Z9. Каждая из этих комбинаций будет, согласно данной формуле, значением понятия «пара». Формула (знак):: = (буква )| (цифра) отличается в своей правой части от предыдущей формулы наличием знака |. Согласно этой формуле, значением понятия (знак) может быть или значение понятия (буква) или значение понятия (цифра). Поэтому она определяет другое понятие, значениями которого яв- ляются одиночные знаки Л, В, . . ., Z, 0,1, . . ., 9. Если предыду- щая формула обеспечивала 26-10=260 различных вариантов «пары», то эта формула обеспечивает 26+10=36 различных вариантов «знака». Формула (номер):: = N (цифра) устанавливает, что конкретными примерами понятия «номер» яв- ляются комбинации NO, N1, . . ., N9. Если мы захотим предусмо- треть не 10, а 11 номеров, то для этого достаточно дописать в конец правой части |N10. Получится формула (номер )::=N (цифра )|N 10 согласно которой допускаются 10 вариантов номера из двух знаков и один вариант из трех знаков. Такие формулы, служащие для формального описания языка, называются металингвистическими. Рассмотренные. нами до сих пор металингвистические формулы четко предопределяют число знаков в возможных значениях описываемых терминов. Однако можно определить формулой и термин из произвольного числа зна- ков. Это вполне естественно, так как нашей конечной целью явля- ется формальное определение любого алгоритма на алголе, а алго- ритмы могут иметь произвольную длину. Примером такого опреде- ления может служить следующая формула: (слово):: = (буква)| (слово) (буква) Согласно своей левой части, эта формула определяет термин «сло- во». Из правой части формулы следует, что, если нам известно ка- кое-то слово (например, из одной буквы), то, добавав к нему букву, мы снова получим слово. Мы можем взять любое значение термина «буква», например, В (которое является словом, согласно первой альтернативе правой части формулы) и приписать к этому значению
2.21 СПОСОБ ОПИСАНИЯ АЛГОРИТМИЧЕСКИХ ЯЗЫКОВ 47 любую букву, скажем, А. Согласно второй альтернативе правой части формулы, полученная комбинация ВА является словом, а, следовательно, опять-таки согласно второй альтернативе правой части формулы, к нему можно приписать еще букву Р, и при этом тоже получится слово, на этот раз ВАР. Так можно получить слово любой длины. Например, легко проследить буква за буквой, что комбинация букв TEMPERATURE также является словом в силу этого определения. Вообще, согласно второй альтернативе из пра- вой части этой формулы, если любая комбинация из п букв является словом, то и любая комбинация из (п+1) букв тоже является словом. Рассмотренная формула представляет собой пример рекурсив- ного определения, когда сам термин определяемого понятия исполь- зуется для получения некоторых своих собственных значений. При этом он может непосредственно встречаться в правой части формулы своего определения. Тем самым уже известные значения понятия служат основой для получения новых значений того же понятия. Аналогично можно строить более общие понятия, например, список слов. (Заметим, что различные списки часто используются в различных конструкциях языка алгол.) Списком слов мы будем на- зывать любую последовательность слов, разделенных запятыми. Металингвистической формулой это выражается так: (список слов):: = (слово)|(список слов), (слово) Согласно этой формуле, любое слово представляет собой частный случай списка слов. Приписывая после него запятую и еще одно слово, мы получаем снова список слов, состоящий уже из двух слов. Например, В — слово, а значит это список слов; ВАР — тоже слово, поэтому В, ВАР — список слов; присоединив к этому списку TEMPERATURE, мы получим новый список слов В, ВАР, TEMPERATURE Аппарат металингвистических формул позволяет нам опреде- лять язык алгол, последовательно. усложняя вводимые понятия и переходя ко все более крупным понятиям до тех пор, пока мы не придем к понятию алгол-программы. Если бы мы формально описы- вали этим же методом русский язык, то в конце концов могли бы прийти к определению термина «книга», например, по формуле (книга):: = (титульный лист) (текст) (выходные данные) Титульный лист можно было бы определить так: , (титульный лист):: = (автор) (название) Текст определялся бы как набор слов и т. д. Однако такой метод не поможет нам формально определить Понятие «хорошая книга» или «правильный алгоритм». Он пригоден
48 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. Э только для того, чтобы проверять, правилен ли синтаксически тот или иной текст. С его помощью нам не удастся узнать, какие вычис- ления предусматривает данный алгоритм, или же, является ли ос- мысленным данное слово. Альтернативы в правой части металингвистической формулы мож- но менять местами: совокупность значений, определяемых форму- лой, от этого не изменяется. В частности, приведенную выше фор- мулу для «слова» можно записать и так: (слово): :=(слово) (буква) | (буква) Такое определение ничем не отличается от данного выше, посколь- ку совсем не обязательно первое значение термина получать по са- мой левой альтернативе. Только из эстетических соображений в фор- муле для «цифры» мы перечисляем в обычном порядке альтернатив- ные значения этого термина 0 | 1 | 2 | 3 ... С таким же успехом мы могли бы начать этот список и так: 7 | 3... Важно только, чтобы бы- ли упомянуты все альтернативы. Однако, как правило, нельзя про- изводить перестановки внутри одной альтернативы. Например, фор- мулы N (цифра) и (цифра) N задают разные виды комбинаций знаков. Таким образом, металингвистическая формула не только указывает знаки, входящие в определяемый термин, но и устанав- , ливает их порядок. Впрочем, иногда можно переставлять части не- кой альтернативы, если окажется, что значениями определяемого термина являются и такие переставленные комбинации знаков. На- пример, поскольку слово — это произвольная последовательность букв, то совершенно неважно, дописываем ли мы буквы справа или слева. Поэтому допустимо и такое определение: (слово) ::= (буква) | (буква )(слово) Теперь к исходному слову можно приписывать буквы только сле- ва, и вывод слова TEMPERATURE нужно начинать не с началь- ной буквы Т, как мы сделали бы при прежнем определении, а с ко- нечной буквы Е. Такое изменение формулы возможно потому, что термин «слово» строится из одинаковых терминов «буква». Авторам алгола не удалось дать обозримое формальное описание семантики этого языка, и поэтому, в отличие от формализованного синтаксиса, вся семантическая информация об алголе была изложе- на ими в виде пояснений на английском языке, переведенных впо- следствии на русский и другие языки. 2.3. Алфавит алгола Мы приступаем к систематическому описанию алгола и начнем с того, что определим набор символов, т. е. знаков, из которых мож- но строить запись алгоритма: (символ) ::= (буква) | (цифра) | (ограничитель)
АЛФАВИТ АЛГОЛА 4» 3.3) В сущности, символ — это буква алфавита языка алгол, но мы оставляем термин «буква» за русскими и английскими буквами, ко* торые входят в алфавит алгола, но не исчерпывают его. Теперь поясним использованные в этом определении альтерна- тивы. При записи алгоритмов на языке алгол разрешается использо- вать любые строчные и заглавные буквы английского и русского языков. <буква>:: = a|b|c|d|e|f|g|ft|/|/|^|/|m|n|o|p|^|r|s|/|u| Q| R |S|T | (/1 V| IF | Х| У |2|б|е|д|ж|з|л |м|н|п|т|ф|ц| ч|ш|и<|ь|'&|ы|э|ю|я|£> |Г|Д|.Ж|3|Я|Л|Л|У|ФЩ| Ч\Ш\Щ[ Ь | Ъ\Ы | Э\ю\я В правой части этой формулы перечислены по порядку буквы латинского алфавита и отличающиеся от них по начертанию русские буквы. Такой широкий выбор букв облегчает использование алго- ла как средства общения между людьми. Однако при общении с вы- числительной машиной возникает необходимость кодировать любую букву пробивками на перфокартах или использовать другие сред- ства механического ввода информации. Чем больше выбор букв, тем сложнее оказывается аппаратура ввода. Как мы уже знаем, в принципе было бы достаточно двух знаков, что обеспечило бы край- нюю простоту аппаратуры, но затруднило бы восприятие информа- ции человеком. На практике выбирается компромисс, приемлемый для людей и пригодный для машинной реализации. Например, в алгоритмах, предназначенных для машин, обычно допускается ис- пользование только заглавных букв латинского алфавита. (Пред- почтение отдается латинскому алфавиту потому, что он привычен для написания математических формул, а это основная цель языка' алгол.) Для определения термина «цифра» используется уже знакомая нам формула <цифра)::=0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 Понятие ограничителя в алголе примерно соответствует понятию знака препинания в русском языке. Элементы фраз ограничиваются запятой, точкой с запятой или точкой. Элементы алгольного текста ограничиваются знаками операций, скобками, знаком := и некото- рыми другими служебными знаками, а также служебными словами (*f»then, do и др.), для которых не удалось подобрать подходящих - знаков. При выборе специальных знаков учитывались установив- шиеся традиции. В частности, элементы списков разделяются запя- тыми (по аналогии с естественными языками).
60 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Итак, ограничитель — это либо служебный знак, либо служеб- ное слово: (ограничитель) (служебный знак) | (служебное слово) (служебный знак) ::== (знакоперации) ((скобка) | (разделитель) Все алгольные знаки операций исчерпываются следующей металинг- вистической формулой: (знак операции)::= ||Х|/|4-|+|—|<|С1=|>1>1¥=|—11 Л |V | =>1= Здесь перечислены 17 знаков операций. Из них первые 6 знаков от- носятся к арифметическим операциям, следующие 6 соответствуют операциям отношения, а последние 5 знаков являются знаками ло- гических операций. Большинство из перечисленных знаков привыч- ны читателям, знакомым с элементарной математикой. Заметим, что знак f служит для обозначения возведения в степень. Знак 4- употребляется для «деления нацело», т. е. для деления целых чи- сел с получением целой части результата. Что касается знаков ло- гических операций, то знак —1 служит для операции отрицания, знак Д обозначает конъюнкцию (операцию, называемую также ло- гическим умножением), V — знак дизъюнкции (логического сложе- ния), z> — знак операции импликации (следования), = служит зна- ком операции эквивалентности. В дальнейшем мы подробнее оста- новимся на этих операциях. В алголе употребляются следующие виды скобок: (скобка) ::= ( | ) | I | 1 | „ | ” Круглые скобки применяются в формулах для выделения под- выражений или аргументов функций. Квадратные скобки служат для выделения индексов (которые обычно в математическом тексте пишутся внизу строки, тогда как в алголе все символы пишутся в - одну строку). Что касается кавычек, то употребление этого вида скобок связано с тем, что в некоторых случаях алгол позволяет оперировать не только числами, но и особыми строками символов, которые отделяются кавычками от остального алгольного текста. Разделители перечисляются в следующей формуле (разделитель) ::=, | . | ; | := | : Запятая разделяет элементы списков. Точка используется для разделения целой и дробной части чисел. Точка с запятой разде- ляет предложения языка. Знак : = отделяет имя переменной от вы- ражения, по которому вычисляется ее значение. Двоеточие отделяет от оператора его имя (метку), а также используется при описа- нии массивов. Служебное слово представляет собой один символ и употреб- ляется Для наглядности, чтобы не выдумывать новые служебные знаки, которых нет в традиционной математической символике и
2.3] АЛФАВИТ АЛГОЛА 5> которые поэтому могли бы запутать читателей. Для каждого слу- жебного слова из нескольких значений, которые соответствующее слово имеет в английском языке, выбрано одно определенное зна- чение, соответствующее служебной роли этого слова в алголе. В табл. 2.1 приводится полный список этих служебных слов с пе- реводом их смыслового значения и с краткими пояснениями. Не- которые из служебных слов будут объяснены в дальнейшем. Работая с языком алгол, необходимо знать эти слова наизусть» чтобы каждый раз понимать их смысл, не обращаясь к словарю. Таблица 2.1 Служебное слово Русский перевод Пояснение true false go to if then else for do step until while comment begin end boolean integer real array switch procedure string label value 10 Помимо 24 ан жебное слово 10, ] истина ложь перейти к если то иначе для выполнить шаг ДО пока комментарий начало конец логический целый вещественный массив переключатель процедура строка метка значение глийских служебны которое также игра Обозначения двух возможных логи- ческих значений в алгебре логики Является начальным символом опе- ратора перехода, который служит для изменения порядка выполнения алгоритма Эти слова употребляются при выборе пути по алгоритму с учетом условий Слова, служащие для задания цикли- ческого выполнения действий Служит для внесения в алгольный текст примечаний, не влияющих на выполнение алгоритма Служебные слова, играющие роль скобок для выделения частей алго- ритма Служат для описания типов данных Служит для удобства записи чисел, начинающихся или заканчивающихся многими нулями х слов, этот список содержит слу- ет роль единого знака.
52 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. S 2.4. Неформальное рассмотрение фрагментов алгол-программ Прежде чем продолжить изложение формальных правил записи алгоритмов на алголе, мы рассмотрим несколько простых приме- ров. Эти примеры берутся из области вычислений, так как язык алгол предназначен именно для описания вычислительных алго- ритмов. Любой вычислительный алгоритм состоит из действий, в ходе выполнения которых значения формул присваиваются переменным, а переменные используются для вычисления новых значений, пока не получатся окончательные результаты. Например, на алголе мож- но записать действие х'.—а означающее присваивание переменной х того значения, которое имеет переменная а. Здесь вместо обычного знака равенства упот- реблен особый знак := . Дело в том, что знак = может пониматься неоднозначно, так как в разных ситуациях он имеет три различных смысла. Запись х=а могла бы означать: 1) высказывание, что уже сейчас переменные х и а имеют равные Значения; нужно сравнить эти значения, чтобы выяснить, истинное это высказывание или нет; 2) присваивание переменной х того значения, которое имеет переменная а; 3) присваивание в противоположном смысле, т. е. установка нового значения переменной а, равного значению переменной х. Во избежание таких недоразумений, в алголе знак = исполь- зуется только для записи высказываний, а для присваивания зна- чений применяется знак :=. В этом знаке двоеточие обращено в сто- рону той переменной, которая получает новое значение. Запись х:=а означает, что значение х изменяется и становится равным значению а, причем переменная а сохраняет свое прежнее значение. Если мы запишем х:=а+12 Это будет означать, что к значению а нужно прибавить число 12 И присвоить переменной х значение полученной суммы. Рассмотрим следующую запись на алголе a:—(V/(OH—VHA4)/t Здесь переменной а присваивается значение дроби, у которой в знаменателе t, а в числителе разность значений двух переменных ч VKOH и VHA4. Тем самым вычисляется ускорение тела при равно- мерно ускоренном движении. Обычно мы записываем такое действие
2.41. \ НЕФОРМАЛЬНОЕ РАССМОТРЕНИЕ ФРАГМЕНТОВ ПРОГРАММ S3 с помощью формулы д V кон — 1^иач На алголе нельзя прямо воспроизвести эту формулу по двум причинам. Во-первых, в ней употреблен знак = в смысле присваи- вания, а во-вторых, в языке есть специфическое требование, линей- ности текста, вызванное тем, что алгол должен быть понятен вычис- лительным машинам. Обычно в устройствах ввода информации зна- ки поступают один за другим и поэтому желательно, чтобы вводи- мый текст был линейной цепочкой символов. Именно поэтому в алголе нет понятий «нижний символ» (индекс), «верхний символ» (степень), «над чертой» (делимое), «под чертой» (делитель) и т. д. В частности, алгол не позволяет называть переменные буквами с подстрочными знаками (VK0H, VHa4), но зато разрешает обозначать переменную не обязательно одной буквой. Например, мы можем на- писать VKOH вместо Укои и VHA4 вместо VHa4. Вместо горизонтальной дробной черты используется знак деле- ния /, что позволяет записывать делимое и делитель в одной строке, заключая каждое из них в случае надобности в круглые скобки. Та- ким образом, мы приходим к записи на алголе a:=(WW —V/L4V)// Заметим, что круглые скобки для делимого необходимы, так как без них мы получили бы запись: a.=VKOH— VHA4/t, означающую применение другой формулы: a=V _____^2 В алголе не используется для обозначения деления двоеточие, так как этот знак применяется для других целей. Рассмотрим теперь несколько более сложный пример вычисле- ния двух сторон треугольника, когда заданы третья сторона и си- нусы углов треугольника. В обычном математическом тексте мы на- писали бы __a sin fl _a sin у sin а ’ sin а * На алголе это можно записать так: b:=axsin (beta)/sin (alpha)-, с: =axsin (gamma)/sin (alpha). Язык алгол допускает любые заглавные и строчные буквы ла- тинского и русского языка, но в нем нет греческих букв. Поэтому вместо греческих букв мы написали на алголе соответствующие ком-
64 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. » бинации латинских букв. В обычном математическом тексте нам ясно, что sin х —это значение функции sin от аргумента х, но алгол позволяет трактовать такую запись как обозначение переменной, состоящее из четырех букв: s, i, п, х. Если же мы хотим обозна- чить sin от х, то необходимо заключить аргумент в круглые скобки, написав sin(x). И вообще, в записи на алголе аргументы любой функции заключаются в круглые скобки. По аналогичной причине, нельзя пропускать знак умножения между сомножителями. Например, запись ab воспримется не как произведение, а как название переменной, состоящее из двух букв. Точно так же запись a sin(alpha) была бы воспринята как приме- нение функции под названием asin к аргументу alpha. Поставив между двумя последовательно записанными алголь- ными фразами точку с занятой, мы сообщили, что нужно выпол- нить сначала первое из этих вычислений, а затем второе. Дело в том, что алгол предусматривает последовательное управление, при котором действия выполняются подряд слева направо, а все пере- дачи управления с нарушением линейного порядка действий долж- ны специально оговариваться. Рассмотрим теперь пример вычисления значения i/=max (и, v). В математическом тексте мы написали бы {и, если и > v, V, если и <1 v. На алголе эти вычисления можно записать так: if u>uthen t/:=uelse yt=v Здесь используются служебные слова if (если), then (то) и else (иначе). Эта запись на алголе буквально означает следующее: «Если t£>v, то у присвоить значение и, в противном случае у присвоить значение V». В принципе, можно было бы пользоваться русской версией ал- гола, в которой вместо английских служебных слов употреблялись бы соответствующие русские слова, но это затруднило бы обмен алгоритмами на алголе с зарубежными учеными и вычислительными центрами, а также ввод в. машину. В настоящее время имеется об- ширный международный фонд алгоритмов на алголе, опубликован- ных в книгах, научных журналах или размноженных другими спо- собами. Одно из важных преимуществ языка алгол состоит именно в том, что он широко используется в разных странах в одном и том же варианте, так что отпадает необходимость дополнительных уси- лий по переводу алгоритмов с одной национальной версии алгола на другую. Служебные слова нужно выделять специальным образом, что- бы отличать их от тех слов, которые вводятся авторами алгольных
S.4] НЕФОРМАЛЬНОЕ РАССМОТРЕНИЕ ФРАГМЕНТОВ ПРОГРАММ 65 алгоритмов для обозначения переменных и функций. Для выделе* ния служебных слов в печатных изданиях принято использовать полужирный шрифт. В рукописном тексте служебные слова, под* черкиваются. Рассмотрим еще один пример. Имеется последовательность из п чисел Xi, х2,... ,хп; нужно вычислить их сумму и присвоить полу- ченное значение переменной S. В математике существует стандарт- ная форма записи такого вычисления в виде п S = xi- Однако алгол не предусматривает такой операции суммирования п чисел. Чтобы описать это вычисление средствами алгола, нужно установить четкий порядок выполнения арифметических действий. Мы можем решить, что сначала присвоим переменной S значение О, а потом будем по очереди прибавлять к значению S значения х2, х2, .... хп, получая последовательно 0,0+xi,0+xi-|-xa и т. д. На алголе это записывается так: S:=0; for t: = l step 1 until n do S:=S+x(i] Знак ; разделяет два предложения, являющиеся операторами ал- гола. Во втором из них употреблены четыре служебных слова for (для), step (шаг), until (до) и do (выполнить). Слово for служит для задания циклического вычисления. Управление циклом осущест- вляется в данном случае с помощью переменной i, которая прини- мает последовательно значения от 1 до гас шагом 1. В алголе эле- менты последовательностей вместо х< обозначаются х [il. Квадрат- ными скобками выделен индекс i. Обычно в математических текстах квадратные скобки применяются, как и круглые, для выделения подвыражений в формулах. Язык алгол более скуп в использовании изобразительных средств и проводит четкое разграничение между круглыми скобками и квадратными, которые соответствуют записи индексов внизу строки. Рассматриваемый Текст на алголе подразумевает, что х пред- ставляет собой набор значений и нас интересует i-e из них. (При i=l это будет первое значение из набора х, т. е. хъ при 1=2 возь- мется ха, при i=3 будет взято х3 и т. д.) Если нужно просуммировать 10 первых квадратов натуральных чисел и присвоить переменной у полученное значение, то вместо формулы ю </= 2 k=\ мы пишем на алголе: ^•==0; for Л: = 1 step 1 until 10 do y:—y+k\ 2
S6 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Этот текст также состоит из двух предложений, разделенных точ- кой с запятой. Запись k f 2 означает возведение значения k в квад- рат. В алгольном тексте вслед за знаком f, обозначающим дейст- вие возведения в степень, пишется показатель степени, который при необходимости может быть заключен в круглые скобки. Напри- мер, запись х f (a+b) означает ха+ь, а запись х f a-\-b означает х“4-&. Рассмотрим еще один пример. Пусть имеется последовательность вещественных чисел, назовем ее у. Нужно найти произведение квад- ратов четных членов этой последовательности. Допустим, что по- следовательность очень велика и нужно прекратить перемножение, не дожидаясь исчерпания последовательности, в тот момент, когда натуральный логарифм произведения станет больше числа 10 (т. е. когда само произведение превысит е10). Рассмотрим следующий алгольный текст, который предлагается в качестве алгоритма ре- шения этой задачи. S: = l; i:=0; for i:=i-|-2 while Zn(S)^10 do S:=Sxt/[i]t2 Этот текст состоит из трех операторов. Последним является опе- ратор цикла с использованием служебного слова while (пока); In является обозначением стандартной функции вычисления натураль- ного логарифма. Произведение накапливается в переменной 3. Сначала 3 при- сваивается значение 1, а затем 3 последовательно умножается на очередные квадраты. Переменная i каждый раз содержит номер очередного элемента последовательности, на квадрат которого мы множим значение 3 в текущий момент. Перед каждым очередным перемножением проверяется верность условия /n(S)^10. В пер- вый же раз, когда это условие оказывается неверным, т. е. когда /n(S)>10, прекращается выполнение оператора цикла. Все приведенные выше фразы языка алгол содержат указания о требуемых вычислениях и состоят из операторов. Их можно срав- нить с повелительными предложениями естественного языка. В рас- сматриваемом примере сначала написаны два оператора присваи- вания, затем следует один большой оператор циклического дейст- вия, называемый оператором цикла. Внутри него есть еще один оператор присваивания S:=SX#li]f2 . выполняемый циклически. Оператор присваивания обеспечивает вычисление некоторого вначения и присваивание этого значения переменной, указанной в левой части. Для этого в правой части оператора присваивания при- ведена формула, по которой нужно вычислить значение. В алголе такие формулы называются выражениями. В простейшем случае
2.51 ОПЕРАНДЫ БТ выражение может быть константой, как в двух первых операторах присваивания из нашего примера. В последнем операторе присваи- вания арифметическое выражение представляет собой более слож- ную формулу. Выражения встречаются и в других операторах из этого примера. Конструкция t:=H-2 после символа for не является оператором присваивания, а представляет собой часть заголовка цикла. Согласно этому заголовку, при каждом новом выполнении цикла переменной I присваивается значение выражения i+2, т. е. она увеличивается на 2. Логическое выражение In (S)^10 указы- вает, при каком условии нужно продолжать цикл. Выполнение цик- ла прекратится, как только это условие окажется невыполненным. Заметим^ что хотя нужны определенные навыки для того, чтобы правильно писать на алголе, но читать и правильно понимать ал- гольный текст может без специальной подготовки человек, знако- мый с обычной математической символикой. 2.5. Операнды Величины, которые встречаются в выражениях, называются операндами. Подобно тому, как объектами машины Тьюринга являются зна- ки, записываемые в ячейках ленты, а объектами нормального алго- ритма служат слова и их части, алгоритмы на алголе имеют дело с операндами, принимающими числовое или логическое значения. Для вычисления новых значений используются выражения, кото- рые строятся из знаков операций, скобок и операндов. Операнда- ми задаются те значения, над которыми могут выполняться опера- ции. Например, в выражении SXt/hl f 2 операндами являются S, и 2. 2.5.1. Классификация операндов. Одним из основных понятий алгола является понятие типа, определяющего свойства операндов. Предусмотрены следующие три типа значений, над которыми можно производить действия: — вещественный, — целый, — логический. Вещественный тип используется в тех случаях, когда допус- каются любые (дробные и целые) числовые значения. Заметим, что при счете на вычислительной машине значения ве- щественного типа могут округляться, а это приводит к тому, что ре- зультаты получаются не абсолютно точными. Целый тип используется при работе с целочисленными значени- ями. Как мы увидим в дальнейшем, при выполнении вычислений
58 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 могут потребоваться специальные действия по преобразованию зна- чений вещественного типа в целый или наоборот. Логический тип используется, если соответствующий операнд должен принимать не числовые, а логические значения true или false. В формулах операнды каждого типа могут встречаться в сле- дующих четырех различных видах: 1) Постоянное одиночное значение. Напри- мер, целое число 0, вещественное число 10.2, логические значения true и false. В примерё алгольного текста S:=l; к=0; for i:=i+2 while In (S)^10 do S:=SXt/[Z]f2 этот вид представлен целыми числами 1, 0, 2, 10. 2) Простая переменная (т. е. переменное одиночное значение). Это переменная, которая может принимать различные значения по ходу выполнения алгоритма. Например, переменная S сначала получает значение 1, а потом последовательно домножает- ся, каждый раз меняя свое значение. 3) Переменная с индексами (т. е. элемент массива). В алголе разрешено определять массивы, т. е. последовательности значений, и работать с элементами этих массивов. В нашем примере y[i\ — это элемент массива у. 4) Вычисляемое значение. Примером вычисляемо- го значения является значение функции Zn(S), которое вычисляет- ся заново при каждом употреблении его в формуле (в отличие от переменных значений S, i, которые вычисляются заново только а соответствующих операторах присваивания или цикла). Заметим, что третий вид подразумевает, что значения элементов массива изменяются. Однако массив с постоянными значениями эле- ментов не выделяется в языке алгол в специальный вид, а рассмат- ривается как частный случай того же третьего вида. Таким образом, каждый операнд может относиться к одному из- трех типов и к одному из четырех видов, т. е. всего возможны 3x4= «=12 вариантов. Тип операнда характеризует принимаемые им зна- чения, а вид операнда характеризует способ организации работы с этим операндом в языке алгол. В частности, вид позволяет судить, - является ли операнд одиночным, или же он должен рассматриваться как элемент упорядоченного массива значений. Кроме того, вид операнда определяет, может ли значение операнда меняться в про- цессе выполнения алгоритма и если да, то нужны ли для этого- специальные операторы перевычисления (при втором и третьем видах), или же операнд вычисляется по ходу дела, при каждом вхождении в формулу (четвертый вид). Возможные операнды арифметических выражений описываются следующей металингвистической формулой:
2.5] ОПЕРАНДЫ 59 (арифметический операнд) ::= (число без знака) ] (простая пе- ременная) | (переменная с индексами) | (функция) | ((ариф- метическое выражение)) Здесь первые четыре альтернативы соответствуют перечисленным выше четырем видам. Последняя альтернатива означает, что опе- рандом может служить арифметическое выражение в скобках. Она не добавляет новых видов и введена формально, чтобы удобно было строить сложные выражения, отвлекаясь от существа тех «кирпи- чей», из которых они составляются. Говоря, что операнд представ- ляет собой арифметическое выражение, мы подразумеваем, что, вычислив данное выражение, мы получим одно значение, которое будет затем использоваться при вычислении. 2.5.2. Числа. На языке алгол можно писать обычные десятичные числа, при- чем вместо десятичной запятой употребляется точка. Впрочем, де- сятичная точка не является особенностью алгола, а соответствует традиционной математической символике на западе. Интуитивное представление о форме написания чисел в алголе дают следующие примеры: О 177 .5374 (т. е. допускаются числа без целой части). Число может начинаться со знака — или +: —200.084 +177 В начале числа может быть один или несколько нулей: +0.731 —00801.304 .00073 Чтобы не писать много нулей в начале или в конце целого чис- ла, можно указывать десятичный порядок. Например, можно на- писать 1.2ю — 4 вместо записи 0.00012 или 73.03ю8 вместо записи 7303000000 Для сокращения разрешается писать один порядок, например, 103 вместо записи 1юЗ, что означает 1000. Аналогично ю—4 озна- чает то же, что и 0.0001, а —ю—2 означает —0.01 Мы имеем теперь некоторое интуитивное представление о поня- тии числа в алголе. Однако это понятие еще не вполне определи- лось. В частности, из сказанного выше не ясно, можно ли писать точку в конце числа: 101. ? Чтобы исчерпывающе определить понятие числа, рассмотрим соответствующие металингвистические формулы. Для лучшей обо- зримости будет введено шесть промежуточных понятий. а) (целое без знака) ::= (цифра) | (целое без знака) (цифра). Согласно этой формуле, целое без знака — это последователь- ность цифр. Примерами могут служить
60 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 1 23 0157 б) (правильная дробь) :: = .(целое без знака) Примеры правильной дроби: .17 .0031 в) (десятичное число):: = (целое без знака) | (правильная дробь) | (целое без знака) (правильная дробь) Примеры десятичного числа: 1 0 173 .1 .03 13.771 01.10 Десятичное число — это промежуточное понятие; в отличие от произвольного числа, оно не может содержать порядка и знака. г) (целое) ::= (целое без знака) | + (целое без знака) | — (целое без знака) Примеры целого: 1 +1 23 —23 д) (порядок) ::=10(целое) Примеры порядка : 1в01 10+3 щ—371 е) (число без знака) ::= (десятичное число) | (порядок) | (деся- тичное число) (порядок) Примеры числа без знака: 177 10—4 9.34]08 Заметим, что порядок числа без знака может содержать знак. Теперь мы окончательно определим понятие числа: (число) ::=(число без знака) | +(число без знака) | — (число без знака) Из этого определения, в частности, следует, что нельзя писать на алголе такие числа: 101. 12.10—2 2ю3.1 Например комбинацию 101. никак нельзя вывести из наших ме- талингвистических формул, потому что точка может попасть в кон- струкцию числа только через конструкцию правильной дроби, а там после точки обязательно должна быть хотя бы одна цифра. Комбинация 2103.1 не годится потому, что после знака ю должно быть только целое число, представляющее порядок. Итак, мы оп- рёделили синтаксис чисел в алголе. Семантика понятия числа состоит в следующем. Числа служат для изображения постоянных арифметических значений. В соответствии с классификацией типов, рассмотренной в разделе 2.5.1, «целые» числа представляют значения целого типа; все осталь- ные числа в алголе изображают вещественные значения. Например, число ю5 имеет не целый, а вещественный тип, тогда как числу 100000 соответствует целый тип. Соответственно этому, при машин- ной реализации алгола число 100000 будет обязательно представ- лено точно, тогда как ю5 может быть представлено приближенно.
2.5J ОПЕРАНДЫ < 2.5.3. Переменные. В алголе переменная — это одиночное переменное значение или элемент массива. Изображение переменной не зависит от типа зна- чений и не показывает его. Переменные (и некоторые другие син- таксические единицы) изображаются с помощью идентификаторов. Вместо термина «[идентификатор» можно было бы употреблять <имя» или «название». Идентификатор — это то, что идентифицирует, т. е. выделяет объект из других.сходных объектов. Синтаксически идентификатор представляет собой про- извольную последовательность букв и цифр, начинающуюся с буквы: (идентификатор) ::=(буква) | (идентификатор) (буква) | (иденти- фикатор) (цифра) Примеры идентификаторов: q х ml if ВЕС sin МАША vlla Семантика. Идентификатор служит для идентификации объектов языка. Заметим, что для облегчения чтения текст на языке алгол может содержать между символами пробелы, которые не влияют на вы- полнение алгоритма. Поэтому можно написать идентификатор из нескольких слов, например, случайное число ШАГ по х Переменные подразделяются на простые переменные и перемен- ные с индексами: (переменная) ::== (простая переменная) | (переменная с индексами) Простые переменные служат для идентификации одиночных зна- чений, а переменные с индексами для идентификации элементов массивов переменных. Они различаются и синтаксически. Простая переменная описывается формулой: (простая переменная) ::= (идентификатор) Другими словами, любой идентификатор может быть использо- ван для обозначения простой переменной. (Как правило, разные объекты должны обозначаться разными идентификаторами.) Переменные с индексами используются в тех случаях, когда не- обходимо выполнить некую циклическую работу, проводя однооб- разные вычисления с наборами однотипных величин. Например, если нужно вычислить скалярное произведение двух векторов, в каждом из которых 6 координат, то можно было бы обозначить все эти координаты разными буквами и записать оператор присваи- вания с использованием простых переменных: х:=а х g-H X Л-Н X H-d X/+е X Л+f X / Однако этот способ имеет два недостатка. Во-первых, он требует Долгой и утомительной работы по написанию (особенно, если ела-
«2 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 гаемых не 6, а, например, 60). Во-вторых, таким способом нельзя задать алгоритм вычисления скалярного произведения векторов произвольной длины. Поэтому необходимо иметь в алгоритмичес- ком языке возможность употребления последовательности значе- ний. В нашем случае удобно ввести в рассмотрение для каждого вектора массив его элементов, имеющий свое имя. Например, для первого вектора можно рассмотреть массив Лив нем величины (элементы) Als Л2, . . ., Лв, а для второго вектора — соответствен- но массив В ив нем величины Ви Вг.Вв. В алголе эти величи- ны выглядят так: Л'[1], А [21.........................Л[61, В[1 ], В[2], . . ., В[6]. Можно выделить в нашем вычислении общую часть, которая многократно выполняется. Это добавление к общей сумме очередного произве- дения i-x элементов двух векторов. Тогда наш алгоритм может быть построен из двух операторов: занесения нуля в качестве на- чального значения суммы х:=0; и очередного добавления произведения x:=x-Mli]XB[il причем второй оператор нужно выполнить в цикле при t=l,..., 6. В общем случае синтаксис переменной с индексами опи- сывается следующими формулами: (переменная с индексами) ::= (идентификатор) [(список индек- сов)] (список индексов) ::= (индекс) | (список индексов), (индекс) (индекс) ::=(арифметическое выражение) Примеры переменных с индексами: Q[7,2] ali] cli] d[i, j] temp[A+\,t f 2,01 В алголе массивы состоят из переменных значений, которые могут (но не обязаны) изменяться в процессе счета. Эти значения назы- ваются элементами массива. Для идентификации элемента употреб- ляется имя массива перед квадратными скобками и список индек- сов элемента внутри скобок. Программист сам выбирает способ ну- мерации элементов. Точнее говоря, элементы по каждому измерению нумеруются подряд, но номер первого элемента выбирается произ- вольно. В определенном месте алгол-программы должно быть опи- сание каждого используемого массива. Описание массива содержит идентификатор этого массива и информацию о границах изменения индексов элементов. Если в списке индексов только один индекс, то соответствующая структура даннйх представляет собой одно- мерный массив, т. е. перенумерованное множество значений. Например, если в описании указано all: 100], то массив а со- держит 100 элементов all], a[2], . . ., allOOl. Если же указано 2>[0: 99], то массив b содержит тоже 100 элементов, но они нуме- руются от нуля: МО], Ml], . . ., 6[99]. При указании с[—7, 82] мае-
2 5Г ОПЕРАНДЫ ' 6Э сив с состоит из 90 элементов d—7], с [—6], . . d—1], d0], dl), • • d82]. Иногда постановка задачи предполагает более сложную струк- туру данных в виде матрицы, где возможен перебор в различных направлениях (по строкам или столбцам). Чтобы было удобно ре- шать такие задачи более сложного перебора, в алголе разрешено наряду с одномерными массивами пользоваться и многомерными (размерности 2,3 и т. д.). Например, если описание массива содер- жит указание dll : 10, 1 : 10], то в этом массиве содержатся эле- менты dll,1], dll,2], ..., dll,10], d[2,l], ...,d[2,10], ..., dllO.l], ... . . ., dllO, 10] и мы имеем дело с матрицей, в которой 10 строк и 101 столбцов. Задавая переменную dli, /], мы тем самым обращаемся к. элементу матрицы d на пересечении i-й строки и /-го столбца. Можно описать прямоугольную матрицу, например dl : 2, 1 : 50] или'/[1 : 2, —3 : 46]. В обеих этих матрицах по две строки и по пятьдесят столбцов, но столбцы нумеруются в них по-разному (от 1 или от —3). При указании §10 : 1, 1:2, 1 : 25] трехмерный массив g содержит тоже 100 элементов и состоит из двух матриц, (с номерами 0 и 1), в каждой из которых по две строки и по 25 столб- цов. Элементы массива g будут идентифицироваться тройками ин- дексов, т. е. арифметических выражений. Например, §11,1,17] — это элемент № 17 строки № 1 матрицы № 1 из массива g. (Числа являются частным случаем арифметических выражений и поэтому могут употребляться в качестве индексов.) Семантика. Переменные с индексами служат для обозна- чения элементов массивов переменных значений. Идентификатор' такой переменной обозначает массив, а индексы служат для иден- тификации элементов внутри массива. Число индексов должно со- ответствовать размерности массива, а значения индексов должны быть заключены между левой и правой границами индекса из соот- ветствующего описания (и могут равняться этим границам). Значение индекса может получаться не целым. В таком случае выбирается ближайшее к нему целое число. Точнее говоря, вычис- ляется целая часть от / +0.5, где / — вычисленное значение ариф- метического выражения, указанного в качестве индекса. Например, при i=2.5 переменная xli] обозначает элемент х[3]. 2.5.4. Операнды-функции. В выражение могут входить функции, которые представляют собой вычисляемые операнды. Примерами функций могут служить sin(x) cos(a+b\2) f(x, г/+1) Здесь sin, cos, f — идентификаторы функций; содержимое скобок, т. е. х, a+b f 2 и х, §+1 — это списки параметров, называемых фак- тическими параметрами. Для каждой функции должен быть задан способ ее вычисления. Для этого служат описания процедур, которые снабжаются теми же
64 ' АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл.» идентификаторами и включаются в ту же программу на алголе. Когда в процессе выполнения программы встречается операнд- функция, нужно найти описание процедуры с тем же именем и вы- полнить вычисления в соответствии с этим описанием, используя значения заданных фактических параметров. Можно многократно ссылаться в программе на одно и то же описание процедуры. В прин- ципе, можно было бы обойтись без функций, если всякий раз запи- сывать в программе нужную последовательность действий, однако применение функций обычно позволяет существенно сократить текст программы и сэкономить усилия, затрачиваемые на ее изго- товление. Не для всякой функции необходимо задавать алгоритм, описы- вая процедуру ее вычисления. Имеется общая договоренность о некоторых стандартных функциях, которым соответствуют стан- дартные идентификаторы abs, sqrt, sin, cos, arctg, In, exp, sign К этому обязательному списку обычно добавляют еще функции entier, tg, cig, arcsin, arccos и некоторые другие. Функция abs(x) — абсолютное значение (модуль) величины х, sqrt(x) — квадратный корень из х, 1п(х) — натуральный лога- рифм значения х, ехр(х)=ех при основании е=2,71828 . . ., 1 при х > О, sign(x) =< О при х = 0, —1 при х < 0. Параметры этих стандартных функций могут быть целого или вещественного типа. Для функции sqrt(x) параметр х должен удов- летворять отношению х^О, а для 1п(х) — отношению х>0. Для функций arcsin(x) и arccos(x) нужно, чтрбы выполнялось отношение |х|<1. Для остальных функций допускаются любые значения пара- метра. Функция sign(x) принимает значения целого типа. Остальные перечисленные стандартные функции дают значения вещественного типа. Синтаксис функций задается следующими формулами: (функция) ::= (идентификатор) (совокупность фактических па- раметров) (совокупность фактических параметров) ::= (пусто) | ((список фактических параметров)) (пусто) ::= (1) (список фактических параметров) ::= (фактический параметр)!
2.6] АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 65 (список фактических параметров) (ограничитель параметра)- (фактический параметр) (ограничитель параметра) ::=.|) (строка букв) :( (2) (фактический параметр) ::= (идентификатор) | (выражение) | (строка) (строка букв) (буква) | (строка букв) (буква) Таким образом, примерами функций являются ctg(a+b) Случайное число Скалпр(А, В) Нам потребовалось ввести, помимо понятия списка, понятие совокупности параметров, так как возможны функции без парамет- ров, например, очередное случайное число. Отсутствие парамет- ров обозначается в этом синтаксисе термином (пусто), который определяется формулой (1), не содержащей никаких знаков справа от знака Если нет параметров, то не нужно и скобок после идентификатора. Встретив оператор У‘ =f +1 мы ищем описание для f и, если оказывается, что это описание про- цедуры, то выполняем вычисления по этому описанию, берем резуль- тат, прибавляем к нему единицу и присваиваем значение получен- ной суммы переменной у. Согласно металингвистической формуле (2), разрешено отделять параметры в списке не только запятой, но и конструкцией, в кото- рой при помощи слов для наглядности поясняется, что означает очередной параметр. Таким образом, например, конструкция ) температура' ( эквивалентна запятой, и поэтому можно функцию F{x, у, t, р) записать в таком виде - F(x, у) температура: (0 давление: (р) Эти две записи совершенно эквивалентны, причем закрывающая скобка после у не завершает список параметров. Такие конструк- ции между параметрами не влияют на выполнение алгоритма и употребляются только для повышения наглядности программы. Строка — это любая комбинация символов из алфавита алго- ла, заключенная в кавычки. Например, для функции код (,/’) па- раметром является строка В наших металингвистических формулах осталось одно еще не определенное формально понятие (выражение) (см. предпослед- нюю формулу). Это понятие будет определено позднее, в разд. 2.7.3. 2.6. Арифметические и логические выражения 2.6.1. Простые арифметические выражения. Вычисления над операндами задаются с помощью выражений. Операнды объединяются в выражения при помощи знаков операций и скобок. Простейший случай выражения — это один операнд, ®- 3. Люб имений и др.
66 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 - например: а 10 ml xti+11 sin (a+b) (a—x 12+3) Последнее из этих выражений-операндов получено взятием некото- рого выражения в скобки. В арифметических выражениях употребляются следующие опе- рации (знаки операций): (операция) ::=+ |—|X|/|-i-| f Чтобы облегчить поэтапное понимание семантики арифметичес- ких выражений, авторы алгола разделили операции на ранги в - соответствии с общепринятыми правилами: (операция 1-го ранга) ::= f (операция 2-го ранга) ::=Х |/| 4- (операция 3-го ранга) ::=+ | — Выражения строятся по рангам нарастающей сложности: (арифметическое выражение 0-го ранга) ::=(арифметический операнд) (арифметическое выражение 1-го ранга) ::=(арифметическое выражение 0-го ранга) | (арифметическое выражение 1-го ранга) (операция 1-го ранга) (арифметическое выражение 0-го ранга) Понятие (арифметический операнд) определено металингви- стической формулой в разд. 2.5.1. Примеры арифметических выражений 1-го ранга: а 3.0 (c+d+e) xt«/t 12 f(x)txf0.2 2f«/[i] Первые три примера — это операнды, т. е. выражения 0-го ранга, а следовательно и выражения 1-го ранга. (Напомним, что любое арифметическое выражение в круглых скобках — операнд.) Вооб- ще же, арифметическое выражение 1-го ранга — это набор операн- дов, задающий последовательные возведения в степень. Эти возве- дения производятся слева направо. В частности, a f b f с=(аь)е, а не аьС. Если мы хотим записать на алголе аьС, то изображаем это так: a f (b | с). В алголе всегда все операции одного ранга выполняются строго слева направо. Арифметические выражения 2-го ранга определяются так: (арифметическое выражение 2-го ранга) ::= (арифметическое выражение 1-го ранга) | (арифметическое выражение 2-го ранга) (операция 2-го ранга) (арифметическое выражение 1-го ранга) Например, выражениями 2-го ранга являются а 3.0 xf у\ 12 ах2 ахЗ/с
2.61 АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 67 При этом, поскольку операции одного ранга выполняются слева направо, то а1Ь1С^с Из выражений 2-го ранга строятся арифметические выражения 3-го ранга: (арифметическое выражение 3-го ранга): := (арифметическое вы- ражение 2-го ранга) | (арифметическое выражение 3-го ранга) (операция 3-го ранга) (арифметическое выражение 2-го ранга) | (операция 3-го ранга) (арифметическое выра- жение 2-го ранга) Таким образом, арифметическое выражение 3-го ранга состоит из арифметических выражений 2-го ранга, разделенных операция- ми 3-го ранга, причем перед первым (или единственным) выраже- нием 2-го ранга может стоять знак + или —. Примеры арифметических выражений 3-го ранга: —3 а+Ь —(а-Н>)Хс Сложная конструкция такого определения арифметических вы- ражений оправдана тем, что последовательно разбирая синтаксис выражений различных рангов, начиная с нулевого, мы тем самым устанавливаем порядок выполнения действий. В частности, сначала вычисляются все выражения 0-го ранга (операнды), затем выраже- ния 1-го ранга, потом 2-го ранга и, наконец, 3-го ранга. Например, рассматривая текст и—y+omega X sum f cos (y+z X 3)/7.310—2 f Ы»4-2.8] f (a—3/y+ vu f 8) мы сначала выделяем в нем операнды и у omega sum cos(y+z*3) 7.3i0—2 tt»[i+2.8] (a—3/y+vu f 8) затем выделяем арифметические выражения 1-го ранга и у omega sum\cos(y-\-z*3) 7.310—2 f w[i+2.8] f (a—3/y+ vu f 8) потом арифметические выражения 2-го ранга и у omega*, sum ] cos (у+г*3)/7.31в—2 f ш[г-|-2.81 f (a—3/y+ vu f 8) и, наконец, приходим к выводу, что исходный текст является ариф- метическим выражением 3-го ранга, так как состоит из выражений 2-го ранга, разделенных операциями 3-го ранга. Аналогично можно разобрать арифметические выражения, со- держащиеся в круглых и квадратных скобках. Теперь определим понятие простого арифметического выраже- «ия, объединяющее все структуры выражений, рассмотренные в этом разделе: (простое арифметическое выражение): := (арифметическое вы- ражение 3-го ранга) 3*
68 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Семантика. Простое арифметическое выражение служит для вычисления значений (целых и вещественных). При этом сна- чала вычисляются значения всех операндов (слева направо), затем выполняются все операции 1-го ранга (слева направо) и т. д. Если - операнд — это заключенное в скобки выражение, то оно вычисля- ется аналогичным способом. Тип выражения (целый или вещественный) определяется типом операндов и операциями, которые над ними выполняются. Тип опе- ранда определяется либо его написанием (например, константа 9 имеет целый тип, а константы 11.7 или 9.0 имеют вещественный тип), либо его описанием. Так, тип выражений а+b, а—b, aXb зависит от того, какие типы указаны в описаниях переменных а и Ь. Если оба типа целые, то выражение тоже имеет целый тип. Если же хотя бы один из них вещественный, то тип всего выражения вещест- венный. Выражение alb всегда имеет вещественный тип, независи- мо от того, какие типы имеют операнды а и Ь. Операция 4- опреде- лена только для того случая, когда оба операнда имеют целый тип и дает результат тоже целого типа. Операция | возведения в степень характеризуется следующей зависимостью типа результата от типов операндов. Если значение b имеет целый тип, причем К>0, то a f b имеет тот же тип, что и а. В остальных случаях a f b имеет вещественный тип. Простые арифметические выражения употребляются в операто- рах присваивания, в индексных выражениях, в качестве фактиче- ских параметров и в других местах алгольного текста. 2.6.2. Простые логические выражения. Часто бывает нужно организовать выбор нужных формул в за- висимости от тех или иных условий. Например, для В граммов воды при температуре t объем V опре- деляется следующим образом: ' B/dj при t О, V = B/dt при t > 100, B/dw в остальных случаях. Здесь dt — удельный вес льда, dw — удельный вес пара, ds — удельный вес (жидкой) воды. Условия /^0 и £>100 представляют собой высказывания, о ко- торых можно утверждать, что они истинны или ложны (в зависи- мости от значения переменной t на данном этапе вычислений). На алголе вычисление объема воды можно записать так: V:=if fcCO then B/di else if £>100 then B/ds else B/dw Такая запись указывает, какие условия нужно проверять и что нужно делать в зависимости от их выполнения. Между if («если») и then («то») заключаются логические выражения, которые при-
- 2.6) АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 69 нимают значения true («истина») или false («ложь»). Обычно их опе- рандами являются отношения. Два простых арифметических выражения, соединенные знаком операции отношения, образуют отношение, которое принимает зна- чение true или false. (отношение) ::= (простое арифметическое выражение) (знак операции отношения) (простое арифметическое выражение) (знак операции отношения) : :=<|^|=|^|>|:т^ В рассмотренном нами примере логические выражения /^0 и />100 представляли собой отношения. Другим примером отноше- ния является выражение а>Ь9 которое принимает значение, true, если значение а больше значения Ь, и значение false, если а^Ь. Значение отношения определяется по значениям простых арифме- тически^^выражений, записанных слева и справа от знака опера- ции отношения. Эти выражения вычисляются, а затем проверяется выполнение отношения между их значениями. Например, если для отношения а+1 —sin(alpha) левая часть принимает значение 1, а правая часть равна нулю, то проверяется отношение 1^0, и его значение false рассматривается как значение отношения а+1^—sin(alpha). Условия для выбора варианта счета не всегда определяются од- ним отношением. Отношения могут комбинироваться при помощи знаков логических операций в логические выражения, подобно тому как арифметические выражения комбинируются из операндов и знаков арифметических операций. Всего в языке алгол разрешается использовать пять логических операций, каждая из которых имеет свой ранг старшинства. Самой старшей является операция отрицания «не» со знаком —i. Далее в порядке убывания старшинства следует конъюнкция (со знаком Д), дизъюнкция (со знаком V), операция следования (со знаком о) и операция эквивалентности (со знаком =). Операция «не» приме- няется к одному логическому операнду, а остальные логические операции выполняются над двумя операндами. Каждая логическая операция вырабатывает результат со значением true или false По соответствующему ей правилу в зависимости от значений (true или false) ее операндов. Обозначим первый (или единственный) операнд через 4, а второй операнд через В. Тогда правила выполнения ло- гических операций можно задать таблицей 2.2. Операция —14 читается «не А». Операции А/\В, А\/В, AzdB и А=В читаются соответственно «4 и В», «4 или В», «из 4 следует В», «4 эквивалентно В». Операция “14 вырабатывает значение, противоположное зна- чению своего операнда. Например, логическое выражение “if<0
70 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Таблица 2.2 Значения операндов Результаты операции А в “М A A В A V В А В A = В true false true true false true true true true true true false false true false false false true False true true false false false false false true true будет истинным, когда конкретное значение t есть неотрицательное число. Операция А/\В вырабатывает значение true только в том слу- чае, если и Л, и В имеют значение true. Например, логическое выра- жение х=0А*+у>1 будет истинным, если х=0 и #>1. Операция A\JВ вырабатывает значение true, если или А или В (хотя бы один из этих операндов) имеет значение true. Например, логическое выражение x=0Vf/=0 будет истинным, если хотя бы од- но из значений х и у (или оба они) равно нулю. Операция Лг)В вырабатывает значение true, если верно хотя бы одно из двух условий: В имеет значение true или А имеет значение false. Например, логическое выражение x>lot/<5 истинно, если у<5 или если х^1. Наконец, операция Л=В вырабатывает значение true, если А и В имеют одинаковые значения (оба true или оба false). Например, логическое выражение x>l=z/<5 истинно, если значения х и у удовлетворяют одному из условий: а) х>1 и у<5; б) х^1 и f/^5. В любом другом случае (например, при х—2 и г/=6) это выражение будет ложным. Кроме отношений в логическое выражение могут входить в ка- честве операндов постоянные значения true или false (как числа в арифметических выражениях) и переменные, имеющие логические значения. (Например, в результате выполнения оператора р\=а>Ь переменной р присваивается логическое значение, которое может быть использовано в логическом выражении рA#>0Vq-) Операн- дами логических выражений могут быть также функции, принимаю- щие логические значения, и логические выражения, заключенные в скобки. Итак:
2.61 АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 71 (логический операнд) : := (отношение) | (логическое постоян- ное значение) | (переменная) | (функция) | ((логическое выражение)) (логическое постоянное значение): :=true | false Логические переменные и функции имеют такой же вид, как арифметические, но принимают другие (логические) значения. Аналогично синтаксису арифметических выражений, синтаксис логических выражений описывается металингвистическими форму- лами, в которых вводятся ранги операций и выражений: (логическая операция 1-го ранга): :=~“1 (логическая операция 2-го ранга): : = Л (логическая операция 3-го ранга): : = V (логическая операция 4-го ранга): :==z) (логическая операция 5-го ранга): :=н= (логическое выражение 0-го ранга) : := (логический операнд) (логическое выражение 1-го ранга) : := (логическое выражение 0-го ранга) ((логическая операция 1-го ранга) (логическое выражение 0-го ранга) Например, а>Ь — логическое выражение 0-го ранга, поэтому —\а>Ь — логическое выражение 1-го ранга. Однако —v~}a>b не является логическим выражением 1-го ранга (и вообще это не логи- ческое выражение), так как знак —i можно ставить только перед выражением 0-го ранга, но не 1-го ранга. Если же заключить выра- жение в скобки, то i(~irC>b) становится логическим выраже- нием 1-го ранга, так как любое логическое выражение в скобках — это операнд, а следовательно, логическое выражение 0-го ранга. Остальные синтаксические формулы: (логическое выражение 2-го ранга) ::= (логическое выраже- ние 1-го ранга) | (логическое выражение 2-го ранга) (ло- гическая операция 2-го ранга) (логическое выражение 1-го ранга*) (логическое выражение 3-го ранга) ::= (логическое выраже- ние 2-го ранга) | (логическое выражение 3-го ранга) (ло- гическая операция 3-го ранга) (логическое выражение 2-го ранга) (логическое выражение 4-го ранга) : := (логическое выраже- ние 3-го ранга) | (логическое выражение 4-го ранга) (ло- гическая операция 4-го ранга) (логическое выражение 3-го ранга) (логическое выражение 5-го ранга) : := (логическое выражение 4-го ранга) ((логическое выражение 5-го ранга) (логическая операция 5-го ранга) (логическое выражение 4-го ранга) (простое логическое выражение) ::= (логическое выражение 5-го ранга)
72 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Заметим, что формула для логического выражения 1-го ранга имеет особенность: в ней употребляется не двуместная, а одномест- ная (т. е. применяемая к одному операнду) операция. Аналогичную особенность имела формула для арифметического выражения 3-го ранга, поскольку в ней плюс и минус могли употребляться как одноместные операции. Однако в синтаксисе арифметических выра- жений эта формула с особенностью была не в начале, а в конце, т. е. допускался знак плюс или минус перед арифметическим выра- жением 2-го ранга. Для логических же выражений особенность воз- никает в начале синтаксиса; поэтому знак отрицания может стоять перед любым операндом. Например, если мы хотим образовать отри- цание логического выражения 2-го ранга, а/\Ь/\с, то нельзя поста- вить знак отрицания перед этим выражением, так как тогда отрица- ние будет отнесено к а, поскольку ранг операции выше всех. Од- нако можно превратить это выражение в операнд, взяв его в скобки, после чего допустимо отрицание: —у(а/\Ь/\с). Примеры простых логических выраже- ний: х——2 — отношение: два простых арифметических выражения, соединенные знаком отношения; y>v\/z>q — два отношения соединены операцией логического сложения V; а-\-Ь>—5ДгДс|2>3— логическое выражение 2-го ранга с тремя операндами, из которых два — отношения и один — про- стая переменная г; §г=—шДбД—\c\/d\J— в этом логическом выражении все -переменные должны иметь логические значения; х:=г<СО — это оператор присваивания, который вычисляет зна- чение логического выражения z<0 и присваивает это значение пе- ременной х логического типа. Семантика. Простое логическое выражение служит для задания правил вычисления логического значения. Для вычисления простого логического выражения сначала определяются значения всех логических операндов, потом выполняются слева направо все операции 0-го ранга, затем слева направо все операции 1-го ранга и так далее. Получаемое в результате значение называется значением простого логического выражения. Простые логические выражения употребляются в операторах присваивания, в условиях, а также в качестве фактических параметров. 2.6.3. Условные выражения. Условные выражения позволяют осуществлять выбор одной из нескольких формул счета. Например, условное выражение в правой части оператора присваивания x:=if a>b then а—b else a+b
2.61 АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 73 ' обеспечивает вычисление значения переменной х либо по формуле а—Ь, либо по формуле а+b, в зависимости от логического значе- ния отношения а>Ь. Другие примеры условных выражений: a) if c^d/\x then a f 2 else 0.1 (Здесь x — логическая переменная.) б) if а>1 then х\/у else true Это условное выражение является логическим. Оно может быть использовано в условии другого выражения, например: в) if if й>1 then х\/у else true then 0 else 1 Итак, условные выражения образуются при помощи служебных слов if (если), then (то), else (иначе). Любое условное выражение начинается с условия, которое определяется следующей формулой: (условие) : :=if (логическое выражение) then Примеры условий: if a>b then if true then if xVa>l then if if x then у else z then Синтаксис произвольных (и в частности, условных) ариф- метических и логических выражений задается следующими форму- лами: (арифметическое выражение) : := (простое арифметическое вы- ражение) | (условие) (простое арифметическое выраже- ние) else (арифметическое выражение) ’ (логическое выражение) : := (простое логическое выражение) | (условие) (простое логическое выражение) else (логиче- ское выражение) Согласно этим формулам, между then и else всегда должно быть простое (арифметическое или логическое) выражение, т. е. выраже- ние без условий. Точнее говоря, условия допускаются, но содер- жащие их условные выражения должны быть заключены в скобки. За словом else может следовать любое (т. е. простое или условное) выражение. Семантика. Вычисление выражения, содержащего усло- вие, сводится к вычислению одного из входящих в него простых вы- ражений следующим (рекурсивным) образом: Вычисляется значение логического выражения, содержащегося в условии. Если оно true (истина), то значением всего выражения является значение (простого) выражения, написанного между then и else. Если же это значение false, то значением всего выражения яв- ляется значение того выражения, которое написано после else. Иначе говоря, условное выражение разбивается на две части, и дело
74 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [IX 2 сводится к тому, что нужно вычислять первую или вторую часть. Если она оказывается простым выражением, то вычисляется по правилам, описанным в разд. 2.6.1 и 2.6.2. Если же это выражение тоже содержит условие, то оно опять разбивается на две части, и в зависимости от истинности условия выбирается одна из этих час- тей и т. д. Рассмотрим некоторые примеры условных выражений. a) if x<Zy then х else у Это арифметическое выражение, определяющее вычисление min (х, у). Оно содержит условие х<у (так мы сокращенно обозна- чаем условие if x<Zy then). Если х<у, то нужно взять значение х, а если х^>у, то нужно взять значение у. б) if а<0 then A/В else if fe=0 then В/A else z Здесь проверяется условие a<0. Если оно выполнено, то зна- чение всего выражения равно А/В. Если же о>0, то следует перейти к рассмотрению выражения после else, которое оказывается услов- ным. Аналогично рассматривается новое условие: если fe=0, то нужно вычислять В/А, если нет, то значение всего выражения рав- но г. в) if q then п—1 else п В этом примере условием является переменная у, которая может принимать значение true или false. Если она true, то значением все- го выражения является значение п—1, а если она false, то—зна- чение п. г) if k<l then s>w else h^c Это логическое выражение, принимающее значение одного из отношений s>w или h^c. Если &<1, берется значение отношения s>w, в противном случае берется значение отношения h^Zc. д) if if if a then b else c then d else f then g else h<k Это выражение также является логическим. Оно принимает значение логической переменной g или отношения h<Zk в зависи- мости от истинности условия (обозначим его через р), которое, в свою очередь, принимает значение логической переменной d или f в зависимости от истинности содержащегося в нем условия if a then feelsec. Последнее условие мы опишем логической формулой р1 = =aA&V"n^A^« Соответственно полное условие, проверяемое при вычислении данного выражения, описывается формулой р=р1А dV~iplAA Итак, значение выражения есть g, если (а/\Ь\/—]а/\с)/\ dV^aA^V—iaA^AA и h<Zk в противном случае. Например, если a=fe=d=true, то значение выражения есть g, так как полное проверяемое условие в этом случае истинно.
2.6] АРИФМЕТИЧЕСКИЕ И ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ 75 Не всегда по виду выражения можно с уверенностью заключить, является ли оно арифметическим или логическим. Например, в вы- ражении if a then с else d переменная а обязательно должна быть логической, а тип перемен- ных с и d не известен. Если они логические, то и все выражение ло- гическое; если они арифметические, то все выражение тоже явля- ется арифметическим. Однако переменные с. и d не могут быть раз- ных типов, поскольку рассматриваемое выражение должно соот- ветствовать либо формуле (условие) (простое арифметическое выражение else (ариф- метическое выражение) либо формуле (условие) (простое логическое выражение) else (логическое выражение) Приведем еще несколько примеров и продемонстрируем разно- образие выразительных средств языка, позволяющих варьировать способ описания алгоритма. Пусть нужно произвести вычисление | а+b при а>6 и c>d, x~\a—b в противном случае. Один из способов реализации этого вычисления основывается на том, что выбор в зависимости от условия можно производить не толь- ко между частными формулами, но и между целыми операторами: if a>b/\Od then x:=a+b else x:=a—b (Такие условные операторы мы будем рассматривать в разд. 2.8.) Приведем еще три способа того же вычисления с использованием условных выражений х: = if a>b/'\c>d then a+b else a—b x:=a+(if a>b/\c>d then b else—b) 1 x:= if a>b then(if c~>d then a+b else a—b) else a—b Последний способ записи внешне выглядит громоздким, но в случае а^Ь он обеспечит наиболее быстрое вычисление, так как при этом не потребуется проверять второе условие. (Заметим, что здесь условное выражение берется в круглые скобки и тем самым<стано- вится операндом.) Пусть требуется вычислить (а+хг при а>0, + при а^О. Иначе говоря, к значению а прибавляется в зависимости от знака числа а либо i-й, либо (i-H)-fl элемент массива х. Тогда возможны.
76 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 например, такие способы записи: у.= if а>0 then a+x[i] else a+x[i+l] z/:=a+(if aX) then x[i] else x[i+1 ]) y:=a+xtit aX) then i else i+ll z/:=a-i-x[t'+(if a>0 then 0 else 1)1 В последних двух примерах проверка условия осуществляется при вычислении самого индекса. 2.7. Основные операторы С участием выражений составляются алгольные операторы — повелительные фразы для исполнителя алгоритма. Эти операторы подразделяются на простые (называемые основными) и сложные, представляющие собой композиции простых. Предусмотрены четыре вида основных операторов. (основной оператор) : := (оператор присваивания) | (оператор перехода) | (оператор процедуры) | (пустой оператор) Понятие пустого оператора не имеет самостоятельного значе- ния; оно вводится для удобства описания более сложных синтакси- ческих конструкций, означая, что на том- месте, где согласно мета- лингвистической формуле должен находиться (оператор), может в частном случае ничего не быть. 2.7.1. Оператор присваивания. Операторы присваивания служат для присваивания значений переменным. Оператор присваивания состоит из двух частей, раз- деленных знаком присваиваниям (оператор присваивания) ::= (список левой части):= (ариф- метическое выражение) | (список левой части):= (логиче- ское выражение) В списке левой части указывается одна или несколько переменных, которым присваивается значение, а правая часть содержит арифмети- ческое или логическое выражение для вычисления этого значения. Ниже приводятся примеры операторов присваивания с одной пе- ременной в левой части d-.—aXb e-.=c+f(d) a:=if 6>0 then a-H else a x '.—a~>b f :=(xa=£) V(xs=true) g[3xt+2] :=xV“УУ В первых трех операторах вычисляется и присваивается пере- менной числовое значение, а в последующих трех — логическое зна-
2.7 J ОСНОВНЫЕ ОПЕРАТОРЫ 77 чение. Если список левой части содержит несколько переменных, они разделяются таким же знаком присваивания (список левой части): := (переменная) | (список левой части) := (переменная) Например, d :=е :=aXb. Семантика. Оператор присваивания выполняется следую- щим образом. Сначала вычисляются значения всех индексных выра- жений, имеющихся в левой части, в порядке слева направо. Потом вычисляется значение выражения, представляющего собой правую часть. А затем это значение (одно и то же) присваивается всем пере- менным, перечисленным в левой части. Переменные из списка левой части должны быть одинакового типа: либо целого, либо вещественного, либо логического. Выра- жение из правой части имеет свой тип, который определяется по типам аргументов и применяемых к ним операций. В простейшем случае тип выражения должен совпадать с типом левой части, одна- ко алгол допускает отличие этих типов при условии, что один -из них вещественный, а другой целый. Если переменные в левой части целого типа, а в результате вычисления получается значение ве- щественного типа, то алгол предусматривает автоматическое ок- ругление этого значения до ближайшего целого. Например, если а — переменная вещественного типа, a i — переменная целого ти- па, то можно написать операторы присваивания: а : = 1 а :=0.б i : = 1 i :=0.6 (переменной i в обоих последних операторах будет присвоено целое значение 1), но нельзя написать a :=i :=0.6 или а:=/ :=1, так как нельзя объединять переменные разных типов в одном списке ле- вой части. Сформулированный выше четкий порядок выполнения опера- тора присваивания определен авторами алгола не из любви к фор- мализму, а по необходимости, чтобы избежать возможности неодно- значного толкования вычислений, предписываемых оператором присваивания. Например, то обстоятельство, что выполнение опе- ратора присваивания начинается с вычисления индексов, сказыва- ется в том, что оператор i :=х[Л:=2 дает результаты, отличные от результатов выполнения пары операторов i:=2; х[Л: = 2. В частно- сти, при начальном значении 1=1 в первом случае 1=2 и х[1]=2, а во втором случае i=2 и х[21=2. Поскольку всем переменным, перечисленным в левой части опе- ратора присваивания, присваивается одно и то же значение, опера- тор а: =Ь: =а+Ь не эквивалентен паре операторов а: —а+Ь\ Ь: =а+Ь. Например, если имелись начальные значения а=1 и 6=3, первый оператор присваивает значения а=4 и 6=4, тогда как пара опера- торов дает значения а=4 и 6=7.
78 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 2.7.2. Оператор перехода и именующие выражения. Как правило, операторы алгол-программы выполняются под- ряд. Однако имеется возможность отклониться от естественной пос- ледовательности выполнения операторов. Для реализации такого отклонения служат операторы перехода. Если в каком-то месте программы нужно перейти не к следую- щему оператору, а к некоторому другому, то в этом месте помеща- ется оператор перехода, в котором указывается, к выполнению ка- кого оператора следует перейти. Для того чтобы обеспечить возмож- ность таких указаний, в алголе разрешено приписывать отдельным операторам имена, которые называются метками: (метка): := (идентификатор) Согласно этому синтаксическому определению, меткой опера- тора может служить любой идентификатор. Если мы хотим назвать оператор некоторым именем (идентификатором), то пишем перед этим оператором данный идентификатор и отделяем его от операто- ра двоеточием. Например, в записи СЧЕТ: х:=а+И2 оператор присваивания оказывается помеченным меткой СЧЕТ. Если в каком-то месте программы стоит оператор перехода go to СЧЕТ, то после выполнения этого оператора перехода будет выпол- няться указанный оператор присваивания и следующие за ним опе- раторы (до тех пор, пока естественный порядок выполнения опера- торов не будет снова прерван каким-нибудь оператором перехода). Разрешается помечать один и тот же оператор несколькими различ- ными метками, разделяемыми двоеточиями; например, допустима запись: СЧЕТ: ЛИ : х:=а+&|2 В таком случае оператор перехода, ссылающийся на данный опера- тор, может содержать указание любой из меток этого оператора. Оператор перехода может быть безусловным (например, go to All), но может и содержать условие, в зависимости от которого вы- бирается тот или иной оператор для дальнейшего продолжения сче- та, например go to if a>b then All else М2 Выражение, стоящее в операторе перехода после символа go to, называется именующим. Синтаксис оператора перехода и именующего выражения задается следующими формулами: (оператор перехода): :=go to (именующее выражение) (простое именующее выражение) : := (метка) | (указатель пе- реключателя) | ((именующее выражение)) (именующее выражение): := (простое именующее выражение) | (условие) (простое именующее выражение) else (именующее выражение) Семантика. Именующее выражение является правилом для определения метки оператора.
2.7] ОСНОВНЫЕ ОПЕРАТОРЫ! 79 Для иллюстрации применения оператора перехода рассмотрим задачу вычисления последовательности значений хг при i=l, 2, .... п по формуле (У,-4-1 при yz<0, Xl~\yi — 1 при t/z>0. С использованием условного именующего выражения в опера- торе перехода это вычисление записывается на алголе следующим образом: i: = l; S:x[i]:=if y[i]<0 then t/[il+l else y[i]—1; go to if i^n then S else /<; K‘. Оператор с меткой S производит вычисление очередного значе- ния xlil Далее i увеличивается на 1. Затем выполняется оператор перехода с условным именующим выражением. Если i^n, то этот оператор передает управление на метку S (т. е. осуществляет пере- ход к оператору с меткой S) для вычисления следующего значения xlil. Если же t>n, то все нужные значения х(Л уже вычислены; тог- да управление передается на метку К, в конец фрагмента алгол- программы. (При i>n ничего вычислять не требуется, но оператор перехода должен содержать указание метки и на этот случай. За- метим, что метка может стоять только перед оператором; в данном случае можно считать, что после метки К находится пустой опера- тор.) В этом примере условное именующее выражение использо- вано для организации цикла. Более удобен для этой цели оператор цикла: for i:=l step 1 until n do x[i]:=if r/Ul<<0 then «ДЛ+1 else y[i] —1 При определении простого именующего выражения мы ввели понятие указателя переключателя. С этим понятием связаны сле- дующие формулы: (указатель переключателя) ::= (идентификатор переключате- ля) ((индекс)] (описание переключателя) : := switch (идентификатор пере- ключателя) := (переключательный список) (переключательный список) ::= (именующее выражение)! (переключательный список), (именующее выражение) Переключатели в алголе служат для удобной записи перехода, «переключаемого» в зависимости от значения некоторой переменной. Например, в алгол-программе можно описать такой переклю- чатель с идентификатором х: switch x:-=alpha, beta, gamma, delta и тогда, если переменная k принимает значение 1, 2, 3 или 4, то опе- ратор go to xl/г] означает то же самое, что и рассмотренный нами one-
80 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 ратор go to if &=1 then alpha else if &=2 then beta else if fe=3 then gamma else delta. Переключательный список может содержать не только метки, но и любые именующие выражения. Например, switch у.=Л41, if a>b then М1 else М2, М3, М4, х[а+6] В этом случае при i=l оператор go to #[i] означает, что нужно перейти к оператору с меткой М1; при 1=2 нужно проверить выпол- нение условия а>Ь и в зависимости от его истинности перейти либо к метке Ml либо к метке М2; при 1=3 или /=4 нужно перейти со- ответственно к метке М3 или М4. При i=5 нужна вычислить значе- ние а+b и обратиться к переключательному списку х, т. е. пере- дать управление на одну из меток alpha, beta, gamma или delta в зависимости от значения а-^Ь. Для того чтобы произошла передача управления на некоторую метку, индексное выражение в указателе переключателя должно принимать положительные значения 1, 2, 3, ..., п, где и — число именующих выражений в соответствующем переключательном спис- ке. Поэтому в нашем примере переменная i может принимать только значения 1, 2, 3, 4 и 5, причем при i=5 значение а+b должно быть 1 или 2 или 3 или 4. Если I или а+b типа real, то они округляются и допустимы значения 0.5^j<5.5 и 0.5^а+Ь<4.5. Заметим, что в одном именующем выражении могут содержать- ся и метки и указатели переключателей, например, if a>ylk] then All else g[i+1 ] Здесь y[k] — переменная с индексом, g[i+l] — указатель переклю- чателя. Семантика оператора перехода. Оператор перехода слу- жит для изменения естественной последовательности выполнения операторов. Его действие состоит в том, что сначала вычисляется значение именующего выражения, написанного после go to, а по- том происходит переход к оператору, помеченному меткой, полу- ченной в результате этого вычисления. Целью вычисления именую- щего выражения является определение метки, являющейся его зна- чением. Если именующее выражение начинается с условия, то при вычислении этого выражения сначала проверяется данное условие и в зависимости от того, выполнено оно или нет, значением всего именующего выражения будет либо значение выражения, написан- ного между then и else, либо значение выражения, написанного пос- ле else. Таким образом, аналогично вычислению арифметического или логического выражения, мы сводим вычисление именующего выра- жения, содержащего условие, к вычислению простого именующего выражения. Если оно оказывается меткой, это и есть значение ис- ходного именующего выражения и нужно передать управление опе- ратору с этой меткой. Если же мы пришли к указателю переключа- теля, то поиск метки продолжается следующим образом. Отыскива-
2.7] ОСНОВНЫЕ ОПЕРАТОРЫ 81 ется переключательный список с тем же идентификатором, какой имеется у этого указателя. Затем в этом переключательном списке выбирается именующее выражение, номер которого равен значению индекса из указателя переключателя. (Нецелые значения индексов округляются, как и в случае переменных с индексами.) Далее вы- числяется значение этого найденного именующего выражения, при- чем снова применяются все те же правила, и такой процесс продол- жается до тех пор, пока не будет найдена метка. Если на каком-то этапе вычисления именующего выражения встречается указатель переключателя, ссылающийся на несущест- вующую позицию переключательного списка, это делает весь опера- тор перехода эквивалентным пустому оператору, т. е. происходит переход к следующему оператору программы. В частности, может оказаться, что в каком-то переключательном списке содержится именующее выражение, в котором указатель пе- реключателя ссылается на тот же список, но с другим индексом. Например, при описании переключателя switch A:=beta, Л[Л; оператор перехода go to Л [2] корректен только в том случае, если i#=2; если же i=2, то, согласно данной семантике, мы никогда не выйдем из зациклившегося процесса вычисления именующего вы- ражения. При i=l оператор go to Л [2] означает переход на оператор, по- меченный меткой beta. При других целых значениях I, например при 1=0, этот оператор перехода эквивалентен пустому, так же как и операторы go to Л[0], go to Л [2.6] и т. д. Переход по переключательному списку можно использовать Для организации многократного использования один раз описанного вычисления. Рассмотрим пример такого использования. Пусть имеются два набора троек длин отрезков, причем каждый набор оформлен в виде двумерного алгольного массива Л[1 : п, 1 : 3] и В[1 : п, 1:3]. Каждую тройку Л[*, 1], АН, 2], АН, 3] и B[i, 1], ВН, 2], ВН, 3] можно отнести к одному из трех классов: 1) из данных трех отрезков нельзя составить треугольник; 2) можно составить тупоугольный треугольник; 3) можно составить остроугольный или прямоугольный тре- угольник. Требуется посчитать число таких значений i, при которых клас- сы троек Л [i, 1], АН, 2], АН, 3] и B[t, 1], B[i, 2], B[t, 3] совпадают. Составим схему нужных вычислений: 1°. Присвоить начальное значение 0 переменной 3, в которой будет накапливаться результат. 2°. Для каждого значения *=1,2, ..., п выполнить пункты 3°, 4° и 5°. 3°. Определить номер класса, к которому принадлежит тройка АН, 1], АН, 2], АН, 3].
«2 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 ЧГл. 2 4е. Определить номер класса, к которому принадлежит тройка Ж 1), ВЦ, 2], BU, 31. 5°. Если эти классы одинаковые, то прибавить единицу к зна- чению S. Мы видим, что в двух разных местах счета производится вычис- ление номера класса, к которому относится очередная тройка длин отрезков. Чтобы не программировать это вычисление дважды, мож- но завести внутри нашей алгол-программы подпрограмму F, ко- торая будет получать длины отрезков в переменных х, у, г и выдавать номер соответствующего класса в переменной и. Выходить из под- программы F нужно каждый раз так, чтобы возвращаться в то мес- то счета, которое следует за соответствующим оператором обраще- ния к подпрограмме go to F. Для организации таких возвратов мы используем управляющую переменную k, которой будем присваи- вать значение 1 при выполнении пункта 3° и значение 2 при выпол- нении пункта 4°. Выход из подпрограммы F, обеспечивающий воз- врат в нужное место счета, будет осуществляться оператором пере- хода по переключателю go to key [Л]. Программа может выглядеть так: switch кеу:=Ф1, Ф2; s:=0; i: = l; Ml : x:=AU, 1]; y:=AU, 21; z:=AU, 3]; Л:=1; go toF; Ф1 : v:—u; x:=BU, U; y:=BU, 2]; z:—BU, 3]; k:=2; go to F; Ф2 : s:=s-|-(if u=v then 1 else 0); i:—i+l; go to if then Ail else СТОП', F : m:=if x>y/\x>z then x else if y>z then у else z; n:=if 2 then 1 else if 2Xm f 2>x f 2+y f 2-f* zf2 then 2 else 3; go to key [Л]; СТОП: Подпрограмма F начинается с метки F и состоит из двух опера- торов присваивания и одного оператора перехода (возврата). Пер- вый оператор подпрограммы F присваивает переменной т значение длины наибольшего из трех отрезков х, у, г. Следующий оператор вычисляет номер класса, к которой относится эта тройка отрезков. (Если 2т>х+у+г, то наибольший отрезок не меньше суммы двух других, т. е. из данных трех отрезков нельзя составить треугольник. Иначе, если 2m2>x2+z/2+z2, то квадрат наибольшего отрезка боль- ше суммы квадратов двух других отрезков, т. е. из данной тройки можно составить тупоугольный треугольник. В противном случае данная тройка образует остроугольный или прямоугольный тре- угольник.) В последние годы широкое распространение получило понятие структурного программирования, введенное голландским ученым Э. Дейкстрой. В рамках теории структурного программирования
2.71 ОСНОВНЫЕ ОПЕРАТОРЫ 83 формируются и развиваются определенные принципы составления программ с «хорошей» структурой. Мы обсудим такие принципы в разд. 2.16, а теперь отметим, что структурное программирование только в трех случаях предполагает необходимым нарушение ес- тественного порядка выполнения операторов в программе: 1) для выбора одного оператора из нескольких в зависимости от выполнения логических условий; 2) для организации повторения (цикла); 3) для обращения к функции (иначе говоря, к процедуре или подпрограмме). Для этих трех случаев в алголе (как и в других развитых язы- ках программирования) введены специальные средства записи, бо- лее удобные и наглядные, чем операторы перехода. Такими сред- ствами записи являются условный оператор, оператор цикла и опи- сание процедуры. С использованием этих средств рассмотренное нами вычисле- ние можно оформить без операторов перехода: integer procedure КЛАСС (х, у, z); begin m:=if x>y/\x>z then x else if y>z then у else г; КЛАСС-.=\\ 2Xtn^x+y+z then 1 else if 2X/nf 2>xf 2+i/t2+ z f 2 then 2 else 3; end кончилось описание процедуры КЛАСС', НАЧАЛО ВЫЧИСЛЕНИЯ' s:=0; for r. = l step 1 until п do if КЛАСС (A[i, 1], A[i, 2], A[i, 3])== КЛАСС (B[i, 1], B[i, 2], B[i, 3]) then s: =$+1; Процесс вычисления начинается с метки НАЧАЛО ВЫЧИСЛЕ- НИЯ и состоит в выполнении двух операторов: оператора присваи- вания s:=0, а затем оператора цикла, внутри которого находится условный оператор (см. разд. 2.8) и используется процедура- функция КЛАСС, описанием которой начинается программа. Употребление операторов перехода, с точки зрения сторонни- ков структурного программирования, неоправдано и приводит к потере наглядности программы. Поэтому иногда структурное про- граммирование называют «программированием без go to». Опреде- ленная потеря наглядности в программах, изобилующих операто- рами перехода, не вызывает сомнений, и, не вдаваясь в крайности, можно рекомендовать программистам не злоупотреблять операто- рами go to. 2.7.3. Оператор процедуры. Операторы ввода и вывода. Операторы процедуры служат для обращения к фрагментам про- граммы, описанным отдельно и предназначенным для неоднократного- использования. Синтаксически оператор процедуры эк- вивалентен понятию функции, с которым мы познакомились в разд. 2.5;4:
.84 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 (оператор процедуры): := (идентификатор процедуры) (сово- купность фактических параметров) (совокупность фактических параметров): :=(пусто) | ((список фактических параметров)) (фактический параметр): := (идентификатор) | (выражение) | (строка) (выражение): := (арифметическое выражение)! (логическое выражение) | (именующее выражение) Различие состоит в том, что функция представляет собой опе- ранд выражения, заключаемый, например, между знаками опера- ций в правой части оператора присваивания, тогда как оператор процедуры является самостоятельным оператором и помещается, например, между знаками ; в последовательности операторов. В обо- их случаях производится обращение к фрагменту программы. Од- нако в случае функции целью обращения является вычисление зна- чения, которое присваивается в данном фрагменте программы иден- тификатору процедуры, причем прочие вычисления, которые могут производиться в этом фрагменте, хотя и выполняются, но рассмат- риваются как «побочный эффект», а в случае оператора процедуры такой специальной цели нет и просто нужно выполнить этот фраг- мент программы. Подробное рассмотрение операторов процедуры будет проведено в разд. 2.13, а здесь мы остановимся только на двух частных случаях. В языке алгол предусмотрены операторы процедуры, которые не требуют отдельного описания соответствующего фрагмента про- граммы (подобно тому как такого описания не требуют стандартные функции sin., In и др.). Такими стандартными операторами являют- ся операторы ввода значений простых переменных и массивов из внешней среды и вывода таких значений во внешнюю среду. Обыч- но вводятся значения начальных данных, а выводятся результаты работы алгоритма. Однако официальное описание алгола не пре- дусматривает достаточно развитых средств для задания операторов ввода и вывода. В различных конкретных реализациях алгола эти операторы формулируются по-разному. Мы выберем некоторый учебный вариант задания операторов ввода и вывода в соответст- вии со следующими металингвистическими формулами: (оператор ввода): :=ввод ((список объектов)) (оператор вывода): :=выеод ((список объектов)) (список объектов): := (объект) | (список объектов), (объект) (объект): := (простая переменная) | (идентификатор массива) Примеры: ввод (а, Ь, с, D) вывод (х, Y, z, U) Поскольку объектом может служить простая переменная или идентификатор массива, операторы ввода и вывода позволяют про-
2.7] ОСНОВНЫЕ ОПЕРАТОРЫ 85 изводить ввод/вывод одиночных значений или массивов целиком. Массив может содержать любое количество значений, что позволя- ет заготовлять для последующей циклической обработки любое количество исходной информации, а также накапливать и выводить наружу большими порциями результаты счета. Благодаря наличию операторов ввода мы получаем возможность писать на алголе программы многократного использования, кото- рые можно применять к различным начальным данным, присваи- вая переменным новые значения, вводимые извне. Без этих опера- торов программа при каждом ее использовании применяется к од- ним и тем же начальным данным (задаваемым числами в тексте ал- гол-программы) и поэтому выполняет одну и ту же работу, так что, использовав один раз, ее можно было бы выбрасывать за ненадоб- ностью. Ввод данных обеспечивает возможность многократного использования алгол-программы подобно тому, как многократное использование машин Тьюринга и нормальных алгоритмов Маркова обеспечивается возможностью применять эти алгоритмические схе- мы к различным словам. (Эти слова задаются извне до начала рабо- ты алгоритма и в преобразованном виде остаются после завершения работы алгоритма.) Отличие алгола состоит в том, что он позволя- ет производить ввод и вывод в процессе выполнения алгоритма. Когда вычислительная машина должна вводить извне данные, ей необходимо задать, откуда (с какого внешнего устройства) дан- ные нужно взять, и в каком виде они там представлены. Данные мо- гут браться, например, с перфокарт, перфоленты, магнитной ленты, магнитного барабана, сменных магнитных дисков и других внеш- них устройств. Аналогично при выводе результаты можно напеча- тать на узкой бумажной ленте или широких листах бумаги, пробить на перфокартах, показать на видеоэкране и др. Относительно вида представления данных можно, например, при выводе массива троек А[1:п, 1:3] указать, что в каждой строке бумажного листа следует печатать очередную тройку чисел из массива А, причем во всех строках целые части чисел нужно печатать под целыми, а дробные под дробными: 1.89 2 1 3 4.5 7 8.05 13.01 2.7 19.4 0.007 4.501 В некоторых современных языках программирования, напри- мер в фортране, имеются развитые специальные средства для указа- ния номера канала ввода/вывода (т. е. конкретного внешнего уст- ройства), а также формата, определяющего вид представления вво- димых или выводимых данных. Однако в рамках нашего изло- жения алгола мы рассматриваем операторы ввода/вывода только с указанием объектов. Что касается каналов и форматов, то они за-
86 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. г даются по-разному в различных конкретных машинных реализа- циях алгола. Семантика. Оператор ввода служит для присваивания пе- ременным значений, которые берутся извне. Оператор вывода обес- печивает копирование значений во внешнюю среду, которые, таким образом, становятся результатами работы алгоритма. 2.7.4. Пустой оператор. В примерах с операторами перехода из раздела 2.7.2 мы уже сталкивались с употреблением пустого оператора. (пустой оператор): := Пустые операторы не задают никаких действий. Они, как пра- вило, служат для размещения меток. Ссылаясь в операторе перехо- да go to L на метку L, мы тем самым указываем, что нужно выпол- нить оператор, следующий за меткой L и двоеточием; но иногда ни- каких дополнительных вычислений не требуется, и ссылка на мет- ку L означает, например, что нужно перейти к концу программы. В таком случае метка L и знак : помещаются в конце программы не- посредственно перед символом end, а синтаксическое требование, чтобы после каждой метки следовал оператор, удовлетворяется, поскольку мы считаем, что между соседними символами L: и end находится пустой оператор. 2.8. Условные операторы Условный оператор является композицией из основных опера- торов и позволяет осуществлять выбор между выполнением различ- ных операторов в зависимости от истинности некоторого условия, например: if х>у then х:=0 else у:=0 В этом случае, подобно уже знакомой нам структуре условного выражения, в зависимости от значения логического выражения х>у, заключенного между if и then, выбирается либо оператор х:=0, заключенный между then и else, либо оператор y.—Q, который следует за else. Иначе говоря, той из переменных х и у, значение которой больше, присваивается новое значение. Однако, в отличие от единообразной синтаксической структуры условного выражения, обязательно предписывающей наличие слу- жебного слова else и следом за ним второй альтернативы, синтаксис условных операторов разрешает употреблять и неполный условный оператор (без else и второй альтернативы). Например, допускается запись а: =0; if х>0 then а: = 1 согласно которой при выполнении условного оператора перемен- ная а либо получает новое значение 1, либо сохраняет свое преж- нее значение 0.
•2.81 УСЛОВНЫЕ ОПЕРАТОРЫ 87 В более общем виде две основные конструкции условного опе- ратора можно представить следующим образом: if В then SI else S2 и if В then S Из-за того, что в конструкции условного оператора может от- сутствовать else, не разрешается после then писать условный опера- тор. Иначе могли бы возникать двусмысленности, как, например, в следующем случае: <г.=0; if fc>0 then if c>0 then a: = 1 else a: =2 Здесь в зависимости от того, какому из двух символов then соот- ветствует единственный символ else, могли бы возникнуть две сле- дующие трактовки: 1) Если else соответствует первому then, то >0 и с^О, О при b 1 при b > 0 и с > 0, k 2 при 6^0. 2) Если else соответствует второму then, то: ' 0 при а= 1 при b > 0 и с > 0, 2 при д>0 и с^О. Поэтому на алголе такая запись не разрешается; а если между then и else нужно поместить условный оператор, то он заключается в операторные скобки begin и end, становясь при этом безусловным составным оператором: 1) а:=0; if b>6 then begin if £>0 then a: = l end else a: =2 или 2) a:=0; if bX) then begin if £>0 then a: = l else a: =2 end Синтаксис условного оператора (условный оператор): := (условие) (безусловный оператор) else (оператор) | (условие) (безусловный оператор) | (условие) (оператор цикла) | (метка): (условный оператор) (безусловный оператор): := (основной оператор) | (составной оператор) | (блок) | (метка):(безусловный оператор) В этих металингвистических формулах употребляются понятия оператора цикла, составного оператора и блока, которые будут оп- ределены позднее. Согласно данному синтаксису, внутри условного оператора мо- гут содержаться метки, так что допускается, например, запись: if а>ЬД£>0 then V:q:—n+m else W:if а>0 then l/:go to M В этом случае извне можно передать управление «внутрь» ус- ловного оператора на метку V, W или U. После оператора go to V выполнится,оператор присваивания q:=n-\-m и далее будет выпол-
88 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 пяться оператор, следующий за условным. После оператора go to W выполнится «короткий» условный оператор if а>0 then go to М. Если же выполнить оператор go to U, то мы сразу же попадем на оператор перехода, который отошлет нас к метке М. Семантика условного оператора. Полный условный опе- ратор служит для выбора одного из двух операторов. При его вы- полнении вычисляется логическое выражение, находящееся в ус- ловии. Если оно истинно, то выполняется тот оператор, который написан между then и else, и не выполняется оператор, найисанный после else. В противном случае выполняется тот оператор, который написан после else, и не выполняется оператор, написанный между then и else. Неполный условный оператор служит для выбора между выпол- нением и невыполнением содержащегося в нем оператора. Если ус- ловие истинно, то выполняется оператор, написанный после then, в противном случае он не выполняется. Поскольку после else может следовать любой (и в частности сно- ва условный) оператор, то конструкция условных операторов позво- ляет производить выбор одного из любого числа операторов, напри- мер, if а<0 then х:=1 else if а<2 then х:=2 else if а=4 then х:=3 - Рассмотрим еще один пример. Пусть имеется одномерный мас- сив с идентификатором а, состоящий из п. элементов, пронумерован- ных от 1 до п, причем элементы массива могут принимать логиче- ские значения (true и false). Нужно посчитать, сколько элементов принимают значение true, и вывести полученный результат. С по- мощью условного оператора это записывается следующим образом: ввод (a)-, s:=0; i: = l; цикл: if alt] then s:=s-M; t:=i+l; if t^n then go to цикл-, вывод (s) Здесь использованы два неполных условных оператора, при- чем в первом из них в качестве условия выступает логическая пе- ременная ali]: если значение ali] есть true, то выполняется оператор s:=s-H. Второй неполный условный оператор обеспечивает п вы- полнений цикла, после чего пропускает нас на оператор вывода ре- зультата. Заметим, что второй условный оператор в данном случае не по существу, так как он используется только в качестве средства замены еще не рассмотренной нами конструкции оператора цикла. 2.9. Составные операторы Часто в программировании возникает естественная задача выбо- ра между группами операторов. В таком случае конструкция ус- ловного оператора непосредственно не применима, так как она обес- печивает только выбор между отдельными операторами. Однако в алголе предусмотрена возможность формального объединения не- скольких операторов в один составной, который к тому же стано-
? 9] СОСТАВНЫЕ ОПЕРАТОРЫ 89 вится безусловным, даже если в него входит условие. Подобно тому, как любое арифметическое выражение, взятое в скобки, тем самым становится операндом, составной оператор образуется как набор операторов, заключенный в скобки, но не круглые, а специальные операторные begin и end. Между этими служебными символами мож- но вписать любое число операторов, отделенных один от другого символом ; . Тем самым мы получаем, например, возможность про- изводить в зависимости от истинности условия выбор между пара- ми операторов: if a>b then begin а: = 1; b:=0 end else begin а:=0; b:=l end Синтаксис составного оператора (составной оператор): :=begin (список операторов) end (список операторов): :=(оператор) | (список операторов); (оператор) (оператор): := (безусловный оператор) | (условный оператор) | (оператор цикла) Напомним, что в синтаксическом определении безусловного опе- ратора одной из альтернатив было понятие составного оператора. Подобная рекурсия в металингвистических формулах нам уже встре- чалась, и в данном случае она означает только, что составной опе- ратор может в частности содержать внутри себя составные опера- торы. В качестве примера работы с составными операторами рассмот- рим вычисление вектора с, являющегося произведением матрицы а на вектор b по формуле п Cj = 2 aJk-bk при 1 < / С П. Поскольку нужны п значений Cj, то естественно организовать счет как цикл с управляющей переменной /, которая последователь- но принимает значения 1,2, .. .,п. При каждом значении / вычис- ляется очередной элемент cj. Внутри этого цикла требуется сложить п произведений a/ftXbA. Поэтому нужно организовать внутрен- ний цикл с управляющей переменной k, которая при каждом кон- кретном значении / тоже последовательно принимает значения от J до п. Программа может выглядеть так: ввод (а, Ь)\ /: = 1; элемент: if /^n then begin fe: = l; с[/]:=0; шаг: if k^n then begin d/]:=d/l+e[/, Л]хЫЛ]; k:—k+l', go to шаг end; go to элемент end; вывод (c)
90 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2: Для удобства зрительного восприятия мы расположили со сдви- гом вправо символы begin, с которых начинаются вложенные сос- тавные операторы, причем каждый символ end помещен под соот- ветствующим ему символом begin. В данной программе организуются два вложенных цикла, каж- дый из которых оформлен как неполный условный оператор, при- меняемый к составному оператору. Во внутреннем составном опе- раторе к накапливаемому значению Cj прибавляется очередное про- изведение ajk-bk, продвигается на единицу значение управляю- щей переменной k и управление передается на метку шаг (для выпол- нения следующего шага цикла вычисления очередного элемента вектора с). Во внешнем составном операторе устанавливается на- чальное значение k—\ для перебора элементов очередной строки матрицы а, которая будет скалярно умножаться на вектор Ь, при- чем скалярное произведение будет накапливаться в очередном эле- менте с/ вектора с. Затем производится начальное присваивание с[/]=0 и выполняется внутренний цикл вычисления очередного скалярного произведения, т. е. очередного элемента с[/1. После этого к счетчику / прибавляется 1 и управление передается на метку элемент для вычисления следующего элемента с[/J или для выхода на оператор вывод (с), если j>n, т. е. если формирование вектора с уже завершено. 2.9.1. Примеры с использованием составных операторов, содер- жащие анализ фРагментов алгольного текста. Рассмотренные нами средства языка алгол позволяют, в част- ности, писать на алголе программы анализа алгольных текстов. Предназначенный для анализа текст на алголе будем представлять в виде массива чисел, каждое из которых изображает очередной сим- вол этого текста. Нужно установить кодировку целыми числами всех символов из алфавита языка алгол. В принципе возможна любая кодировка со взаимно однозначным соответствием. Для боль- шей наглядности мы выбираем кодировку, которая разделяет сим- волы на несколько классов, причем каждый класс кодируется под- ряд, начиная с круглого числа. Такими классами являются цифры (коды от 0 до 9), строчные латинские буквы (коды от 10 до 35), про- писные латинские буквы (коды от 40 до 65), строчные и прописные буквы русского алфавита, не совпадающие по написанию с латин- скими (коды от 70 до ПО), служебные знаки и служебные слова алфавита алгола (коды от 200 до 252). Заметим, что в связи с этим в кодировке имеются пропуски чисел. Например, число 36 не ко- дирует никакого алгольного символа. Коды служебных знаков и слов представлены в таблице 2.3. Теперь будем анализируемый алгольный текст кодировать последовательностью элементов массива Т, начиная с Т[0]. В конце ч закодированного тексту будем помещать специальный код конца —
2.91 СОСТАВНЫЕ ОПЕРАТОРЫ 91 Таблица 2.3 Сим- вол Ч ИСЛОВОЙ код Сим- вол Ч ИСЛОВОЙ код Символ Числовой код Символ Число- вой код t 200 V 214 true 228 own 242 1 x 201 ZD 215 False 229 boolean 243 / 202 = 216 go to 230 integer 244 203 217 if 231 real 245 + 204 218 then 232 array 246 205 • 219 else 233 switch 247’ 206 220 for 234 procedure 248 207 •= * 221 do 235 string 249 208 ( 222 step 236 label 250 209 ) 223 until 237 value 251 210 [ 224 while 238 10 252 211 ] 225 comment 239 212 n 226 begin 240 /? 213 n 227 end 241 число 999. Например, текст x:=al + S; кодируется последователь- ностью целых чисел 33, 221, 10, 1, 204, 41, 220, 999, располагаемых в элементах ПО], ЛИ, ..., Л7]. Формирование нужных значений этих элементов может быть выполнено оператором ввод (Т) или опе- раторами присваивания: Л0]:=33; Т[\}:=22\\ Л21: = 10; Л3]: = 1; Л4]:=204; Л5]:=41; Л61: =220; Л7]: =999 Рассмотрим примеры анализа алгольного текста, закодирован- ного в массиве Т. Пусть нужно узнать, является ли текст в массиве Т идентифи- катором, и присвоить логической переменной Р значение true, если да, и false, если нет. Требуемый анализ можно выполнить следующими действиями: 1°. Ввести текст. 2°. Если первый символ не буква, то выполнить присваивание P:=false и закончить работу выводом результата Р. 3°. Для 1=1, 2, ..., пока Л/] есть буква или цифра, перебирать символы. 4°. Если символ, оказавшийся не буквой и не цифрой, есть при- знак конца (999), то P;=true, иначе P:=false. s 5°. Вывести Р. На алголе это записывается так: ввод (Г); if ЛО]<1ОУЛО]>11О then P:=false else begin i: = l; ЦИКЛЫ ЛЛ^НО then begin f:=i+l; go to ЦИКЛ end; Р:=ЛЛ=999 end; вывод (P)
92 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 Переменная i служит счетчиком элементов 71 Л, в которых коди- руются последовательные символы текста. Если текст в Т начина- ется нес буквы, т. е. 710] < 10 или 710] > 110, то условный оператор присваивает значение P:=false, после чего мы переходим к опера- тору вывод (Р). (Мы предполагаем, что, пока не встретим кода 999, будем встречать в массиве Т только коды алгольных символов, а числами от 10 до НО кодируются только буквы.) Если текст начи- нается с буквы, то содержащийся в составном операторе цикл пере- бирает последовательные элементы массива Т, являющиеся кодами чисел или букв. Такие коды 7li] характеризуются отношением TH 110. Мы выходим из этого цикла, встретив код не буквы и не цифры. Если это код конца 999, то текст, закодированный в масси- ве Т, является идентификатором, и тогда оператор присваивания P:=71i]=999 выдает ответ P=true. В противном случае это код алгольного символа, отличного от буквы или цифры, и значение отношения 71i]=999 есть false. Рассмотрим теперь такую задачу. Пусть в массиве Т содержится кодировка целого числа без знака из (п+1) цифры в элементах от 710] до Tin], причем цифра младшего разряда записана в Tin}. (В данном случае мы можем вместо «код цифры младшего разряда» употребить слова «цифра младшего разряда», поскольку в нашей ко- дировке коды цифр совпадают с ними самими.) Нужно записать в массив Т кодировку числа на единицу большего. Предполагается, что закодированное в Т число состоит не из одних девяток. (Сход- ную задачу мы решали применительно к машине Тьюринга.) Нуж- но, если цифра младшего разряда не 9, прибавить к ней 1, а если 9, то написать вместо нее 0 и прибавить 1 к предыдущей цифре, если та не 9 и т. д. Составим схему требуемого вычисления: Г. Ввести текст. 2°. Присвоить начальное значение i:=n. 3°. Если 7’[i]#=9, то выполнить присваивание 7li]:=71t]+I и перейти к п. 5° иначе 4°. Выполнить присваивание ТЦ]: =0, вычесть 1 из значения i и перейти к п. 3°. 5°. Вывести полученный текст. На алголе это записывается так: ввод (7'); 1’:=л; Af:if then 71i]:=7li]+l else begin T[i]:=0; i:=i—1; go to M end; 1 вывод (T) В конструкцию условного оператора мы включили после else составной оператор, который целиком пропускается, если очеред- ная цифра не равна 9, так что после прибавления к ней 1 произой- дет переход к выводу массива Т. Если же эта цифра равна 9, то вы- полняется составной оператор, который заносит в нее 0, измёняет
, 2.10] ОПЕРАТОРЫ ЦИКЛА 93 * / счетчик i для перехода к рассмотрению предыдущей цифры и пере- дает управление в начало условного оператора. Поскольку исход- ное число состоит не из одних девяток, то когда-нибудь встретите? ' значение T[t]=/=9, к нему прибавится 1 и произойдет вывод масси- ва Т. 2.10. Операторы цикла В примерах из предыдущих разделов мы часто сталкивались с задачей описания циклического счета. Многократное последова- тельное выполнение одинаковых действий является основой про- граммирования, так'как обеспечивает возможность широкого при- менения программ. Как правило, однотипные действия применяют- ся многократно либо к одним и тем же переменным в итерационном процессе, пока не выполнится определенное условие, либо к разным величинам, являющимся элементами одного массива. Продвиже- ние циклического счета обычно определяется перебором последова- тельных значений переменной, называющейся управляющей пере- менной или параметром цикла. Оператор цикла состоит из заголовка и одного оператора. Этот оператор представляет собой тело цикла, выполнение которого бу- дет повторяться. (Поскольку за заголовком цикла может следовать любой оператор, такая структура позволяет нам включать в цикл любое количество операторов, объединяя их в составной оператор.) Заголовок задает закон изменения параметра, при каждом значе- нии которого один раз выполняется тело цикла. Авторы алгола пре- дусмотрели три способа задания такого закона: перечисление, про- грессию и пересчет и соответственно три структуры элементов спис- ка цикла, которые могут содержаться в заголовке цикла. Первым способом явно указываются значения параметра, при которых нужно выполнять тело цикла. Каждое такое значение за- дается элементом, состоящим из одного арифметического выра- жения. Прогрессия задается тремя арифметическими выражениями по схеме £1 step Е2 until £3. Сначала значение параметра цикла уста-, навливается равным £1; в дальнейшем на каждом шаге цикла к не- му прибавляется £2, и этот процесс продолжается до тех пор, пока параметр не достигнет значения £3 или не перейдет за это зна- чение. Пересчет задается элементом вида £ while В, где £ — арифме- тическое, В — логическое выражение. Эта структура означает, что на каждом шаге цикла нужно вычислять значение £ и присваивать его параметру цикла, действуя так, пока выражение В, также вы-' числяемое на каждом шаге, сохраняет значение true; процесс закан- чивается, как только при очередной проверке оказывается, что зна- чением В является false. Обычно при этом в формулу £1 входит сам
94 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 параметр цикла, что позволяет вычислять его следующее значение по предыдущему. Например, закон геометрической прогрессии со знаменателем а можно задать заголовком цикла for s:=sxa while s^4 do. Если предварительно было присвоено значение s=l, то данный элемент списка цикла обеспечивает выполнение тела цикла при значениях s=a, а2, а3 и так далее, пока параметр s не перейдет через верхнюю границу А. Заголовок цикла начинается с записи for К:=(где V — перемен- ная, являющаяся параметром цикла) и заканчивается служебным словом do, например, for i:=0 step 1 until 100 do В заголовке цикла могут содержаться несколько элементов спис- ка цикла, разделенные запятыми. В этом случае элементы управ- ляют перебором значений параметра по очереди. Так, тот же цик- лический счет при значениях параметра i—6, 1, ..., 100 можно за- дать заголовком fori:=0, i-H while iXlOO do где первый элемент — типа перечисления, а второй — типа пере- счета. Синтаксис операторов цикла (элемент списка цикла ):: = (арифметическое выражение)! (арифметическое выражение) step (арифметическое выраже- ние) until (арифметическое выражение) | (арифметическое выражение) while (логическое выражение) (список цикла): := (элемент списка цикла) | (список цикла), (элемент списка цикла) (заголовок цикла): :=for (переменная): = (список цикла) do (оператор цикла): := (заголовок цикла) (оператор) Семантика. Оператор цикла служит для задания повто- ряемого выполнения оператора, являющегося телом цикла, при пос- ледовательных значениях параметра, определяемых заголовком цикла. Входящие в заголовок элементы списка цикла управляют последовательностью значений параметра по очереди слева направо до исчерпания каждого очередного элемента (без возвратов к уже исчерпанным элементам). Чтобы строго определить порядок дейст- вия предусмотренных типов элемента списка цикла, напишем на алголе программы, эквивалентные каждому такому типу. Будем обозначать параметр цикла через V, тело цикла через S, арифмети- ческие выражения через El, Е2, ЕЗ, Е, логическое выражение через В. Когда рассматриваемый элемент списка исчерпан, мы уходим на метку ЭЛИСЧ. Предполагается, что после этого произой- дет переход либо к следующему элементу списка цикла (если он есть), либо к оператору, следующему за оператором цикла. Вычисление, управляемое элементом типа перечисления Е1, эквивалентно программе V:=E1; S; go to ЭЛИСЧ
2.10) ОПЕРАТОРЫ ЦИКЛА 95 Вычисление, управляемое элементом типа прогрессии £1 step £2 until £3, эквивалентно программе У:=£1; Ll:if(V—£3)Xsign(£2)>0 then go to ЭЛИСЧ-, S; y:=V4-E2; go to LI Вычисление, управляемое элементом типа пересчета £ while В, эквивалентно программе L: V:=£; if-1£ then go to ЭЛИСЧ-, S; go to L В программном представлении элемента типа прогрессии уч- тено, что шаг £2 может быть как положительным, так и отрица- тельным. Например, при заголовке for i: = 10 step—1 until 2 do вы- полнение тела цикла происходит при значениях i=10, 9, ..., 2. Затем при i=l оказывается истинным условие (V—£3) Xsign(£2)>0, поскольку V=l, £3=2 и sign. (£2)=—1, а это значит, что элемент списка цикла исчерпан.^ Поскольку проверка на исчерпание элемента производится до выполнения тела цикла, может оказаться, что элемент списка цикла не задает ни одного выполнения. Например, элемент 10 step 1 until 9 сразу же оказывается исчерпанным, так как при V—10, £3=9, sign(£2)=l верно, что (V—£3)Xsign(£2)>0. ( Теперь мы можем рассмотреть следующий пример, в котором представлены различные типы элементов списка цикла. fori: = l, 5, 6, 7 step 2 until 13, i4-3 while i>0, 35 step— 5 until 20 do вывод (i) Тело цикла состоит из оператора вывода очередного значения параметра цикла. В заголовке данного цикла 6 элементов, разде- ленных запятыми. Первые три элемента перечисляют последова- тельные одиночные значения 1=1, 5, 6. Далее элемент типа про- грессии задает выполнение цикла при i=7, 9, 11, 13. Этот элемент будет исчерпан при значении 1=15. Затем элемент типа пересчета каждый раз делит на 3 операцией 4- предыдущее значение i. По- скольку перед этим мы имели 1=15, то получаем выполнения цикла при i=5 и 1 (так как 54-3=1). И, наконец, последний элемент типа прогрессии задает выполнения тела цикла при i=35, 30, 25, 20. Таким образом, всего тело цикла выполняется 13 раз и будут напе- чатаны числа 1, 5, 6, 7, 9, 11, 13, 5, 1, 35, 30, 25, 20 Параметр цикла может изменяться не только управлением эле- ментов из заголовка, но и в теле цикла. Так, в результате выпол- нения оператора for i:=l step 1 until 20 do begin вывод (i); i:=i+lend будут выведены числа 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, так как на каждом шаге происходит прибавление 1 к параметру i два раза: в заголовке цикла и в операторе присваивания i:=i+l. Согласно правилам алгола, значение параметра цикла поело окончания этого цикла по исчерпанию элементов его списка не оп-
96 (АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 ределено, так что им нельзя пользоваться как аргументом при вы- полнении последующих операторов. (Однако если выход из цикла осуществляется посредством какого-либо оператора перехода, то значение параметра цикла сохраняется таким, каким оно было не- посредственно перед выполнением оператора перехода.) Входить в цикл извне разрешается только через его заголовок, хотя в теле цикла и могут быть метки, предназначенные, например, для пере- дач управления между различными операторами, содержащимися в теле цикла. 2.10.1. Некоторые примеры с использованием операторов цикла. Оптимизация циклов. Пусть требуется вычислить значение s целого числа без знака, состоящего из (п+1) цифр, которые хранятся в элементах ПО], ... . .., Т[п] массива Т, как описано в разд. 2.9.1. (Напомним, что элемент ПпЧ-И содержит признак конца 999.) Это вычисление можно записать на алголе так: s:=0, for k:=0 step 1 until n do s:=s+T[/s]x 10 f (n—k) На каждом шаге цикла происходит прибавление к накапливаемой сумме очередной цифры, умноженной на значение соответствую- щего разряда. Самая старшая цифра ПО] соответствует (п-Ы)-му разряду и поэтому умножается на 10", последующие цифры умно- жаются на 10"“*, ..., 10°=1. Можно предложить другую программную реализацию решения данной задачи, избавившись от многократного выполнения сравни- тельно дорогостоящей операции возведения в степень 10|(п—k). Для этого воспользуемся соотношением xfc=xft_j/10, где хк — коэф- фициент при T[k\ в вычисляемой нами сумме. Мы вычислим только х<>=10", а каждое следующее значение х будем получать делением на 10 предыдущего значения: s:=0; х: = 10 f п; for k‘.=0 step 1 until n do begin s:=s+Hfe] Xx; x:=x/10end Можно обойтись и без начального вычисления 10 f п, если про- изводить суммирование в обратном порядке, от Пп] к ПО]: s:=0; х: = 1; for kt =п step—1 until О do begin s: =s4-H£] X x; x: =x X10 end При каждом выполнении цикла мы производим два умножения и одно сложение. Можно сократить число действий в цикле до од- ного умножения и одного сложения, если воспользоваться выраже- нием для искомого значения s в так называемой схеме Горнера: s=(.. .((ПО] X10.-4-7411) X10+И2]) X10+ПЗ])...) X 10+Пп] Соответственно наша программа получает следующий вид: 8:=П01; for kt=\ step 1 until n do s:=sX 10+ПЛ] Одно из преимуществ схемы Горнера состоит в том, что она поз- воляет написать программу, пригодную для вычисления значения s при любом количестве цифр:
2.10] . ' ОПЕРАТОРЫ ЦИКЛА 97 s:=710]; fe=0; for /г:=/г+1 while 7W=999 dos—sXlO+Ш] В этой программе мы вне цикла выбираем первую цифру Т[0], а затем циклически перебираем последовательные цифры, пока не встретим признак конца 999, который приведет к невыполнению проверяемого в заголовке цикла условия. Заметим, что хотя формально можно сделать величину s пара- метром цикла: 6:=0; for s: =7101, sx10+Ш1 while Ш+И=/=999 do A:=A:+1 для нас такая программа не годится, так как s является искомой ве- личиной, значение которой должно использоваться в дальнейшем, а мы знаем, что значение параметра цикла неопределено после окончания цикла. Здесь в условии написано ТЧб-НЬ^ЭЭЭ, а не 7W¥=999 потому, что в теле цикла производится «настройка» на следующую цифру Tlfc+1], которая добавится к значению s на сле- дующем шаге при вычислении параметра (раньше проверки усло- вия, т. е. независимо от результата этой проверки). Теперь рассмотрим обратную задачу. Пусть дано положитель- ное значение переменной s целого типа, и нужно найти последова- тельные цифры этого числа и переписать их в массив Т. Число s выражается через свои цифры по формуле ss=STr10»4 г=о Самая младшая цифра Тп — коэффициент при 10® получается как остаток от деления s на 10. Целая часть результата деления s на 10 определяется формулой Л-1 s' = 2 1=0 Следующая цифра TB_j получается как остаток от деления на 10 этого числа s' и так далее. В алголе нет операции взятия остатка от деления, но есть операция -j- получения целой части частного. Целую часть результата деления s на 10 мы будем заносить на место той же переменной s оператором s:=s4-10. Соответственно каждая цифра (т. е. остаток от деления очередного значения s на 10) будет определяться выражением s—(s-i-10)X10. Нужные вычисления можно выполнить следующей программой: for k: =0, k+1 while s^0 do begin a[6]:=s—(s-i-10) X10; s:=s-4-10; m:—k end; for k:=0 step 1 until tn do T[k]:=alm—k\\ T(/n+l]:=999 Первый цикл обеспечивает вычисление и занесение в массив а всех цифр исходного числа s. Самая младшая цифра заносится в 40], следующая в all] и так далее до получения в некоем элемента alm] самой старшей цифры. Легко заметить, что цифры накапли- ваются в массиве а в порядке, противоположном требуемому. По- Э. 3. Любнмскнй и др.
98 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 1Гл. 2 этому в следующем цикле эти цифры переписываются в массив Т в обратном порядке: alm] попадает в ПО], alm—1] в ЛИ и так да- лее. В заголовке этого цикла используется значение /и, вычисляе- мое в теле предыдущего цикла. (Оно равно последнему значению параметра, при котором выполнялось тело предыдущего цикла. Сам параметр, как мы знаем, недоступен для непосредственного дальнейшего использования после окончания цикла.) Желательно избежать употребления вспомогательного массива а (так как при реализации алгоритма на ЭВМ этот массив потребует дополнительной машинной памяти, ресурсы которой бывают ог- раничены). Другой недостаток этой программы в том, что в первом цикле два раза вычисляется один и тот же результат s-r-10. Ниже приводится вариант программы, в котором это выражение вычис- ляется в цикле однократно, а вместо массива а употреблена одна вспомогательная переменная: for fe:=0, k+l while s=/=0 do begin i:=s4-10; T[k]:=s—fXlO; s:=Z; m:=kend\ for &:=0 step 1 until (m+1)4-2 do begin a:—Tlk]\ Tlk]:=Tlm—k]; Tim—k]:=a end В первом цикле значения цифр накапливаются сразу в массиве Т, хотя и в обратном порядке. Во втором цикле последовательно ме- няются местами сначала крайние цифры ПО] и Tim], затем соседние с ними ЛИ и Tim—1], и этот процесс продолжается в направлении от краев к центру, пока все цифры не займут нужные места в мас- сиве Т. Поскольку в алголе нет операции, которая сразу меняла бы местами соответствующие значения П&] и Tim—k], эта перестановка выполняется тремя операторами присваивания в теле второго цикла, с использованием простой переменной а для промежуточного хране- ния очередного значения ПАП, на место которого пишется Tim—k]. При каждом выполнении второго цикла в процессе проверки ус- ловия его окончания будет вычисляться одно и то же значение (т+1)4-2. Желательно вынести это инвариантное вычисление из цикла, тогда программа приобретает следующий вид: for&:=0, АН-1 while s#=0 do begin t:=s4-10; Tlk]:=s—ix 10; s:=t; m:=k end; r.=(/n-H)4-2; for k:=0 step 1 until i do begin а:=ЛАП; Tlk]~Tlm—k]\ Tim—AH:=aend Обычно следует особенно тщательно программировать вычисле- ния внутри цикла, так как ускорение этих вычислений дает особый эффект благодаря их многократному выполнению. Существуют не- которые общие приемы оптимизации циклов. К ним относятся: 1) вынесение инвариантных частей счета за цикл; 2) использование для ускорения результатов предыдущего шага цикла; 3) замена более сложных операций на более простые; 4) однократное вычисление одинаковых подвыражений;
2.10] ОПЕРАТОРЫ ЦИКЛА 99 5) исключение операций над константами. Первый прием состоит в том, что если в циклически выполняе- мой последовательности операторов имеются выражения, значения которых не изменяются внутри цикла, то вычисление этих выраже- ний выносится за пределы цикла для того, чтобы эти выражения вычислялись только один раз, а не при каждом выполнении цикла. Рассмотрим, например, оператор цикла for f: = l step 2х£ until п—£+1 do a[i]:=x f 2 X alii На каждом шаге выполнения этого цикла будут заново вычисляться значения выражений п—£+1, х f 2 и 2Х&. Однако видно, что в дан- ном случае эти значения в цикле не изменяются, поэтому нет ни- какой необходимости вычислять их каждый раз заново — доста- точно это сделать один раз, до входа в цикл: Н:=п—&+1; г2:=х|2; гЗ:=2х/?; for r. = l step гЗ until rl do a[i]:=r2Xa[i] Этот же прием был применен, когда в предыдущей программе мы вынесли из цикла вычисление (т+1)4-2. Второй прием позволяет уменьшить число операций на каждом шаге выполнения цикла за счет использования результатов, полу- ченных на предыдущем шаге. Мы применили этот прием в начале данного раздела, когда воспользовались соотношением хй=хА-1/10 между результатом xkt получаемым на шаге й, и результатом xk~lt получаемым на шаге k—1. Третий прием состоит в сведении операции возведения в степень к умножению, умножения — к сложению и т. д., так как a f п=аХа.. .Ха (п раз), аХп=а+а+ .. .+а (п раз). В частности рекомендуется использовать сложение вместо умноже- ния на два, умножение вместо возведения в квадрат. Четвертым приемом мы воспользовались в программе получе- ния цифр по числу, когда избавились от двукратного вычисления выражения S4-10. Пятый прием исключает использование таких операндов, как, например, 1/4 или 10 f 5. (Вместо этого нужно писать соответст- венно .25 или 105.) Последние три приема необходимо применять не только в циклах. Применим теперь рассмотренные приемы оптимизации циклов к решению следующей задачи. Пусть требуется вычислить sin (х) по формуле ^« = £(-1).^ Разумеется, мы не можем суммировать бесконечный ряд, и по- этому задается значение требуемой точности е. Суммирование пре- кращается, как только модуль очередного члена оказывается мень- 4*
100 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 ше, чем е. (Известно, что весь отбрасываемый остаток по модулю меньше этого члена.) Поскольку в алголе нет операции вычисления факториала, нам 2п+1 придется вычислять (2п+1)! по формуле i, которая реализует- 1=1 ся операторами Л—1; for i:=l step 1 until 2xn-H do f:==fyi Сформируем сначала схему нашей программы. Iе. Установить начальное значение 0 переменной s, где будет на- капливаться сумма. 2°. Прибавлять к s очередной член ряда ип, пока |ип|^е. Пункт Г вполне ясен и не нуждается в дальнейшей детализа- ции. Что касается пункта 2°, то он все еще довольно сложен, и по- этому мы напишем для него более детальный подплан, пользуясь дробными «номерами» пунктов: 2.1 е. Для д=1, 2, 3, ... пока |ып|^8 выполнить: 2.2 °. {Вычислить (2п+1)1 2.3 °. Вычислить «„ = (— 1)В^7)Г 2.4 °. Прибавить ип к s) Фигурные скобки, объединяющие пункты 2.2°, 2.3° и 2.4°, оз- начают, что все эти три подпункта нужно выполнять в цикле (пока |un|^e). Составленной нами схеме соответствует такая программа: s:=0; forn:=0, п+1 while abs(un)^eps do begin a:=l; for f:=l step 1 until 2Xn-H do a:=aXi; un:=(—1) f nXxf (2xn-f-l)/a; s:=s+un end Здесь внутри цикла, перебирающего члены ряда, содержится другой цикл вычисления факториала, который выполняется (2п-Н) раз при каждом n-м выполнении внешнего цикла. Еще до составления этой программы мы могли путем рассмотре- ния схемы заметить, что делаем лишние действия, так как в данном случае применим второй прием оптимизации циклов: каждый очеред- ной член un+t может вычисляться с использованием значения пре- дыдущего члена ип. В самом деле, =~' 2. п (2* n -f-'i) ’ ПоэтомУ __ X2 U«+l ~~и* 2.л.(2.л-Н) ’ С учетом этого изменяем детализацию пункта 2 схемы вычисле- ния sin х следующим образом: 2.1°. Присвоить значение и:=х. 2.2°. Для п=1, 2, 3, ... пока |м|^е выполнить s: = s + «; «: = — 2.п.(2.« + 1) • Эта схема также может быть улучшена, поскольку в ней при каж- дом выполнении цикла вычисляется не зависящая от п величина х\ а также дважды производится умножение п на 2. Применим первый
2.11) ОПИСАНИЯ В АЛГОЛЕ 101 прием оптимизации циклов, вынеся вычисление — хг/2 из тела цикла, и получится следующий вариант детализации схемы: 2.1 °. Присвоить значения и:=х, о:=—№/2. 2.2 °. Для п=1, 2, 3, ... пока |ы|^8 выполнить , U'V s^s+u; ---------•-(2.n+ij t Наконец, при вычислении an=n-(2'n+l) можно сэкономить одно умножение, если воспользоваться значением этого выражения, полученным при предыдущем выполнении цикла: а„+1=ап+4п4-3. Полагаем аг=3 и выбираем в качестве параметра цикла величину У=4*п+3. Тогда наша детализированная схема приобретает сле- дующий окончательный вид: 2.1 °. Присвоить значения и‘.—х\ v:=—х2/2; а:—3. 2.2 °. Для N=l, 11, 15,... пока |«|>в выполнить s: = s4-u; = a: = a-f-A(. Соответственно мы получаем программу в виде: s:=0; и:=х; v:~—хХх/2; а:=3; for N'.=l, N+4 while abs (u)^eps do begin s:=s+«; u:=«X»/a; ai=a+N end 2.11. Описания в алголе В алголе принято общее правило: все объекты, употребляемые в программе, должны быть описаны. Поэтому, наряду с операторами, предписывающими определенные действия, в алголе предусмотрена еще одна форма предложений — описания. Каждое описание объяв- ляет объект, вводит его имя, сообщает его вид и сообщает его до- полнительные свойства (для простых переменных это тип значений, а для массивов — тип и структура). Кроме того, каждое описание определяет область существования объекта; это будет рассмотрено в следующем разделе. В каждом месте программы каждое имя долж- но обозначать только один объект. Такими описываемыми объектами могут быть простые перемен- ные (описывается тип одиночных значений), массивы, переключате- ли, процедуры или функции. (описание): := (описание типа) | (описание массивов)! (бпи- сание переключателя) | (описание процедуры) В описании типа определяются простые переменные, а в описа- нии массивов — массивы, элементами которых являются перемен- ные с индексами. С описаниями переключателей мы уже знакоми- лись в разделе 2.7.2, а описания процедур (и функций) рассмотрим позднее. Перечисленным четырем вариантам описаний соответствует че- тыре различных вида имен. Еще один, пятый, вид имен — это мет-
102 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл 2 ::и, которые не описываются отдельными предложениями, посколь- ку сам факт появления идентификатора перед оператором и знаком : считается описанием метки. Например, запись х231:а:=0 означает, что х231 — метка, причем соответствующая именно это- му оператору присваивания. Поэтому если где-то в программе встре- тится оператор перехода go to х231, то следующим будет выполнять- ся этот оператор присваивания. Если в программе метка не описана (например, нет оператора, которому предшествует ALFA:), то нельзя употреблять оператор go to ALFA согласно общему правилу, запрещающему употребление имен неописанных объектов. В описании типа перечисляются идентификаторы определяемых тем самым простых переменных с указанием типа значений этих переменных, т. е. (описание типа): :=(тип) (список идентификаторов) (тип): :=real | integer | boolean (список идентификаторов): := (идентификатор) | (список иден- тификаторов ), (идентификатор) Семантика. Описание типа объявляет о существовании простых переменных указанного типа и закрепляет за ними иденти- фикаторы, перечисленные в списке. Примеры описаний типов: real х integer а, Л, k boolean В, сог2\ Первое из этих описаний означает, что вводится простая пере- менная с именем х, которая будет принимать вещественные значе- ния. Во втором примере описываются три простых переменных, ко- торые будут принимать только целые значения. В третьем описы- ваются две простые переменные, которые могут принимать логиче- ские значения true и false. Если при наличии описаний real х и integer а в программе встре- тится оператор а:=х, то должно быть обеспечено автоматическое округление вещественного значения х до ближайшего целого значе- ния и присваивание этого значения переменной а. Описание массива определяет не только имя и тип значений эле- ментов массива, но и структуру самого массива: его размерность, ко- личество элементов и наборы индексов для нумерации его значений. Тип массива задается так же, как и для простых переменных; ука- занием, что описывается массив, является служебное слово array, а структура массива задается списком граничных пар. Например, граничная пара 0 : 10 означает, что имеется 11 элементов с индек- сами 0,1,2, ..., 10; граничная пара —3 : 5 означает 9 элементов с ин- дексами —3, —2, —1,0, 1,2, 3, 4, 5. Сочетание из двух граничных пар 1 : 7, 0: 8 означает, что в массиве 7 строк по 9 элементов в каж- дой, причем элементы характеризуются парами индексов 1,0; 1,1;... ..., 1,8; 2,0; ...; 7,8. Описание массива может содержать список из
ОПИСАНИЯ В АЛГОЛЕ 103 21 li любого количества граничных пар, т. е. массив может быть любо?, размерности. Например, при описании real array х[—1 : 1, 2:3, 3:6] х представляет собой трехмерный массив в трех параллельных плос- костях (с индексами —1, 0 и 1), причем в каждой плоскости 2 стро- ки (с индексами 2 и 3) по 4 элемента (с индексами 3, 4, 5, 6). Всего в этом массиве 3x2x4=24 элемента. Внутри одного описания допускаются различные структуры мас- сивов. Например, описание integer array х, t/[l, 5], a, X, Y [0:10, От] сразу вводит пять массивов с целочисленными значениями элемен- тов, причем первые два — одномерные массивы из 5 элементов каж- дый, а последующие три — двумерные из 11 Х(п-Н) элементов каж- дый. Мы видим из этого примера, что в граничных парах употреб- ляются не только числа, но и переменные. Вообще в граничных па- рах могут употребляться любые арифметические выражения; эти выражения вычисляются, и вместо них подставляются их числовые значения. В нашем примере х, у, а также а, X, Y — это списки мас- сивов. Части описания х, у[1 : 5] и а, X, У[0: 10, 0: п] называются сегментами массивов. Синтаксис описаний массивов задается так: (граничная пара): ; = (арифметическое выражение): (арифме- тическое выражение) (список граничных пар): ;= (граничная пара) | (список гранич- ных пар), (граничная пара) (список массивов): := (идентификатор массива) | (список мас- сивов), (идентификатор массива) (сегмент массивов): := (список массивов) [(список граничных пар)] (описание массивов): :=аггау (сегмент цдссивов) | (тип) array (сегмент массивов) | (описание массивов), (сегмент мас- сивов ) Семантика. Описание массивов объявляет о существовании массивов переменных значений указанного типа и закрепляет за ними идентификаторы, перечисленные в списках. Граничные пары определяют структуру массивов. К моменту рассмотрения описания массивов значения всех переменных, входящих в выражения гра- ничных пар, должны быть известны. Если в описании массивов тип не указан, то по умолчанию под- разумевается тип real. (Это одно из проявлений принятой в боль- шинстве современных языков программирования так называемой концепции умолчания, которая означает, что для достижения лако- ничности можно опускать некоторые указания, и тогда вместо них автоматически будут подставлены некие стандартные варианты. По- скольку языки программирования рассчитаны на формальное ис-
104 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 полнение алгоритмов, все возможности применения концепции умол- чания явно оговариваются в описаниях языков.) В качестве примера работы с описаниями запрограммируем на алголе универсальную машину Тьюринга. Как мы знаем, в машине Тьюринга имеется бесконечная лента, вдоль которой передвигается автомат. Лента разбита на ячейки, в каждой из которых находится какой-нибудь символ. Автомат может поменять символ в обозреваемой ячейке, руководствуясь при этом программой, заданной в виде таблицы и определяющей (в зависимости от состояния автомата и от обозреваемого символа) три результата: новый символ в этой ячейке, шаг (направление сдвига автомата вдоль ленты) и новое состояние автомата. Все эти три действия кодируются в клетке на пересечении строки, соответствующей состоянию, и столбца, соответствующего обозре- ваемому символу. В отличие от машины Тьюринга, алгол позволяет работать не с символами, а с числами. Однако мы можем пронумеровать воз- можные символы Si на ленте и состояния qj автомата и оперировать вместо них соответствующими им номерами i и /. Нам нужно запрограммировать ввод закодированной ленты и программы машины Тьюринга, а также выполнение одного оче- редного такта работы этой машины (т. е. выполнение действий со- гласно очередной клетке программы), после чего выполнение тактов будет повторяться до появления в очередной клетке программы знака останова !, который мы будем кодировать числом 999. После этого нужно будет вывести кодировку ленты, преобразованной в результате работы нашей машины Тьюринга. Схема нашей алгол- программы выглядит так: 1°. Ввести ленту, программу, номер начального состояния и начальный номер обозреваемой ячейки. 2°. Пока номер состояния </^999, выполнять п. 3°. 3°. Выполнить действия согласно очередной клетке программы. 4°. Вывести ленту. Ленту будем кодировать в виде одномерного массива целых чисел о идентификатором ЛЕНТА. Его описание на алголе имеет вид integer array ЛЕНТА [1: N] Каждому элементу этого массива будет присваиваться значение номера символа, содержащегося в соответствующей ячейке ленты. Таким образом, записи в k-ю ячейку ленты символа s, будет соот- ветствовать оператор ЛЕНТА [fc]:=i. Программа машины Тьюринга — это двумерная решетка, в каждой клетке которой хранятся три числа — коды символа, направления движения и состояния автомата. Ее можно было бы описать при помощи трех двумерных массивов: integer array s 11 : п, 1 : ml integer array h 11 : n, 1 : ml
2.111 ОПИСАНИЯ В АЛГОЛЕ 105' integer array q [1 : п, 1 : т] или, что то же самое, integer array s, h, q [1 : n, 1 : m}, где n — число возможных состояний и т — число различных возможных символов на ленте. Тогда для каждого текущего состояния q{ и обозреваемого символа sj соответствующая клетка программы кодировалась бы значениями s[i, Д, hli, /] и q{i, /]. При такой организации эти три числа оказываются разнесенными по трем различным массивам, в одном из которых выделены все состояния, в другом все направ- ления сдвигов, а в третьем все заносимые символы для данной про- граммы. Однако, для разнообразия мы выберем другую форму и будем описывать программу в виде трехмерного массива ПРОГг integer array ПРОГ [1 : n, 1 : т, 1 : 3] Элемент ПРОГ [t, /, 1] — это числовой код символа, храняще- гося в клетке (i, /) программы. ПРОГ U, /, 2] — код шага (направления сдвига). ПРОГ h', j, 3] — код состояния, в которое нужно перейти со- гласно этой клетке. Сдвиг по ленте влево мы будем кодировать числом — 1, сдвиг вправо — числом +1 и неподвижность — числом 0. Если сопоставить трехмерный массив ПРОГ с рассмотренными нами выше двумерными массивами s, h и q, то ПРОГ [t, /, U=s[t, /] ПРОГ [i, /, 2]—hit, /] ПРОГ [i, /, 31=<7(1, /] Нам потребуются три целых переменных: СОСТОЯНИЕ — номер текущего состояния автомата. АДРЕС — индекс обозреваемой ячейки в массиве ЛЕНТА. СИМВОЛ — код (номер) символа, содержащегося в обозрева- емой ячейке. Теперь мы можем составить схему для пункта 3°, описывающую выполнение действий согласно очередной клетке программы машины Тьюринга. 3.1 °. Присвоить переменной СИМВОЛ значение символа из обозреваемой ячейки ленты. 3.2 °. Записать новый символ в обозреваемую ячейку (присвоить новое значение переменной ЛЕНТА {АДРЕС}). 3.3 °. Сделать шаг (изменить АДРЕС). 3.4 °. Установить новое состояние (изменить значение йеремен- ной СОСТОЯНИЕ). В соответствии с нашей схемой, алгольная реализация универ- сальной машины Тьюринга выглядит следующим образом: integer array ЛЕНТА 11 : М, ПРОГ (1 : п, 1 : т, 1 i 31j integer СОСТОЯНИЕ, АДРЕС, СИМВОЛ; ввод (ЛЕНТА, ПРОГ, СОСТОЯНИЕ, АДРЕС); for СИМВОЛ1.—ЛЕНТА {АДРЕС} while СОСТОЯНИЕ =H=999do begin ЛЕНТА {АДРЕС]:=ПРОГ {СОСТОЯНИЕ, СИМВОЛ, llj
ICG АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 АДРЕС:=АДРЕС+ПРОГ [СОСТОЯНИЕ, СИМВОЛ, 21 СОСТОЯНИЕ:=17РОТ [СОСТОЯНИЕ, СИМВОЛ, 3] end; вывод (ЛЕНТА) В начало алгольного текста мы поместили описания использу- емых величин, а далее следуют операторы, реализующие схему. (Пункт 3.1° мы совместили с заголовком цикла, так как синтаксис алгола все равно требует, чтобы начало заголовка цикла выглядело как оператор присваивания.) Ограниченность алгольной реализации по сравнению с абст- рактной универсальной машиной Тьюринга состоит в том, что массив ЛЕНТА не является бесконечным и его длина ограничена некоторым значением специально введенной переменной АЛ Ана- логично нам пришлось ограничиться определенными значениями п и ш. 2.12. Блоки и локализация В примерах из предыдущего параграфа мы описывали сначала все употребляемые объекты (массивы, простые переменные), а затем писали все операторы, в которых эти объекты используются. Такая конструкция, заключенная в операторные скобки begin и end, называется блоком. Блок — это один из видов операторов языка алгол. Подобно составному и условному операторам, блок явля- ется композиционным оператором, т. е. содержит в себе другие операторы. Отличие блока в том, что он представляет собой неко- торый замкнутый фрагмент программы, использующий внутри себя новые объекты; как правило, так оформляется некое достаточно самостоятельное вычисление. В программе может быть много бло- ков, если она подразделяется на большое количество таких подвы- числений. Синтаксис блока: ^0K>::=begin (список описаний); (список операторов) end (список описаний)::= (описание) | (список описаний); (опи- сание ) Принципиальное отличие блока от составного оператора со- стоит в том, что блок содержит не только операторы, но и описания, причем описания предшествуют операторам. Внешне отличить описание от оператора можно по первому символу: описание может начинаться с символов real, integer, boolean, array, procedure, switch, а оператор — с символов for, go to, if, begin, а также с начальной буквы метки или другого идентификатора (в случае операторов присваивания и процедуры, .в которых за идентификатором следует соответственно знак := или открывающая скобка). Как известно, описания вводят объекты и закрепляют за ними обозначающие их идентификаторы. Блок является областью су-
2.121 БЛОКИ И ЛОКАЛИЗАЦИЯ 107 шествования описанных в нем объектов. Эти объекты определены в пределах того блока, в котором они описаны. Их называют локаль- ными объектами блока, или объектами, локализованными в блоке. Такое закрепление объектов за блоками впервые было введено ав- торами алгола и получило название принципа локализации. Этот принцип в том или ином виде применяется во всех языках про- граммирования. Поскольку идентификаторы закрепляются за объектами, то в пределах блока один идентификатор может обозначать только один объект. Поэтому нельзя одинаково обозначать в одном блоке, например, простую переменную и массив, или переменную и метку. (Закрепление идентификаторов относится и к меткам, хотя они описываются не в начале блока; описанием метки является запись этой метки и двоеточия перед оператором.) Определим синтаксическое понятие алгол-программы'. (алгол-программа ):: = (блок) т. е. все программы, которые мы рассматривали до сих пор, фор- мально не были правильными, так как не представляли собой блоки (Они начинались не с begin, либо в них использовались неописанные идентификаторы.) Теперь мы в первый раз можем написать на алголе полностью правильную программу: begin real х; ввод (х); х:=х+1, вывод (х) end Это блок, в котором описывается простая переменная х и содер- жится три оператора: ввод х, присваивание х нового значения и вывод х. Поскольку блок — это частный случай оператора, то, согласно синтаксису, блоки могут быть вложены один в другой. Объекты, описанные в каждом блоке, могут употребляться только внутри него и становятся недоступными при выходе из этого блока. (По- этому локальные объекты вложенного блока недоступны во внешнем блоке.) Что касается описаний из внешнего блока, то их действие, как правило, распространяется и на внутренний блок; в частности, во внутреннем блоке можно употреблять все те же переменные, что и во внешнем блоке. Так, программа begin integer k\ £:=5; tn\ begin integer f, s; s:=0; for /: = ! step 1 until k do s:=s+Z; вывод (s) end end представляет собой блок, в который вложен внутренний блок с меткой tn. Во внутреннем блоке описываются переменные i и 6 и производятся вычисления с этими переменными и с переменной k, которая описана во внешнем блоке. Заметим, что оператор выводу может находиться только во внутреннем блоке, так как вне его переменная s не определена.
108 . АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 Итак, в каждом блоке могут употребляться объекты, локализо- ванные в нем самом или в одном из объемлющих его блоков. Опи- санные в объемлющих блоках объекты называются глобальными по отношению к этому блоку. В нашем примере глобальной яв- ляется переменная k по отношению к блоку с меткой т. Бывают исключения, когда глобальный объект не доступен во внутреннем блоке, потому что в нем тот же идентификатор описан заново, и тем самым этому идентификатору придан другой смысл. Например, в программе begin integer k; real s; s:=2.5; kt=10; mt begin integer i, s; s:=0; for i:=l step 1 until k do s:=s-H; вывод (s) end; вывод (s) end описаны две разные переменные с одинаковыми идентификаторами s, одна типа real во внешнем, а другая типа integer во внутреннем блоке. Тем самым глобальная переменная s становится недоступной в блоке с меткой т, где вычисляется как сумма арифметической прогрессии значение локальной переменной s. Это значение (равное 55) будет напечатано оператором вывод (s), находящимся в конце внутреннего блока. Оператор вывод (s) из внешнего блока напеча- тает значение глобальной переменной s (равное 2.5), которое было вычислено до входа во внутренний блок, являлось недоступным во время работы этого блока и снова стало доступным, когда мы вер- нулись во внешний блок. Таким образом, случайно совпадающие по имени с локальными глобальные переменные становятся недоступными до выхода из блока. Объекты, локализованные в блоке, порождаются при входе в него и исчезают при выходе. Поэтому результаты, полученные во внутреннем блоке, можно использовать во внешнем блоке, только если присвоить их значения глобальным переменным. В нашем примере мы могли бы в конце внутреннего блока написать k:=s и тогда сохранили бы в переменной k значение локальной переменной s. Локальные переменные — это внутренние, рабочие величины блока. Вся информация, которую он получает или от- дает во внешние блоки, проходит через глобальные переменные. Принцип локализации оказывается весьма полезным при созда- нии больших программ, написание которых не под силу одному человеку. В таком случае организуется группа из нескольких программистов. Руководитель разбивает задание на примерно одинаковые части и составляет схему, определяя при, этом интер- фейс, т. е. глобальные переменные всей программы, через которые ее. части будут взаимодействовать между собой. Каждая частичная программа, которую пишет человек, представляет собой большой блок, в котором описаны свои переменные. При этом обычно нет оснований бояться совпадения локальных идентификаторов в раз-
2.12] БЛОКИ И ЛОКАЛИЗАЦИЯ 109' личных блоках, так как эти блоки не будут вкладываться один в другой, а «параллельно» войдут в один общий блок всей программы, в кагором в соответствии с блок-схемой будут описаны глобальные переменные, обеспечивающие взаимодействие блоков, написанных различными исполнителями. Таким образом, каждый исполнитель может работать независимо, как бы отгородившись от остальных частей программы барьером из операторных скобок begin и end. Если какая-то часть оказывается слишком большой, ее можно снова поделить, определив с помощью более детальной схемы ин- терфейс между ее подчастями, которые тоже становятся блоками. Другое достоинство принципа локализации состоит в том, что блочная структура помогает экономно распределять машинную память при трансляции, т. е. при автоматическое преобразовании алгол-программы в машинную программу. Для каждого массива обычно отводится столько ячеек памяти, сколько элементов преду- смотрено описанием этого массива. В пределах одного блока каж- дый массив и каждая простая переменная помещаются на отдельное место в памяти (в принципе можно применять специальные алго- ритмы экономии памяти, которые иногда позволяют совмещать в памяти разные переменные; описанные в одном и том же блоке, но в большинстве трансляторов этого не делается). Если же имеется несколько независимых (параллельных) блоков, то переменные, локализованные в этих блоках, можно помещать на одно место. Рассмотрим пример. Пусть имеется программа, состоящая иа блоков с описаниями массивов: begin array А, В, С ... begin array х, Y ... begin array и, v, х ... end begin array v, w ... end end; begin array P, Q, R ... end end Для наглядности изобразим блочную структуру этой программы по отношению к описываемым массивам в такдм виде: {Л, В, С {х, У {и, v, х} {о, u»)}{P, Q, R}} 1 а з « а (Нам уже известно, что во втором и третьем блоках описыва- ются разные массивы х, в третьем и четвертом — разные массивы о.) ' Возможна схема размещения этих массивов в памяти, изобра- женная на рис. 2,1. Глобальным массивам А, В, С отводятся отдельные места на все время вычисления программу, так как в любой момент должна иметься возможность присвоить их элементам новые значения или воспользоваться их имеющимися значениями. Массивам из второго
по АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-бО [Гл. 2 и пятого блоков можно отвести одни и те же места, потому что эти блоки, а значит, и области существования их массивов не пересе- каются (т. е. эти массивы существуют не в одно и то же время). Аналогично совмещаются в памяти массивы из блоков 3 и 4. Хотя глобальный массив х из второго блока недоступен в третьем блоке, где имеется локальный массив с тем же именем, тем не менее 1 2 3 Ч 5 Номер блока. Рис. 2.1. Схема размещения массивов в памяти. третий блок не может использовать это место для своих массивов, так как он находится внутри второго блока и, вый- дя из третьего блока, мы «вспомним» массив к и сможем воспользоваться его ранее вычисленными значениями в самом втором блоке, а также во вложенном в него четвертом блоке. Таким образом, свойство недоступности одноименных глобальных переменных не позволяет экономить память, а только создает удобство свободного использования имен. Граничные пары для массивов вы- числяются хпри входе в блок, где эти массивы описаны. Поэтому, если гранич- ные пары являются не числами, а выраже- ниями, то переменные из этих выражений должны быть глобальными и к моменту входа в блок этим глобаль- ным переменным должны быть присвоены значения. (При входе в блок локальные переменные этого блока только возникают и еще не имеют определенных значений, так как не могло быть соответ- ствующих операторов ввода или присваивания.) Например, допустима запись begin integer /и, п; ввод (т, п)\ begin array а [1 : т, 1 : л|; ввод (а) ... но нельзя писать begin integer tn, п\ array а (I : т, 1 : п]. Вычис- ленные при входе в блок значения граничных пар остаются постоян- ными до выхода из этого бдока, даже если внутри него меняются значения глобальных переменных входящих в выражения для граничных пар. Но* при каждом новом входе в блок граничные пары вычисляются заново, с учетом происшедших изменений зна- чений глобальных переменных. Рассмотрим пример программы: begin integer i\ for i:=l step 1 until 10 do begin array all: Л, Ы1 : 2хЛ; integer k\ for &:=1 step 1 until i do begin a[k]:=kXi*> b[2xk—l]:=fe[2xfc]:=2x&+f end; вывод (a, b) > end end
2 Л 31 ПРОЦЕДУРЫ И ФУНКЦИИ П1 Во внутреннем блоке массивы а и b имеют переменные границы. При каждом из десяти последовательных выполнений внешнего цикла происходит выполнение внутреннего блока с новыми гра- ницами для а и Ь\ каждый раз массив а увеличивается на один элемент и массив b увеличивается на два элемента. Таким образом, выполняемый в этом блоке оператор вывод (а, Ь) будет с каждым разом выдавать все более длинные последовательности элементов массивов а и Ь. При ( = 1 печатаются числа 1 3, 3 При i=2 печатаются числа 2, 4 4, 4, 6, 6 При /=3 печатаются числа 3, 6, 9 5, 5, 7, 7, 9, 9 и т. д. При t=10 будут напечатаны следующие две группы чисел: 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 и 12, 12, 14, 14, 16, 16, 18, 18, 20, 20, 22, 22, 24, 24, 26, 26, 28, 28, 30, 30 Хотя метки не описываются при входе в блок, они локализу- ются в ближайшем объемлющем их блоке. Вне блока не может быть выполнен оператор перехода на метку из этого блока, поскольку эта метка (как и все локальные объекты блока) доступна только операторам, находящимся в самом этом блоке. Поэтому нельзя войти в блок иначе, чем через его начало; однако выйти из блока можно не только через его конец, но и с помощью оператора пере- хода (на метку из внешнего блока). 2.13. Процедуры и функции Процедура — это средство, позволяющее многократно исполь- зовать в разных местах программы один раз описанный фрагмент алгоритма. В математике (и в других областях науки) принято ссылаться на один раз описанные объекты. Например, введя функцию г/ \ х2+#2 f (Х> У) ~ sjn (Х — у) ’ мы можем потом в математическом тексте употреблять f(l, 3), /(7, 83), f(a, b+c). Здесь f — имя функции, которое вводится, чтобы отличать разные функции друг от друга. Величины х, у — формальные параметры (аргументы), они используются только для обозначений, чтобы описать данную функциональную зависимость. Пары аргументов (1, 3), (7, 83), (а, Ь+с) представляют собой списки фактических параметров, по которым фактически будет вычисляться функция. Количество формальных и фактических параметров
112 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 1Гл. 2 должно быть одинаковым. Соответствие между ними устанавлива- ется порядком написания в списке: первый фактический параметр 1, 7 или а подставляется вместо первого формального параметра х, второй фактический 3, 83 или b+с подставляется вместо второго формального параметра у. В алголе сохранен тот же принцип работы с функциями, но он сформулирован более четко и широко развит, благодаря чему алгольные процедуры дают пользователю гораздо больше возмож- ностей, чем обычная математическая символика функциональной зависимости. 2.13.1. Простейшие процедуры. Мы не будем сразу излагать все возможности процедур, а дадим сначала определение простейшего вида процедуры, и затем будем постепенно дополнять и расширять его, пока не придем к окон- чательному определению. Итак, (описание процедуры) ::=procedure (идентификатор проце- дуры > ((список формальных параметров)); (тело процедуры) (идентификатор процедуры):: = (идентификатор) (формальный параметр )::= (идентификатор) (тело процедуры):: = (оператор) Напомним также определение оператора процедуры (см. раздел 2.7.3): (оператор процедуры )::= (идентификатор процедуры) ((список фактических параметров)) Как и в списке фактических параметров оператора процедуры, разрешается, чтобы элементы списка формальных параметров в описании процедуры разделялись либо запятыми, либо конструк- циями вида ) (строка букв):(. Употребление любого такого огра- ничителя параметра никак не сказывается на выполнении проце- дуры. Нет необходимости воспроизводить в списке фактических параметров разделители из соответствующего списка формальных параметров. Например, при описании procedure f(x) высота: (у) ... можно написать оператор процедуры f(a, 5). Семантика описания и оператора процедуры задается следующими правилами выполнения оператора процедуры. 1. Отыскивается описание процедуры с этим идентификатором. При этом учитывается принцип локализации идентификаторов: сначала выясняется, не описан ли этот идентификатор в том же блоке, где содержится оператор процедуры, если нет, то проверяется объемлющий блок, затем следующий объемлющий блок и так далее. Если ни в одном из объемлющих блоков не окажется искомого описания, то это будет свидетельствовать об ошибке в программе.
? I3J ' ПРОЦЕДУРЫ И ФУНКЦИИ 113, 2. В найденном описании процедуры все вхождения формальных параметров в оператор, являющийся телом процедуры, заменяются нан соответствующие им фактические параметры из данного опера- тора процедуры. (Исключением являются параметры-значения, которые будут рассмотрены в разд. 2.13.5.) 3. Затем получившийся в результате замены оператор выпол- няется так, как если бы он был написан на том месте, где написан оператор процедуры. Когда вместо формальных подставляются фактические пара- метры, они там, где нужно, берутся в скобки. Так, в рассмотренном примере при подстановке вместо у значения Ъ+с (и вместо х значе- ния а), мы получаем выражение ' sinfx—(6-f-c)) Если тело процедуры представляет собой блок и в нем описаны объекты с такими же именами, как и в фактических параметрах выполняемого оператора процедуры, то, чтобы не возникало пу- таницы при подстановке фактических параметров, локальные объ- екты тела процедуры автоматически переименовываются, т. е. их идентификаторы заменяются на другие. Из третьего правила выполнения оператора процедуры следует,, что после выполнения тела будет выполняться оператор, следую- щий за оператором процедуры. В целом соответствие фактических и формальных параметров, должно быть таким, чтобы в результате подстановки тело проце- дуры осталось правильным оператором языка алгол. Рассмотрим пример. Пусть требуется вычислить выражения 10 В*-1 10 k = s а = S sin7, 6= s ^[/]- 1=1 /=5 /=6 Все три выражения представляют собой результаты сумми- рования, но в первом складываются квадраты последовательных целых чисел, во втором — синусы, а в третьем последовательные- элементы некоторого вектора. Алгол позволяет написать одну процедуру суммирования, которую можно использовать во всех трех случаях: procedure СУМ(/) по: (т) от: (х) до: (у) рез: (s); begin s:=0; for m:=x step 1 until у do s:—s+f end Формальными параметрами этого описания- процедуры являются суммируемая величина f, параметр цикла суммирования т, началь- ное и конечное значения этого параметра х и у и переменная $, которой нужно присвоить значение результата суммирования. Программа вычисления трех искомых выражений может выгля- деть так:
114 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл 2 begin real k, a, b, В-, integer i, j; array P [6 : 10]; procedure СУМ (f) no: (m) от: (x) до: (у) рез: (s); begin s:=0; for m:=x step 1 until у do s:=s+f end; ввод (В, P): СУМ (if 2, i, 1, JO, k); СУМ (sin(j), j, 5, B|2—1, а); СУМ(Р1)], j, 6, 10, b); вывод (k, a, b) end При выполнении первого оператора процедуры СУМ (i f 2, i, 1, 10, k) на его место подставляется составной оператор, являющийся телом процедуры СУМ, с заменой параметров: if2 вместо /, i вместо т, I вместо х, 10 вместо у и k вместо s. Этот оператор при- обретает вид: begin й:=0; for i: = l step 1 until 10 do k:=k+i]2 end В результате его выполнения значением k становится искомая ю сумма X i 12. Аналогично при выполнении второго оператора процедуры тело процедуры приобретает вид: begin а:=0; for /:=5 step 1 until Bf2—1 do a:=a+sin(j) end При выполнении третьего оператора процедуры вместо него подставляется составной оператор: begin b:—0-, for 6 step 1 until 10 do b:=b+Plj] end Таким образом, изменяя фактические параметры, мы можем менять не только исходные значения параметров, но и формулы выражений, по которым будет вестись счет. 2.13.2. Локализация процедур. Как и все алгольные описания, описание процедуры локализу- ется в том блоке, где оно написано. Иначе говоря, описанной в блоке процедурой можно пользоваться в самом этом блоке и в со- держащихся в нем блоках, но нельзя обращаться к ней из других блоков. В теле процедуры могут встречаться формальные параметры, локальные переменные блоков, входящих в тело процедуры (если такие блоки имеются), и глобальные переменные из объемлющих блоков. Рассмотрим пример: begin real d; procedure P(x, n)\ begin integer i; for i: = l step 1 until n do d:=d+xli] end; d:=0; begin array b П : 21; real a; a:=3; bl 1 J: =£121:=1; P(b, 2); вывод(а) end; вывод(а) end Здесь имеются три блока: блок программы, тело процедуры и внутренний блок программы. В теле процедуры описана локальная
2.13) ПРОЦЕДУРЫ И ФУНКЦИИ 115- переменная I, в общем и внутреннем блоках описаны одноименные переменные а. (Чтобы читателю было удобнее их отличать, мы специально ввели обозначение &.) Одним из параметров процедуры, является идентификатор массива, элементы которого нужно про- суммировать и прибавить к исходному значению глобальной пере- менной а. Возникает вопрос, какая из двух одноименных перемен- ных а имеется в виду. Одна из них описана в блоке, объемлющем описание процедуры, а другая — в блоке, объемлющем оператор процедуры. Согласно правилам алгола, глобальными по отношению к телу процедуры считаются переменные из блоков объемлющих описание процедуры. Поэтому в нашем случае оператор Р[Ь, 21 в результате подстановки заменяется на блок begin integer i; for t: = l step 1 until 2 do d:=d+b[i] end где a — переменная а, описанная в общем блоке. При выполнении- этого оператора будет взято исходное значение d, равное 0, и к нему прибавятся Ml 1=1 и М2]=1. Затем произойдет вывод зна- чения локальной переменной а из внутреннего блока. Это значение- было вычислено оператором присваивания а:=3 и больше не из- менялось. Следующий оператор вывода выдаст значение глобальной; переменной а=2. Таким образом, напечатается сначала число Зг а потом 2. 2.13.3. Ограничения на фактические параметры. Как правило, описание процедуры накладывает определенные- ограничения на фактические параметры, которые могут подстав- ляться вместо употребляемых в теле процедуры формальных пара- метров. Эти ограничения вытекают из общего правила: в результате- подстановки должен получиться оператор, правильный с точки зрения языка алгол. (Заметим, что и само тело процедуры по оп- ределению должно быть правильным оператором.) Например, пусть процедура F описана следующим образом: procedure F(a, b, с, d, е, f, g; h, i, k)\ begin ai=b:=c+dli\-, e(fy, go to if g then h else М2] end Из этой записи можно вывести следующие ограничения на фак- тические параметры данной процедуры. Фактические параметры, соответствующие формальным пара- метрам а и Ь, должны быть переменными, причем либо обе типа integer, либо обе типа real, поскольку они употребляются в левой части одного оператора присваивания, правой частью которого^ является арифметическое выражение. Фактический параметр, со- ответствующий с, должен быть арифметическим выражением; в. частном случае это может быть число или переменная. Формаль- ному параметру d должен соответствовать фактический параметр,, являющийся идентификатором массива, причем обязательно од-
116 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 номерного. (Нельзя, чтобы этот фактический параметр был пере- менной с индексами. Если бы мы подставили г[1] вместо d, то в теле процедуры получилась бы неправильная запись г[1 ] [Л.) Фор- мальному параметру I должно соответствовать арифметическое выражение. Фактический параметр, подставляемый вместо пара- метра е, должен быть идентификатором процедуры. Что касается формального параметра f, то мы не можем наложить никаких ог- раничений на соответствующий ему фактический параметр, если не знаем, какой конкретный фактический параметр соответствует формальному параметру е. (При разных заменах параметра е воз- никают разные ограничения на замену параметра f.) Фактический параметр, соответствующий формальному параметру g, должен быть логическим выражением. Параметру h должно соответствовать именующее выражение. Параметр k должен заменяться на иденти- фикатор переключателя, причем в соответствующем переключа- тельном списке должно быть не менее двух элементов. 2.13.4. Спецификации. На предыдущем примере мы увидели, что анализируя тело про- цедуры, можно узнать об ее возможных фактических параметрах многое, но не все. Например, мы ничего не выяснили о том, каким может оказаться фактический параметр, подставляемый вместо /. Мы узнали, что параметры а и b должны быть одинакового типа, но не знаем, какого именно: integer или real. Между тем от этого может существенно зависеть выполнение оператора процедуры. Например, в описании процедуры procedure f(a); а: =0.6; тело является оператором присваивания, в правой части которого на- ходится вещественное число, а в левой — формальный параметр. При выполнении оператора f(x), если фактический параметр х будет переменной типа integer, то потребуется округлить результат и присвоить .переменной х значение 1. Если же переменная х типа real, то ей будет присвоено без округления значение 0.6. Таким образом, присваивание выполняется с округлением или без него в зависимости от типа фактического параметра. В алголе разре- шается писать такие неопределенные описания процедур, так как в момент выполнения соответствующего оператора процедуры, после подстановки фактических параметров, в любом случае полу- чится правильный оператор и тогда будет ясно, нужно ли выпол- нять округление результата. Однако, если автор программы за- ранее знает, какого типа должен быть фактический параметр дан- ной процедуры, то, чтобы облегчить трансляцию и понимание программы, он может указать этот тип в описании процедуры в виде так называемой спецификации. В нашем случае с включением спецификации описание процедуры может получиться в виде: procedure f(a); integer а; а-. =0.6;
2.13] ПРОЦЕДУРЫ И ФУНКЦИИ 117 Посредством этой спецификации мы сообщили, что вместо формаль- ного параметра а можно задавать фактический параметр только типа integer. Теперь, если переменная х типа real, то оператор f(x) становится незаконным. В общем случае спецификации помещаются в описании про- цедуры перед ее телом и в них сообщаются типы формальных пара- метров, т. е. типы фактических параметров, которые могут под- ставляться вместо этих формальных. Если процедура рассчитана на фактический параметр разных типов, то для соответствующего формального параметра спецификация не указывается. С учетом спецификаций синтаксис описания процедуры можно несколько уточнить: (описание процедуры) ::=procedure (идентификатор проце- дуры) ((список формальных'параметров)); (совокупность спецификаций) (оператор) Совокупность — это список, который может быть и пустым: (совокупность спецификаций)::= (пусто) | (список специфи- каций ); (список спецификаций)::= (спецификация) | (список специ- фикаций ); (спецификация) (спецификация):: = (спецификатор) (список идентификаторов) (спецификатор )::=real | integer | boolean | array | real airay | integer array | boolean array | switch | string | procedure j real procedure j integer procedure | boolean procedure | label (список идентификаторов)::= (идентификатор) | (список иден- тификаторов ), (идентификатор) — Спецификаторами выражаются свойства, которые можно ука- зывать при формальных параметрах как характеристику тех фак- тических параметров, которые должны соответствовать этим фор- мальным. Мы видим, что большинство спецификаторов совпадает со служебными словами описаний, с которыми мы познакомились в разд. 2.11. Кроме того, допускаются спецификаторы string (строка) и label (метка). Отличие спецификации от описания состоит в том, что спецификация не вводит нового объекта, а только сообщает о свойствах уже введенного формального параметра. Например, спецификация array х (или, что то же самое, real array х) означает, что формальному параметру должен соответст- вовать в качестве фактического параметра идентификатор вещест- венного массива; спецификация real у означает, что соответствую- щий фактический параметр должен быть арифметическим выраже- нием. При спецификации label г соответствующим фактическим параметром должно являться именующее выражение, а при специ- фикации string и соответствующий фактический параметр должен
118 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 (Гл. 2 быть строкой (т. е. заключенной в кавычки последовательностью символов алгола). 2.13.5. Параметры-значения. t В разделе 2.13.1 мы рассмотрели обращение к процедуре СУМ оператором CyM(sin(j), j, 5, Bf 2—1, а). В результате подстановки фактических параметров тело процедуры приобрело вид: begin а:=0; for /:=5 step 1 until В\2—1 do a:=a+sm(j) end Недостаток этой процедуры состоит в том, что при каждом вы- полнении цикла заново вычисляется одна и та же величина В f 2—1. Лучше было бы в самом начале узнать, чему она равна, и в даль- нейшем использовать это значение. С такой точки зрения параметры естественно разделяются на два класса. Для одних параметров важно, как они написаны, потому что в таком виде их необходимо подставить в тело процедуры. (В нашем примере это фактические параметры sin(j), j, а, подставляемые вместо формальных пара- метров /, /и, s.) Для других параметров не имеет значения форма их записи, о таком параметре нужно только знать, чему равно его значение перед выполнением процедуры, так как именно это зна- чение фактически требуется подставить в тело процедуры. Таковы в нашем примере параметры 5 и В|2—1, подставляемые вместо формальных параметров х и у. В алголе есть возможность выде- лить при описании процедуры формальные параметры, для которых важно знать только значения фактических. Такие формальные параметры называются параметрами, вызываемыми по значению или просто параметрами-значениями. Остальные формальные пара- метры называются параметрами, вызываемыми по наименованию. или заменяемыми параметрами. Параметры-значения перечисляются в специальном списке зна- чений, который начинается служебным словом value (значение) и пишется сразу после списка формальных параметров. С исполь- зованием такого списка описание процедуры СУМ может выгля- детьтак: procedure СУМ(/, т, х, у. s); value х, у\ integer т. х, у\ real /, s; begin s:=0; for /n:=x step 1 until у do s:=s+f end Включенные в список значений формальные параметры х и у не будут заменяться в теле процедуры на соответствующие им фак- тические. Вместо этого, в начало тела будут добавлены описания переменных х и у типа integer (в соответствии с их спецификацией). Тем самым тело данной процедуры превратится из составного опе- ратора в блок. Кроме того, при обращении к процедуре в начало этого блока будут автоматически включены операторы присваи- вания переменным х и у значений соответствующих фактических
2.13] . ПРОЦЕДУРЫ И ФУНКЦИИ 119 параметров. Параметры Д т и s, не включенные в список значений, будут обычным образом заменяться на соответствующие фактические. В частности, при выполнении оператора процедуры СУМ {sin (j), j, 5, 73 f 2 — 1, a) па его место будет подставляться соответственно блок begin integer х, у\ х:=5; у.=В f 2—1; for j:=x step 1 until у do a:=a+sm(j) end Аналогично будут подставляться блоки с описанием integer х, у и при других обращениях к процедуре СУМ. Параметры, входящие в список значений, обязательно должны входить и в список спецификаций. Неправильное включение параметра в список значений приво- дит к ошибочной программе. Так, в нашем примере, если бы мы включили в список значений параметр /, то, присвоив ему началь- ное значение, мы потом в цикле все время прибавляли бы это зна- чение, не изменяя его. Например, оператор СУМ (i 12, Д 1, 10, k) заменился бы на блок begin real Д integer х, у\ х: = 1; у: = 10; for к=х step 1 until у do k\=k+f end Аналогично, если включить в список значений параметр т, то, например, при обращении СУМ (Plj], j, 6, 10, b) при подстановке фактического параметра j произойдет начальное присваивание m:=j, после чего в теле процедуры не произойдет подстановки / вместо имени т. В результате при выполнении цикла будет ме- няться значение/n, a j останется неизменным, так что подставляемый вместо f параметр P1/J будет при каждом суммировании §+/ браться с одним и тем же значением /: begin integer т, х, у\ х:=6; у: = 10; for т:=х step 1 until у do b:=b+P[j] end Если в список значений некоторой процедуры включен формаль- ный параметр х со спецификацией array х (или real array х, или integer array х, или boolean array х), то при обращении к этой про- цедуре в начало ее тела будет автоматически включено описание массива х с такими же граничными парами, как в описании массива, выступающего в роли фактического параметра, а также будет включен цикл присваивания всем элементам массива х значений соответствующих элементов массива — фактического параметра. Таким образом, возникают дополнительные расходы времени и места, и поэтому включение таких фактических параметров в спи- сок значений не всегда целесообразно (особенно, если массив со- держит много элементов).
120 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Формальный параметр, специфицированный как переключатель (switch х), или как процедура (procedures) или как строка (strings), нельзя включать в список значений. 2.13.6. Процедуры-функции. К описанию обычной процедуры можно обращаться только опе- ратором процедуры. Однако в алголе имеется возможность описать процедуру, к которой можно обращаться и как к функции, являю- щейся операндом выражения. После выполнения функции должно остаться значение, которое будет использоваться как операнд. Для этого разрешается писать в теле процедуры не совсем обычный оператор присваивания, у которого в левой части находится само имя этой процедуры. Например, можно описать: real procedure F(s, у)', F-.—x+y, Это присваиваемое значение считается значением функции. Вычисленное значение функции используется как мгновенное зна- чение операнда и не сохраняется для дальнейшего использования. Если оно требуется повторно, то его нужно вычислять заново, например: fe:=(a+F(:, c+6))xF(i, c+b) либо при первом вычислении указывать как правую часть опера- тора присваивания, например: + =F(j, с+6); 6:=(a+d)Xd; В теле процедуры-функции может выполняться большая про- грамма. Если в ней несколько раз присваиваются значения иден- тификатору процедуры, то в качестве значения функции берется результат последнего присваивания. Идентификатор процедуры нельзя использовать в ее теле как операнд, а можно употреблять только в левой части оператора присваивания. Бывает так, что имеет смысл обращаться к одной и той же про- цедуре попеременно: иногда как к функции, а иногда как к опе- ратору. Такая возможность обеспечивается тем, что к процедуре, допускающей обращение в виде функции, можно обратиться и как к оператору: ...; F(i, с+6); ... При этом происходит обычное выполнение оператора процедуры. В теле процедуры производится присваивание значения иденти- фикатору F процедуры, но это значение не может использоваться и сразу же пропадет. (Однако могут сохраниться результаты других вычислений, выполненных в теле процедуры, если в этом теле про- изводятся присваивания значений глобальным переменным.) Итак, первая особенность процедур, допускающих обращение в виде функций, состоит в присваивании значения идентификатору процедуры. Вторая особенность (логически вытекающая из первой) состоит в том, что требуется снабжать описание такой процедуры
2.13] ПРОЦЕДУРЫ И ФУНКЦИИ; 4 121 типом, который приписывается вырабатываемому этой процедурой операнду — функции. Например, при описаниях integer d\ real procedure F... если встречается оператор d‘.=F(i, сА-Ь"), то алгол предписывает округление вещественного значения функции F, чтобы получить целое значение d; при описании integer procedure F... такое округ- ление не потребуется. Чтобы еще раз продемонстрировать влияние фактических па- раметров на алгоритм, описываемый телом процедуры, рассмотрим следующий пример процедуры-функции, вычисляющей скалярное произведение двух векторов: real procedure СКАЛ ПР (х, у, i, n); real х, у, integer i, n; begin real s; s:=0; for t: = l step 1 until n do s:—s+xXy, СКАЛПР-.—s end (Нам пришлось ввести переменную s для накопления суммы, по- тому что нельзя употребить оператор СКАЛПР-.=СКАЛПР-\-хху, содержащий идентификатор процедуры СКАЛПР в своей правой части.) Пусть эта функция используется в выражении из правой части в следующем операторе присваивания: Ь-.==\+СКАЛПР(А[(\, Bill, i, 10)XC7GW7P(D[l, /], Dl/, 1], /, Ю)+СКАЛПР(АЦ— 1], x\(i— 1), i, n+1); При вычислении выражения мы сначала находим значения всех операндов в порядке слева направо, а затем выполняем операции над ними. В нашем случае заранее как бы заготавляются места для четырех операндов: единицы и трех значений функции СКАЛПР. Получив от процедуры значение СКАЛПР, мы запоминаем его как значение очередного операнда, и оно тут же забывается как СКАЛПР.- Иначе говоря, как значение идентификатора СКАЛПР оно существует только в момент непосредственно после обращения, а в дальнейшем не может быть использовано. При первом обращении из нашегр выражения тело процедуры после подстановки будет содержать оператор цикла: for t:=l step 1 until 10 do s:=s-MUlxB[i] Здесь для двух векторов А и В вычисляется их скалярное про- изведение (сумма попарных произведений элементов этих векторов). Во второй раз мы скалярио.умножаем первую строку матрицы D на первый столбец той же матрицы: for /:=1 step 1 until 10 do s:=s+D [1, j]XD[j, 1] В последний раз оператор цикла приобретает вид: for j:=l step 1 until n+1 do s:=s+AU—1]хх|(/—1)
122 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 Тем самым вычисляется многочлен причем массив А представляет собой вектор коэффициентов данного многочлена. 2.13.7. Полный синтаксис описания процедуры. Теперь мы можем систематизировать те элементы синтаксиса описания процедуры, с которыми познакомились в разд. 2.13. (описание процедуры):: = (заголовок процедуры) (оператор) (заголовок процедуры):: =procedure (идентификатор процеду- ры) (совокупность формальных параметров); (совокупность значений) (совокупность спецификаций) | (тип) procedure (идентификатор процедуры) (совокупность формальных параметров); (совокупность значений) (совокупность спе- цификаций ) (совокупность формальных параметров)::= (пусто) | ((список формальных параметров)) (совокупность значений)::= (пусто) | value (список иденти- фикаторов ); (совокупность спецификаций)::= (пусто); | (список специфи- каций ); Заметим, что в силу нашего синтаксиса допускаются процедуры не только без списка значений и списка спецификаций, но и без списка формальных параметров, например: real procedure /; /: = 1; В данном случае процедура-функция f присваивает своему идентификатору всегда значение 1. Возможны более содержатель- ные процедуры без параметров, производящие вычисления над глобальными переменными и над локальными переменными тела процедуры. 2.14. Рекурсивные процедуры Мы знаем, что телом процедуры может служить любой опера- тор. В нем могут встречаться операторы процедуры, и тогда во время выполнения данной процедуры выполняются те процедуры, которые соответствуют этим операторам. Если при выполнении некоторой процедуры возникает необходимость обратиться к ней же самой, то такая процедура называется рекурсивной. Рекурсив- ная процедура/может обращаться самак себе или непосредственно» или косвенно; в последнем случае гфм^едура f обращается к другой процедуре Д, которая непосредственно или косвенно обращается к процедуре f. Случай, когда каждое обращение к процедуре f влечет за собой еще одно обращение к той же процедуре, не представляет прак- тического интереса, так как сводится к бесконечному циклу. В ре- альных алгоритмах обращения из рекурсивной процедуры к ней
2.141 РЕКУРСИВНЫЕ ПРОЦЕДУРЫ 123 самой происходят не бесконечно, а при выполнении некоторых -условий, которые когда-нибудь исчерпываются. Ключом к пони- манию рекурсии является правило выполнения оператора проце- дуры, состоящее в замене формальных параметров в теле проце- дуры на фактические и в подстановке преобразованного таким образом тела на место оператора процедуры. В качестве примера рассмотрим рекурсивную процедуру вы- числения факториала: procedure ФАКТ (п) РЕЗУЛЬТАТ1, (f)-, integer п, f\ If п=1 then f: = l else begin ФАКТ (п—1, /); f:=nxf end Проследим, как будет выполняться эта рекурсивная процедура в программе begin integer k\ procedure ФАКТ (n, f)...; ФАКТ (3, k)\ вывод (k) end (Мы обозначили многоточием описание процедуры ФАКТ, приве- денное выше.) Подставляя вместо оператора ФАКТ (3, k) тело процедуры с заменой параметра f на k и параметра п на 3, мы получаем: if 3=1 then k: — l else begin ФАКТ (3—1, k); k:=3xk end В этом условном операторе, поскольку 3=/=1, нужно выполнять оператор процедуры ФАКТ (3—1, к). Аналогично подставляем вместо него тело процедуры с заменой п на 3—1 и заменой f на к и получаем следующий расширенный оператор: if 3=1 then k: = \ else begin if 3—1 = 1 then k: = \ else begin ФАКТ (3—1—1, k)-, k:=(3— l)Xk end; k:=3xk end Выполнять этот оператор нужно с того места, куда только что подставлено тело процедуры ФАКТ, т. е. с первой операторной скобки begin. Мы снова получили текст с оператором процедуры ФАКТ, ко- торый нужно выполнять, так как 3—1#=1. Подставляем на место этого оператора тело процедуры ФАКТ на этот раз с заменой п на 3—1—1 и f на k. Получаем if 3=1 then /г. = 1 else begin if 3—1=1 then k: = \ else begin if 3—1—1=1 then /г:=1 else begin ФАКТ (3—1—1—1, *); fc:=(3—1—l)x£ end; fe:=(3—l)Xfe end; ki=3xk end Программа снова содержит оператор процедуры ФАКТ, однако на этот раз не нужно выполнять этот оператор, потому что условие 3—1—1 = 1 является истиной. В содержащем это условие условном операторе выполняется присваивание Л:=1. Затем выполняется
!24 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 £Гл. 3 оператор й:=(3—l)Xfe, который дает результат k=2, после чего выполнение оператора k:=3xk приводит к получению окончатель- ного результата k=6. На этом выполнение оператора ФАКТ (3, k) завершено, и следующим выполняется оператор вывод (k). Рекурсивное выполнение процедур возможно и при обращении к функциям. Так, нашу процедуру вычисления факториала можно переписать в виде: integer procedure ФАК(п)\ integer п; if n=l then ФЛК: = 1 else ФАК:=пХФАК(п— 1); По сравнению с предыдущим описанием процедуры ФАКТ, это описание немного укоротилось, так как отпала необходимость выполнять оператор процедуры ФАКТ (п—1, k), а вместо этого непосредственно в выражении используется как множитель функ- ция ФАК(п—1). Последовательность рекурсивных обращений в этом случае будет аналогична предыдущей. Пусть исходным был - оператор 1г:=ФАК(3). Чтобы вычислить значение функции, выпол- няется тело: if 3=1 then ФАК: = 1 else ФА К: =ЗхФА Я(3—1) Условие 3=1 не верно, и поэтому вычисляется ФАК(3—1) посред- ством выполнения тела: if 3—1 = 1 then ФЛК:=1 else ФЛК:=(3— \)ХФАК (3—1—1). Снова условие 3—1 = 1 не верно, и мы вычисляем ФАК (3—1—1), выполняя тело if з_1—1 = 1 then ФАК‘.=\ else ФАК:=(3— 1—1)ХФЛК(3— 1 — 1-D На этот раз условие верно, производим присваивание ФАК=1 и это значение подставляем вместо ФАК(3—1—1) в выражение для вычисления ФАК(3—1). Получаем ФАК=(3—1)’Х1=2 и под- ставляем это значение в выражение для вычисления ФАК (3), что дает нам ФЛЛ=ЗХ2=6. Это окончательное значение ФАК присваивается переменной k из левой части оператора k: =ФАК(3). Заметим, что можно (а для ускорения выполнения процедуры и нужно) включить параметр п в список значений: value п. Иначе, если соответствующим фактическим параметром является ариф- метическое выражение, оно каждый раз будет вычисляться заново. Мы этого не сделали, чтобы не усложнять изложение выполняемых подстановок тела процедуры. Преимущество рекурсивных процедур состоит в том, что они позволяют обеспечить хорошую наглядность. Например, наше описание рекурсивной процедуры ФАК похоже на обычное мате- матическое определение факториала: 1!=1, nl=nX(n—1)!. Однако с точки зрения скорости вычислений такая процедура менее эффективна, чем вычисление факториала при помощи обык- новенного цикла: /: = 1; for t: = l step 1 until n do f:=fxi
2 15J ИСПОЛЬЗОВАНИЕ КОММЕНТАРИЕВ 12J Рекурсивными процедурами нужно пользоваться не в таких тривиальных случаях, когда ясно, как обойтись без рекурсии. Она всегда замедляет счет, но приносит облегчение в некоторых слу- чаях, когда без нее трудно описать вычислительный процесс. , Еще одним примером употребления рекурсии может служить следующая процедура вычисления чисел Фибоначчи: integer procedure ФИБ(п); value п; integer n; ФИБ: =if n—Q then 0 else if n=l then 1 else ФИБ (п—1)+ФИБ(п—2) В описании этой процедуры выражен тот факт, что каждое число Фибоначчи, начиная с третьего, является суммой двух пре- дыдущих. Потеря эффективности за счет рекурсии проявляется в данном случае отчетливо: на каждом шаге мы выполняем, как правило, два новых обращения к процедуре ФИБ, а всего при вычислении ФИБ(п) число рекурсивных обращений при увеличении значения п возрастает по закону геометрической прогрессии. То же самое вычисление можно выполнить, не прибегая к ре- курсии, если воспользоваться следующей процедурой: integer procedure ФИБ1 (п); value и; integer и; begin integer i, ФИБ, ФИБп, ФИБпп: if n=0 then ФИБ\:=0 else if n=l then ФИБ\:~1 else begin ФИБп: = 1; ,ФИБпп: =0; for i:=2 step 1 until n do begin ФИБ:=ФИБп+ФИБпп: ФИБпп:=ФИБп; ФИБп:—ФИБ end; ФИБ\:=ФИБ end end В принципе, любую алгол-программу с рекурсией можно за- менить на эквивалентную ей алгол-программу без рекурсии. 2.15, Использование комментариев Язык алгол позволяет включать в программу любые поясни- тельные тексты в виде комментариев, которые предназначаются только для людей и игнорируются при машинном исполнении про- граммы. Синтаксис алгола предусматривает специальные правила оформления таких комментариев, позволяющие формально усга- овить, где начинается и где кончается текст комментария. Допускаются комментарии двух типов: (комментарий первого типа)::=соттеп1 (любой текст, не содержащий точку с запятой);
426 ' АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 (комментарий второго типа )::= (любой текст, не содержащий символов ; или end или else) Комментарий первого типа можно писать в алгол-программе после любого символа begin или после любой точки с запятой. Комментарий второго типа можно писать после end. Признаком того, что такой комментарий закончился, служит появление любого из трех символов: точки с запятой, end или else. (По синтаксису алгола после end в программе может следовать только точка с за- пятой или end или else, это позволяет распознать комментарий второго типа.) Так, в примере begin comment это легкий пример; г.=г-|-2; if г < q then <?:=? end пример кончился; текст это легкий пример является комментарием первого типа, а текст пример кончился — комментарий второго типа. 2.16. Методы разработки программ 2.16.1. Программирование по принципу сверху вниз. Когда поставленная задача требует составления сложной про- граммы, обычно человеку не удается сразу представить себе деталь- ный вид этой программы в ее эффективной реализации, так как для этого пришлось бы удерживать в голове непомерно большой объем информации. Сделать объем осмысливаемой информации посиль- ным позволяет метод программирования сверху вниз, который основывается на том, что задача разбивается на несколько более простых частей и при дальнейшей детализации мы думаем о каждой части отдельно. Части задачи выделяются таким образом, чтобы их можно было программировать независимо. При этом состав- ляется план решения задачи, пунктами которого и являются вы- деленные части. План оформляется либо в виде линейной схемы, как это будет сделано ниже, либо в виде блок-схемы, которая будет описана в 2.16.2. Затем аналогично производится детали- зация выделенных частей. В принципе допускается любое количе- ство таких уровней детализации, которые вводятся до тех пор, пока не станет полностью ясно, как программировать все выделенные части задачи в виде отдельных фрагментов алгоритма. Для того, чтобы можно было программировать фрагменты алго- ритма независимо, необходимо четко определить связи между этими «фрагментами, строго сформулировать, какие данные получает и какие результаты выдает каждый фрагмент. Эти связи называются интерфейсом. Фактически мы уже пользовались методом программирования сверху вниз, например, в разд. 2.10.1, когда разрабатывали про- грамму для вычисления sm(x). Теперь мы применим этот метод к разработке еще одной программы.
2 1б] МЕТОДЫ РАЗРАБОТКИ ПРОГРАММ 127 Пусть дана прямоугольная матрица, состоящая из т строк и п- столбцов. Требуется найти строку, сумма элементов которой мак- симальна, найти в этой строке ее минимальный элемент и вычислить, сумму элементов того столбца, которому принадлежит этот эле- мент. Начнем с того, что введем необходимые обозначения для на- чальных данных и результатов вычисления и уточним постанофсу задачи. Матрицу обозначим через а, ее элементы будем обозначать через ац при и Номер строки с максимальной сум- мой элементов обозначим через k. Номер столбца, в котором нахо- дится минимальный элемент этой строки, обозначим через /. Сумму элементов этого /-го столбца обозначим через s. Теперь нужно уточнить постановку задачи. В матрице может оказаться несколько строк с одинаковой максимальной суммой элементов, а в каждой такой строке может оказаться несколько равных минимальных элементов. Поэтому для определенности будем искать самую первую из строк с максимальной суммой эле- ментов, а в ней самый первый из минимальных элементов. Теперь мы можем составить исходную схему требуемого вы- числения: 1°. Ввести значения /и, п и матрицу а. 2°. Вычислить номер k первой из строк с максимальной суммой элементов. 3°. Вычислить номер I первого минимального из элементов akJ^ т 4°. Вычислить s = 2 аи- 5°. Вывести значения A, Z, s. Согласно правилам алгола (см. раздел 2.12) значения /и, п долж- ны быть введены не в том блоке, в котором вводится матрица а- Поэтому мы разделяем пункт 1° на два подпункта: 1.1°. Ввести значения т, п. 1.2°. Ввести матрицу а. Пункт 2° можно детализировать следующим образом: 2.1 °. Присвоить значение k: = l. п 2.2 °. Вычислить /: = S аи. / = i 2.3 °. Для i=2, 3, .... т выполнить: п 2.4 °. {Вычислить иг — 2 2.5 °. Если u>t, то присвоить значения &:=/; Пункт 3° детализируется так: З .Г. Присвоить значения /:=1; 3.2 °. Для /=2, 3...п выполнить:
128 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 1Гл. 2 3.3 °, {Если akj<Zt, то присвоить значения l:=j; t:=akJ}. Пункт 4° детализируется следующим образом: 4 . Г. Присвоить значение s:=0. 4.2 °. Для i=l, 2, ..т выполнить: 4.3 °. {s:=s+a/z} Пункт 5° не нуждается в детализации, так как он может быть реализован одним оператором вывода. Не обязательно доводить детализацию схемы до уровня отдель- ных операторов алгола, однако в этом примере мы проведем такую детализацию. Для этого уточним суммирование элементов строки для пунктов 2.2° и 2.4°. 2.2.1°. /:=0 2.2.2°. Для /=1, 2, ..., п выполнить: 2.2.3°. {/:=Н-а1у} и аналогично: 2.4.1°. ы:=0 2.4.2°. Для /=1, 2....п выполнить: 2.4.3°. {и:=и+йц} Теперь детализация схемы завершена. Для большей наглядности можно выписать окончательную схему в таком виде: 1.1°. Ввести значения т, п. 1.2°. Ввести матрицу а. 2.1°. Л:=1. 2.2.1°. /:=0. * 2.2.2°. Для / = 1, 2..п выполнить: 2.2.3°. {/:=/4-<zv}. 2.3°. Для i=2, 3, .... т выполнить: 2.4.1°. {«:=0 2.4.2°. Для /=1, 2, ..., п выполнить: 2.4.3°. {м:=«4-а7} 2.5°. Если u>t, то присвоить значения k:=i-, /:=«}. З.Г. Присвоить значения /: = 1; t:—akl. 3.2 °. Для /=2, 3, ..., п выполнить: 3.3 °. {Если akf<.t, то присвоить значения /:=/; 4.1 °. Присвоить значение s:=0. 4.2 °. Для i=l, 2, ..., т выполнить: 4.3 °. {s:=s+afz}. 5°. Вывести значения k, I, s. Схемы представляют собой важный и широко употребимый инструмент разработки алгоритмов. Они позволяют делить алго- ритм на части и затем вести работу с каждой частью в отдельности, зная, что если правильно запрограммировать каждую часть, то получится и правильная программа в целом. После завершения построения схемы производится переход от схемы к программе на формальном языке, например, на алголе.
216j МЕТОДЫ РАЗРАБОТКИ ПРОГРАММ 129 - Таким образом, использование схем позволяет не только по* степенно осмысливать план программы, разрабатывая на каждом этапе алгоритм с удобной степенью детализации (обычно каждая схема содержит не более, чем 10—15 пунктов), но и отделить раз- работку алгоритма от его кодирования. Чтобы продемонстрировать универсальность и удобство при- менения схем, изобразим в виде схемы сам процесс программиро- вания на алголе. Г. Составить детальную схему. 2°. Кодировать все элементы схемы на алголе. Интерфейсом между частями Г и 2° этой работы служит схема программируемого алгоритма. Теперь детализируем часть 1°, выписав для нее более подробную схему: 1.1°. Составить крупную схему. 1.2°. Пока все элементы не станут простыми для кодирования, детализировать отдельные элементы схемы. Применительно к нашей задаче поиска минимального элемента в строке с максимальной суммой элементов мы выполнили работы 1.Г и 1.2° из этой схемы и теперь переходим к работе 2°, т. е. к кодированию на алголе разработанной схемы алгоритма. В резуль- тате получаем алгол-программу: begin integer т, л; ввод (т, п)-, begin array а [1 : tn, 1 : л[; integer i, j, k, l\ real s, t, w, ввод (о); ft: = l; /:=0; for /: = 1 step 1 until л do /:=/+а [1, /]; for i:=2 step 1 until tn do begin u:==0; for /: = ] step 1 until л do м:=ы+а [i, /]; if u>t then begin kt — i, t:=u end end; /:==!; t:=a [ft, 1]; for j:=2 step 1 until n do if a [ft, j]<i then begin t:=a [ft, /] end; s:=0; for t: = l step 1 until m do s:=s+a [i, /1; вывод (k, I, s) end end Обозревая составленную таким способом программу, мы можем при желании отвлечься от подробностей ее окончательного вида и перейти к рассмотрению того промежуточного уровня ее описа- ния, на котором уже присутствуют интересующие нас детали, но Э. 3. Любинский и др
130 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. » I еще нет более частных деталей, которые при данном рассмотрении для нас несущественны. Иногда разработка алгоритма по принципу сверху вниз оказы- вается неудачной из-за того, что при попытке дальнейшей детали- зации одного из пунктов общей схемы выясняется, что эта общая схема в имеющемся виде не годится или нуждается в серьезных изменениях. Например, детализация алгоритма анализа матрицы могла бы показать, что следует перебирать строки не от первой к последней, как задумано в первоначальной общей схеме, а от последней к первой. В таком случае приходится корректировать общую схему с учетом ее детализации. 2.16.2. Блок-схемы. Приводившиеся до сих пор схемы легко обозримы, поскольку представляемые ими структуры алгоритмов линейны. В случае структур со сложными переходами используются блок-схемы. Блок-схемой называется такое графическое изображение струк- туры алгоритма, в котором каждый этап процесса переработки данных представляется в виде прямоугольника, ромба, овала или другой геометрической фигуры («блока»). Эти фигуры соединяются между собой линиями со стрелками, отображающими последова- тельность выполнения алгоритма. Внутри каждой фигуры разре- шается писать (как и в схеме) произвольный текст, в котором на понятном человеку языке сообщается о нужных вычислениях в соответствующей части программы. Приняты определенные стандарты графических обозначений. Так, прямоугольник обозначает вычислительные действия, в ре- зультате которых изменяются значения данных. Ромбом обозна- чается этап разветвления вычислительного процесса. Выбор одного из двух возможных направлений дальнейшего счета производится в зависимости от выполнения условия, записанного в ромбе. Если это условие выполнено, то нужно перейти по стрелке с надписью «да», если же условие не выполнено, то нужно перейти по стрелке с надписью «нет». Например: Овалом обозначается начало или окончание выполнения алгоритма. На рис. 2.2 в качестве примера приведена блок-схема алгоритма извлечения квадратного корня x = а итерационным методом Ньютона x»==x»-i+ Т — x«-i) ’ п== Ь 2, ...
2.161 МЕТОДЫ РАЗРАБОТКИ ПРОГРАМЛ1 131 Исходными данными являются значение а, из которого нужно извлечь корень, значение х», являющееся некоторым грубым при- ближением корня, и значение eps>0, определяющее требуемую точность вычисления. Более сложная блок-схема приводится, например, в раз- деле 3.3.4 на рисунке 3.3. Рис. 2.2. Блок-схема вычисления а. 2.16.3. Программирование по принципу снизу вверх. Программирование сверху вниз может быть затруднено, если неясно, на какие части де- лить задачу. При этом часто бы- вает видно, что заведомо пона- добятся определенные фрагмен- ты вычислений. (Обычно это типовые вычисления, которые уже использовались при про- граммировании других задач.) В таких случаях можно попы- таться оформить эти фрагменты и, опираясь на них, конструиро- вать нужную программу. Такой способ программирования называ- ется программированием снизу вверх. Принцип программирования снизу вверх состоит в том, что сначала выбираются или строятся детали алгоритма, а потом из них наращиваются более крупные части, пока не будет построен искомый алгоритм Программирование снизу вверх оказывается особенно эффективным, если имеется готовый набор фрагментов алгоритмов, оформленных, например, в виде процедур алгола. Тогда мы можем выбрать из этого набора те процедуры, которые, как нам кажется, пригодятся для выполнения отдельных этапов требуемого вычисления, затем из этих процедур мы конструируем более крупные фрагменты, нацеленные на частичное решение по- ставленной задачи, реализуем эти фрагменты и, постепенно нара- щивая и уточняя получаемые конструкции, пытаемся прийти в конце концов к решению нашей задачи. Например, при решении рассмотренной в разд. 2.16.1 задачи поиска минимального элемента в строке с максимальной суммой элементов мы могли бы сначала выделить процедуры вычисления минимума в строке, суммирования элементов строки и суммиро- вания элементов столбца, а потом построить из них нужный ал- горитм. Возможны смешанные стратегии программирования, когда при разработке алгоритма совместно используются принципы про-
132 АЛГОРИТМИЧЕСКИЕ ЯЗЫКИ. АЛГОЛ-60 [Гл. 2 граммирования сверху вниз и снизу вверх. Примеры такой раз- работки мы будем встречать в следующей главе. 2.16.4. Принципы структурного программирования. В разделе 2.7.2 мы уже говорили о понятии структурного про- граммирования. Теперь мы знаем структуры условного оператора, оператора цикла и оператора процедуры, которые позволяют ор- ганизовать алгольными средствами без использования операторов перехода выбор, повторение и обращение к функции. Однако такая стандартизация структур управления последовательностью счета служит в структурном программировании и средством для дости- жения гораздо более важной цели: писать удобочитаемые программы. Занимающая хотя бы несколько страниц печатного текста про- грамма со сложной логикой часто становится необозримой для человека, если заранее не предусмотреть строгую дисциплину ее написания. Между тем, одно из назначений алгола состоит в пуб- ликации алгоритмов для ознакомления с ними людей. Даже если алгольная программа предназначается только для машинного исполнения, ее неудобочитаемость часто является источником многочисленных скрытых ошибок, выявлять и исправлять которые должен опять-таки человек. Структурное программирование основывается на рассмотренном нами в разделе 2.16.1 принципе разработки программы сверху вниз с постепенной детализацией. В рамках структурного программирования на каждом этапе детализация очередного промежуточного действия может произ- водиться только следующими четырьмя способами: 1) последовательное перечисление нескольких более мелких действий; 2) выбор одного из нескольких более мелких действий в зави- симости от определенных условий; 3) циклическое повторение более мелкого действия при опреде- ленных значениях параметров. 4) использование более мелкого действия как процедуры или подпрограммы. Последовательное применение принципов структурного про- граммирования позволяет, как правило, получать удобные и легкие для чтения программы.
Глава 3 СТРУКТУРЫ ДАННЫХ Как мы уже говорили, для каждого круга задач характерны определенные структуры данных, над которыми выполняются алгоритмические действия. В реальной среде, в которой протекают процессы, отображаемые алгоритмом, элементы связаны некоторыми формами взаимного влияния или взаимной зависимости. Например, если среда пред- ставляет собой металлическую пластинку, то между ее элементами, являющимися пространственными соседями, передается тепло. Если это телефонная сеть, то связаны аппараты с подстанциями, подстанции со станциями, станции между собой и т. д. Разумеется, обычно нельзя отобразить все связи реальной среды. Поэтому нужно выбирать связи, существенные для данного процесса, от- брасывая остальные связи и получая таким образом упрощенную модель среды. Связанные между собой элементы данных образуют структуру данных. В алголе предусмотрены две структуры данных: одиночное значение (простая переменная) и прямоугольный массив значений, элементы которого задаются наборами индексов. Однако эти две алгольные структуры не всегда оказываются удобными для решения реальных задач, в которых имеются свои структуры данных, от- ражающие структуры исследуемых объектов. Например, возникают определенные трудности, если описывать на алголе телефонную сеть, схемы железнодорожного сообщения, шахматные позиции, очереди. В принципе любые специфические структуры удается отобразить на массивы языка алгол, но такое отображение требует изобретательности и может существенно затруднить формулировку алгоритма. В этом одна из причин того, что программисты не до- вольствуются одним языком (например, алголом), а используют разные языки программирования, ориентированные на различные классы решаемых задач, причем в первую очередь ориентирован- ность языка определяется принятыми в нем структурами данных. В частности, существуют языки программирования, специально предназначенные для работы с очередями, строками, таблицами. В этой главе мы введем типичные структуры, характерные для данных из реальных задач, и опишем реализацию этих структур на алголе.
134 СТРУКТУРЫ ДАННЫХ 1Гл. 3 Каждая структура данных характеризуется, во-первых, взаи- мосвязью элементов и, во-вторых, набором типовых операций над этой структурой. Как правило, такой набор в основном состоит из операций выборки, записи и поиска. Для реализации некоторой структуры на алголе необходимо: 1) отобразить ее на одну из структур алгола, 2) написать на алголе процедуры выполнения типовых операций. Оформляя типовые операции над структурами в виде алгольных процедур, мы получаем возможность в дальнейшем писать на ал- голе программу, оперирующую с новыми структурами, не задумы- ваясь над тонкостями алгольной реализации этих структур, а просто включив в свою программу описания этих процедур и об- ращаясь к ним в тех местах, где возникает потребность в операциях над такими структурами. Все рассматриваемые структуры мы будем отображать на од- номерный массив. Этот выбор объясняется тем, что в последующих главах мы будем изучать еще один язык программирования — язык ЭВМ, в котором имеются только две структуры данных: одиночное вначение и вектор (т. е. одномерный массив). Таким образом, ото- бражая некоторую структуру на одномерный массив, мы сразу решаем, как отображать ее и на языке алгол, и на языке ЭВМ. 3.1. Очереди Очереди — это одна из простейших и в то же время достаточно Типичных структур данных, потребность в которых возникает при программировании реальных процессов и ситуаций. Проблема Очереди возникает, когда имеется некоторый механизм обслужи- вания, который может выполнять заказы последовательно, один за другим. Если при поступлении нового заказа обслуживающее устройство свободно, оно может немедленно приступить к его выполнению, но если оно уже выполняет некоторый ранее получен- ный заказ, то новый заказ должен поступить в конец очереди за- казов, ожидающих выполнения. Каждый раз, когда обслуживаю- щее устройство завершает выполнение текущего заказа, оно при- ступает к выполнению заказа из начала очереди, причем этот заказ удаляется из очереди и первым в очереди становится следу- ющий за ним заказ. Если заказы поступают нерегулярно, их оче- редь то удлиняется, то укорачивается, и время от времени может становиться пустой, когда все имеющиеся заказы выполнены, а новых еще не поступило. Для простоты предположим, что каждый заказ кодируется не- которым целым числом. В таком случае очередь организуется из чисел, представляющих собой коды заказов. Например, исходная очередь могла бы состоять из чисел 59 и 17. Потом из очереди вы- бирается первое число 59, и в ней остается единственный элемент 17.
8.1 J ОЧЕРЕДИ 135 Если далее в очередь поступают последовательно числа 31 и 7, то теперь очередь имеет вид: 17; 31; 7. Над структурой очереди могут выполняться операции двух видов: — выборка и одновременное удаление из очереди первого ее элемента; — занесение нового элемента в конец очереди. Опишем реализацию очереди на алголе. Для отображения оче- реди будем использовать одномерный массив S, в последовательных элементах которого будут храниться последовательные элементы очереди. Кроме того, будем использовать две целочисленные пере- менные п и k, которые будут служить для указания соответственно индексов в массиве S начала очереди и первого свободного места в конце очереди. Так, если очередь из чисел 59 и 17 занимает эле- менты Sill и SI2], то п=1 и k—З. Занесение нового элемента х в очередь S реализуется следующей процедурой: procedure В ОЧЕРЕДЬ (S, х) конец: (k); value х; integer array S; integer x, k; begin SlAl:=x; k:—k+l end При выполнении этой процедуры значение х заносится в позицию, являющуюся концом очереди, и указатель конца очереди k про- двигается на единицу вперед. Удаление из очереди первого элемента с присваиванием его значения переменной х реализуется процедурой: procedure ИЗ ОЧЕРЕДИ (S, х) начало: (п); integer array S; integer x, n; begin x:=S[/d; n:=n+l end При выполнении этой процедуры указатель начала очереди п про- двигается на единицу вперед. Если очередь пустая, то n=k. В программе, использующей структуру очереди, должен быть описан массив S, содержащий некоторое количество т элементов*, integer array S[0: tn—1 ]. Можно считать, что значение т достаточно велико и заведомо больше, чем количество чисел, которые могут одновременно находиться в очереди. Однако, если числа в очереди обновляются часто, то состоящая даже из немногих чисел очередь будет постепенно исползать» по направлению к концу массива S. Когда-нибудь последний элемент очереди попадет в Sim—11, между тем как элементы SIO], S[l], S12], ... окажутся не используемыми. Таким образом, наши процедуры В ОЧЕРЕДЬ и ИЗ ОЧЕРЕДИ нуждаются в корректировке. Один из способов такой корректи- ровки состоит в том, чтобы после занесения элемента очереди в Sim—1] перенести всю очередь из конца массива S в его начало (установив значение п—Q и соответственно изменив значение А).
136 СТРУКТУРЫ ДАННЫХ (Гл 3 При этом процедура занесения нового элемента х в очередь не- сколько усложняется: procedure В ОЧЕРЕДЬ 1 (5, х) конец-, (k) длина массива: (т); value х, т: integer array S; integer x, k, nr, begin integer i; S(£l:=x; if k=£m—1 then k:=k+\ else begin for i:—n step 1 until m—1 do Sli—n]:=S[»l; k: =m—n; n: =0 end end В этом варианте процедуры одним из ее параметров является длина т массива 5. Тело процедуры теперь представляет собой не составной оператор, а блок, в котором описывается переменная i, используемая как параметр цикла. После присваивания значения 5[£]:=х, если k=£m—1 (т. е. пока не достигнут конец массива), выполняется присваивание k: =k+1, как и в процедуре В ОЧЕРЕДЬ. Если же k—m—1, то выполняются составной оператор, включаю- щий в себя цикл перемещения всех элементов очереди в начало массива 5 (с сохранением порядка следования элементов очереди), и операторы присваивания новых значений п и k, причем значение k указывает номер первого свободного места, но теперь уже в конце перенесенной очереди. Например, если т=90, л=80 и £=89, то в результате выполнения процедуры В ОЧЕРЕДЬ 1 десять эле- ментов очереди будут перенесены из 51801, 5181 ], ..., <S[89] в 510), 5111, ..., 5191 и получатся новые значения п=0 и £=10. При такой процедуре В ОЧЕРЕДЬ 1 процедура ИЗ ОЧЕРЕДИ не нуждается в корректировке. Чтобы сократить число параметров процедур, условимся, что для хранения значений п и k отводятся два первых элемента мас- сива 5: значение п будем хранить в 5101, а значение k — в 51П- Тогда элементы очереди могут размещаться, начиная с элемента 5121. Более простой способ избежать «сползания» очереди основы- вается на том, что очередь в массиве 5 «замыкается по кольцу», т. е. мы считаем, что элемент 5[т—1] не является последним, а за ним следует первый из отведенных для размещения очереди эле- мент 5121. Тогда при 511 }—т—1 (т. е. при k=m—1) новая проце- дура В ОЧЕРЕДЬ 2 занесет значение х в элемент 51/п—1 ] и уста- новит соответственно новое значение конца очереди 5111=2. Теперь процедура В ОЧЕРЕДЬ 2 имеет вид: procedure В ОЧЕРЕДЬ 2 (5, х) длина массива: (/п); value х, m; integer array 5; integer x, m-, begin 5 151111: =x; if S[ll=m—1 then S[l]:=2 else 5[ll:=Slll-H end
ОЧЕРЕДИ 137 3.1] Здесь запись SISI 1И означает то же, что в процедуре В ОЧЕ- РЕДЬ означала запись 3lfcl. Аналогично изменяется и процедура ИЗ ОЧЕРЕДИ'. procedure ИЗ ОЧЕРЕДИ 2 (S, х) длина массива', (т); value nr, integer array S; integer x, nv, begin x:=S(5(0]|; if S(01=m—1 then 3(01: =2 else SIOJ:=3(01+1 end Запись 3(3(0]] означает то же, что S(n] в процедуре ИЗ ОЧЕ- РЕДИ. Отличие этой процедуры в том, что если начало очереди совпадает с последним элементом массива 3, то после удаления этого элемента из очереди считается, что новым началом очереди является «следующий по кольцу» элемент S[2]. Приведенные процедуры не содержат контроля правильности обращения к ним. В частности, если очередь пустая, то выполнение процедуры ИЗ ОЧЕРЕДИ 2 (3, х, т) приведет к присваиванию переменной х постороннего значения. Если же очередь настолько большая, что заняты все элементы массива S, то выполнение про- цедуры В ОЧЕРЕДЬ 2 (S, х, т) приведет к наложению нового элемента на элемент, находящийся в начале очереди с уничтоже- нием этого начального элемента. Ответственность за правильные обращения к таким процедурам полностью ложится на человека, пользующегося ими. Однако можно несколько усложнить эти процедуры, введя в них контроль правильности обращения. Мы знаем, что если очередь пустая, то 3[1]=3(0), т. е. начало очереди совпадает с началом свободного места после очереди. Если очередь замкнута по кольцу, такое же равенство S[1]=S[O] возни- кает, когда заняты все элементы массива 3. Чтобы не путать эти две ситуации, мы договоримся реагировать на полное заполнение массива $ (сразу же) как на ошибку переполнения. Тогда наши процедуры могут выглядеть следующим образом: procedure В ОЧЕРЕДЬ 3 (3, х) длина массива', (/и); value х, nr, integer array 3; integer х, т\ begin S [S[l)]:=x; if 3(1 ]=m— 1 then S[l]:=2 else SI 11: =3111+1; if S[11=3(0] then ОШИБКА („переполнение", 3) end procedure ИЗ ОЧЕРЕДИ 3 (S, x) длина массива:(т)', value nv, integer array S; integer x, m; begin if 3(11=3(01 then ОШИБКА („нет элемента”, 3) else begin x:=S[S(011; if S[0]=/n—1 then S[0]:=2 else 3(01:=3(01+1 end end
138 СТРУКТУРЫ ДАННЫХ (Гл. 3 В этих процедурах в некорректных ситуациях производится обращение к специальной процедуре ОШИБКА, которая обеспе- чивает нужную реакцию на эти ситуации. При переполнении мас- сива S такой реакцией может быть, например, увеличение длины массива. Первым параметром процедуры ОШИБКА является строка с диагнозом ошибки. При машинной реализации процедура ОШИБКА может довести эту диагностику до сведения человека, напечатав ее на бумаге или выведя на экран, имеющийся в составе аппаратуры ЭВМ. В последующих процедурах алгольной реализации операций над структурами, мы не будем предусматривать контроля правиль- ности ’обращения к ним. (Контроль в процедурах не вводится, во-первых, чтобы не усложнять эти процедуры, и, во-вторых, по- тому, что выполнение операций контроля замедляет работу про- цедур.) 3.2. Стеки В предыдущем разделе мы рассмотрели очередь, в которой элементы (заказы) обслуживаются в порядке их поступления. В про- граммировании часто используется и другой тип очереди, в которой каждый раз предпочтение в обслуживании отдается последнему по времени поступления элементу. Потребность в такой органи- зации очереди возникает, например, при решении следующей задачи. Пусть массив Т содержит в элементах Т 10], Т [1], ... запись ал- гол-программы в кодировке, описанной в разделе 2.9.1. Каждому символу этой алгол-программы ставится в соответствие его коор- дината в массиве Т (индекс того элемента массива Т, в котором содержится код этого символа). Задача состоит в том, чтобы найти все пары операторных скобок begin — end и построить таблицу, в которой для каждой такой пары должна содержаться строка с ука- занием координат символа begin и соответствующего ему символа end. Продвигаясь по записи алгол-программы последовательно от начала к концу, мы можем образовать очередь координат открываю- щих скобок begin, ожидающих соответствующих закрывающих скобок end. Особенность этой очереди состоит в том, что каждый Еаз, когда встретится символ end, он будет соответствовать тому egin из очереди, который появился там самым последним. Пусть, например, в алгол-программе последовательность операторных ско- бок имеет вид: ,, m begin.. .begin.. .end.. .begin.. .end.. .end Координаты В T: О 17 42 61 84 85 Продвигаясь по записи этой алгол-программы, мы дойдем до первого end (с координатой 42), который соответствует второму (т. е. последнему в состоянии очереди на данный момент) символу
3.21 * ' СТЕКИ ' 139 begin (с координатой 17). Удалим из очереди этот begin и занесем в таблицу его координату 17 вместе с координатой 42 первого end. После этого в очереди останется один (первый) begin, с,которого начинается программа. Двигаясь по записи алгол-программы да- лее, мы встретим третий begin (с координатой 61) и занесем его в очередь нового типа, которая состоит теперь из первого и третьего символов begin Встретив второй end, мы удалим из этой очереди последний (в данном случае третий) begin и занесем строку, содер- жащую координаты 61 и 84 символов begin и end, в таблицу. Теперь в очереди опять останется один первый begin. Дойдя до соответст- вующего ему последнего end, мы удалим из очереди этот begin и занесем координаты 0 и 95 в таблицу. В результате очередь оста- нется пустой, а таблица координат begin — end будет завершена и примет вид: 17 42 61 84 О 95 Для реализации такой очереди, организованной по принципу «последним пришел — первым ушел», употребляется специальная структура данных, которая называется стек. (В литературе встре- чается и другое название — магазин.) Стек представляет собой последовательность элементов, упорядоченных по времени их по* ступления, причем извне доступна только вершина стека, т. е. место, в котором находится последний по времени поступления элемент. Обратиться к стеку можно для того, чтобы забрать элемент с вер- шины или добавить в него новый элемент. Стек можно сравнить со стопкой бумаги, накапливающейся на письменном столе. Когда мы забираем элемент с вершины, то новой вершиной стека стано- вится предыдущий по времени поступления элемент. Когда мы до- бавляем новый элемент, то он как бы ложится поверх прежней вер- шины, и теперь уже он становится вершиной стека. В простейшем случае каждый элемент стека является одиноч- ным числовым значением. Такой стек легко отобразить на одномер- ный алгольный массив S. Операция занесения В СТЕК (т. е. в вершину стека) значения х будет реализовываться процедурой В СТЕК. (S, х)\ операция извлечения из стека (т. е. из его верши- ны) хранящегося там значения будет реализовываться процедурой ИЗ СТЕКА (S, х), где х — имя переменной, в которую занесется значение, извлекаемое (и одновременно удаляемое) из стека. Мас- сив S будет начинаться с элемента S[0], который служит указате- лем очередного свободного места в стеке, т. е. содержит индекс того элемента массива S, который следует за текущей вершиной стека. Самый давний по времени поступления элемент стека хранится в массиве S в элементе S[1 ], а поступившие позднее элементы стека хранятся в массиве S соответственно в его элементах S12], S[3], ... ..., SIS101—1]. Если стек пуст, то S[0]=l.
140 СТРУКТУРЫ ДАННЫХ (Гл 3 Процедуры В СТЕК и ИЗ СТЕКА описываются на алголе сле- дующим образом: procedure В СТЕК (3, х); value х, integer array 3; integer x; begin S[S[0]]:=x, S[0]:=S[0]-H end procedure ИЗ СТЕКА (S, x); integer array 3; integer x; begin x:=S[S(0]—l]; S[O]:=S[OJ— 1 end Процедура В СТЕК начинает работу с того, что заносит значе- ние х в новую вершину стека, индекс которой в массиве 3 указыва- ется значением 3[0]. После этого первым свободным становится сле- дующий элемент массива S с индексом S[0]-H. Это новое значение индекса первого свободного элемента заносится в S[OJ. Процедура ИЗ СТЕКА берет значение из вершины стека и за- носит его в х. Вершина стека предшествует первому свободному эле- менту массива 3, и поэтому ее индекс в массиве 3 равен 3101—1. Те- перь бывшая вершина стека становится первым свободным элемен- том массива S, и поэтому значение 3[0]—1 заносится в S101. Как отмечалось в конце разд. 3.1, эти и последующие процеду- ры не содержат контроля правильности обращения к ним. Напри- мер, при пустом стеке (т. е. при 3[0]=1) процедура ИЗ СТЕКА будет работать некорректно. Уточним теперь постановку задачи построения таблицы соот- ветствия операторных скобок begin — end. В массиве Т последова- тельно, символ за символом, закодирована алгол-программа. Тре- буется построить двумерный массив В, в котором столько строк, сколько пар begin — end в этой алгол-программе, причем каждая строка соответствует очередной паре и содержит два числа: индек- сы элементов массива Т, в которых закодированы символы begin и end из этой пары. Признаком конца закодированной алгол-про- граммы является число 999 в очередном элементе массива Т. Для наглядности, чтобы не воспроизводить конкретные числовые коды, мы введем в употребление функцию КОД с одним аргументом КОД („(символ алгола)”). Например, КОД („begin”)=240. КОД (,,end”)= =241. Приведем сначала неформальное описание алгоритма. 1. Образовать пустой стек 3 и подготовиться к формированию первой строки массива В. 2. Перебирать последовательно от начала все элементы массива Т, пока не встретится признак конца алгол-программы. Если в очеред- ном элементе массива Т встретится код символа begin, то ин- декс этого элемента занести в стек. Если же в очередном элементе 7W встретится код символа end, то сформировать очередную t-ю строку массива В, в которую занести, во-первых, значение из вер- шины стека (индекс элемента массива Т, содержащего код символа
8.2] СТЕКИ 141 begin), й, во-вторых, индекс k. Тем самым получаются координаты очередной пары begin — end Запишем соответствующую схему: Г. 3(01: = 1 (образуется пустой стек 3); (:=1 (номер первой сво- бодной строки в массиве В). 2°. При k—0, 1, ... пока TUhM)99 выполнить 3°. {Если Т[1г]=КОД („begin”), то В СТЕК (3, k) 4°. Если Т{к}=КОД („end”), то ИЗ СТЕКА (S, В\1, 1]); В(/, 2]:=й; i:=H-l) 5°. Вывести полученную таблицу. Ниже приводится блок алгол-программы, реализующий этот алгоритм. (Предельное число строк в таблице В и элементов в сте- ке 3 задается глобальными переменными N и п, процедуры В СТЕК и ИЗ СТЕКА также считаются описанными во внешнем блоке.) begin integer array B[\:N, 1:2), 3)0:n); integer i, S[0J: = l; i: = l; for£:=0 Л+l while 7W/=999 do if Т[к}=КОД („begin”) then В СТЕК (3, k) else if Т[к]=КОД („end") then begin ИЗ СТЕКА (S, B[i, 1]); B[i, 2]:=Л; i:=t+l end; вывод (В, i) end Выводимое значение i указывает число строк в полученной таблице. Например, если применить этот алгоритм к программе с опера- торными скобками begin ... begin ... begin ... end ... begin ... T [О] 7(73] 7(117] 7(215) 7(310) ... end ... end ... begin ... end ... end 7(470) 7 (501) 7 (605] 7(700) 7(1800) то по мере возрастания k изменение содержимого стека и формирова- ние последовательных строк таблицы В будет происходить так, как показано в табл. 3.1. Таблица 3.1 k Содержимое стека Очередная строка таблицы В 0 0 73 0; 73 117 0; 73; 117 215 0; 73 117; 215 310 0; 73; 310 470 0; 73 310; 470 501 0 73; 501 605 0; 605 700 0 605; 700 1800 Пусто 0; 1800
142 СТРУКТУРЫ ДАННЫХ (Гл. 3 Таким образом, в массиве В перечисляются все пары b^gin — end, но в порядке следования символов end. Если нам нужно пере- числить их в порядке следования begin, то для «решения этой зада- чи удобно употребить стек, в котором элементами являются не оди- ночные значения, а пары значений: k — координата символа begin и i — его порядковый номер в тексте. Очевидно, что i — это и есть номер строки в массиве В, в-которую нужно будет записать коорди- наты символов begin и end, когда встретится соответствующий сим- вол end. При отображении такого стека на одномерный алгольный мас- сив S операция занесения в стек значений х и у будет реализовы- ваться процедурой В СТЕК 1 (S, х, у)\ операция извлечения из стека будет реализовываться процедурой ИЗ СТЕКА 1 (S, х, у), где х и у — имена переменных, в которые занесутся первое и второе значения, извлекаемые (и одновременно удаляемые) из вершины. Как и в случае простого стека, элемент S10] служит указателем оче- редного свободного места в стеке. Теперь процедуры В СТЕК 1 и ИЗ СТЕКА 1 описываются на алголе так: procedure В СТЕК 1 (S, х, у)-, value х, у, integer array S; integer x, у, begin SIS]0]]:=x; SLS[O]+11: =y, SlO]:=SlOl+2 end procedure ИЗ СТЕКА 1 (S, x, y)\ integer array S; integer x, y, begin x:=S[S[0]—2]; i/:=SLS]O]—11; S[0]:=S[01—2 end Вернемся теперь к решению нашей задачи построения табли- цы пар begin — end. Продвигаясь по записи Т алгольной программы, мы будем для каждого встреченного символа begin заносить в вершину стека две величины: координату k этого символа в массиве Т и порядковый номер i этого begin (т. е. номер, который должна занять соответ- ствующая строка в таблице результатов В). Неформально наш алгоритм можно описать следующим об- разом: 1. Образовать пустой стек S и занести 0 в счетчик i номеров сим- волов begin. 2. Перебирать все элементы массива Т, пока не встретится при- знак конца алгол-программы. Если в очередном элементе ПЛ] встретится код символа begin, то занести в стек индекс k этого эле- мента и порядковый номер i символа begin. Если же в очередном эле- менте ПЛ] встретится код символа end, то взять из вершины стека координату I и порядковый номер / символа begin (соответствующе- го данному end) и занести в /-ю строку таблицы В значения I и Л (координаты символов begin и end).
а.2]-. СТЕКИ М3 Соответствующая схема записывается так: 1°. i:=0 2°. При k=0, пока Т[£]=/=999 выполнить 3°. {Если Т[к]=КОД („begin”), то »:=Ж; В СТЕК 1 (S, k, О 4°. Если Т[к]=КОД („end”), то ИЗ СТЕКА 1 (S, I, fr, B[j, 1]:« Z; B[j, 2]:=Л) 5°. Вывести полученную таблицу. Описанный алгоритм реализуется следующим блоком алгол* программы: begin integer array Bl 1 :N, 1:2], S[0:2Xn); integer i, k, I, j; S[Oh=l; i:=0; for/г:=0, A+l while ШИ999 do if T\k]=KOE („begin”) then begin i:=i+l; В СТЕК I (S, k, i) end else if Т[к]=КОД („end”) then begin ИЗ СТЕКА 1 (S, t, ]), B\j, 1]: = /; B[/, 2]:=£end; вывод (B, i) end Если применить этот алгоритм к рассмотренному выше примеру, то по мере возрастания значений k изменение содержимого стека и формирование строк таблицы В будут происходить в последователь- ности, показанной в табл. 3.2. Таблица 3.2 k Содержимое стека Формируе- мая 1-Я строка таблицы Значе- ние i 0 (0,1) 73 (0,1); (73,2) 117 (0,1); (73,2); (117,3) 215 (0,1); (73,2) 117; 215 3 310 (0,1); (73,2); (310,4) 470 (0,1); (73,2) 310; 470 4 501 (0,1) 73; 501 2 605 (0,1); (605,5) 700 (0,1) 605; 700 5 1800 Пусто 0; 1800 1 В результате таблица В примет вид: 0; 1800 73; 501 117; 215 310; 470 605; 700
144 СТРУКТУРЫ ДАННЫХ (Гл. 3 3.3. Строки Мы уже убедились в главе 1, что все алгоритмы могут быть пред- ставлены как алгоритмы преобразования слов. Для довольно ши- рокого класса алгоритмов, называемых алгоритмами символьной обработки, объекты естественно и удобно представлять в виде слов. Например, алгоритм преобразования алгебраических формул пре- образует слово а Xb+aXc в слово ах (b+с), асловоа| 2+2хахЬ+ Ь f 2 в слово (a+b) | 2 и т. д. Алгоритм определения пар символов begin и end также имеет одним из своих объектов слово, представ- ляющее собой текст на языке алгол, но не изменяет этого слова, а только просматривает его. В программировании объекты, являющиеся последовательно- стями символов из некоторого алфавита, принято называть не сло- вами, а строками. Основная операция, которую применяют к стро- кам, это подстановка одной подстроки вместо другой, аналогичная применению формулы нормального алгоритма. В этом смысле нор- мальные алгоритмы предлагают аппарат, удобный не только для тео- ретических рассмотрений, но и для решения определенного класса практических задач. Однако обычно эта операция разбивается на ряд более простых операций, таких как последовательный перебор символов строки; включение заданного символа в указанное место строки; исключение символа из указанного места строки; поиск вхождения в строку заданной подстроки и определение места этого вхождения. Наряду с включением и исключением одного символа, употреб- ляются операции включения и исключения подстроки. 3.3.1. Векторное представление строк. С этим простейшим способом представления строк мы уже по- знакомились в предыдущем разделе. Каждый символ кодируется целым числом, и эти коды располагаются в одномерном массиве под- ряд друг за другом. Именно так мы представляли строку символов алгол-программы в массиве Т: располагали соседние символы про- граммы в элементах массива Т с подряд идущими значениями ин- декса, а в конце записывали код 999 конца строки. В векторном представлении последовательный перебор символов строки обеспечивается тем, что индекс следующего символа является простой функцией от индекса предыдущей, а именно след(0=> = t’+l. Чтобы вставить в такую строку символ, нужно освободить для него место, а для этого требуется «раздвинуть» строку, т. е. все сим- волы, которые должны следовать за вставляемым, нужно перенести в следующие элементы массива. Например, если исходная строка с17548 размещалась в массиве S:
8.3) СТРОКИ 145 символы: а 1 7 5 4 8 координаты в S: 0 1 2 3 4 5 и если после первого символа а вставляется символ f, то все после- дующие символы сдвигаются на одну позицию вправо, т. е. коорди- ната каждого из этих символов увеличивается на единицу: символы: a f 1 7 5 4 8 координаты в S: 0123456 При удалении из такой строки одного символа нужно сдвинуть все последующие символы влево, чтобы не оставалось свободных мест. Если из строки S удаляется символ после i-ro, то на алголе это реа- лизуется, например, следующей процедурой: procedure ИЗ СТР (S) после', (i); value t; integer array S; integer i; begin integer n; for n:=i+l, n+1 while S[n]=/=999 do S[n]:=S[n+l] end Если размеры строк невелики или строки редко изменяются, то можно пойти на затраты времени, связанные с перемещением сим- волов, следующих за вставляемым или удаляемым. Иногда символы удаляются из строки для того, чтобы на их место вставить такое же количество новых символов, тогда нет надобности сдвигать осталь- ные символы. Вообще же тот факт, что локальное изменение строки, касающееся одного ее символа, может повлечь за собой необходи- мость перемещения многочисленных последующих символов, являет- ся основным недостатком векторного способа представления строк. Причина этого состоит в том, что в векторе понятие «следующий эле- мент» связывается с местом расположения предыдущего элемента. Такое представление, как правило, плохо пригодно для структур, которые меняются в середине, хотя оно вполне удовлетворительно для структур, меняющихся только по краям (таким, как очередь или стек) или вовсе не меняющихся (как строка алгольного текста из предыдущего раздела). 3.3.2. Представление строки в виде цепочки. Между тем совсем не обязательно жестко связывать место распо- ложения (т. е. индекс) следующего символа с индексом предыду- щего. Можно располагать символы в любом порядке, но зато каж- дый символ сопровождать указанием места расположения следую- щего. При таком отображении строки на одномерный массив каж- дому символу строки ставится в соответствие пара соседних между собой элементов массива: в одном записывается код самого символа, а в другом — ссылка на следующую пару, т. е. индекс первого эле- мента пары, в которой записан следующий символ. Каждая такая пара элементов массива называется звеном. По- следовательные ссылки друг на друга сцепляют звенья в одну це-
СТРУКТУРЫ ДАННЫХ [Гл. » ) А* почку. Такой способ отображения структуры данных называется сцеплением. Как мы увидим, он применяется не только для пред- ставления строк. Но при представлении любой структуры данных звено всегда состоит из двух частей. В первой части (мы договоримся всегда располагать ее в начале звена) находится ссылка (или не- сколько ссылок) на соседние звенья. Эту часть мы будем называть справочной частью звена или просто справкой. Во второй части зве- на изображается сам элемент структуры данных (в нашем случае кодируется символ). Эту часть мы будем называть информационной частью или телом звена. При отображении разных структур под справку и тело звена может отводиться разное число элементов мас- сива. Мы будем называть индекс первого входящего в звено эле- мента массива индексом этого звена. Если информационные части звеньев цепочки содержат коды символов, то вся цепочка в целом является представлением строки, состоящей из этих символов. Итак, каждое звено в цепочке, которая соответствует строке, содержит два значения: ссылку и код символа. Звенья цепочки мы можем раз- мещать в одномерном массиве 3, каждое звено занимает два сосед- них между собой элемента массива S, но допускается любой разброс , этих звеньев по массиву, так как следование определяется не по- рядком индексов, а значениями ссылок. Мы уже решили, что само значение кода символа занимает вторую позицию звена, а в первой позиции хранится ссылка на следующее звено. Примем соглашение, что последнее звено содержит ссылку нуль. Например строка al5 представляется цепочкой, состоящей из трех звеньев. Если первое звено находится в элементах 3121 и 3131, второе звено — в элементах S141 и 3151 и третье звено — в элемен- тах 3161 и 3171, то мы имеем следующее заполнение элементов мас- сива с «S[21 по 3171: 3121=4 — ссылка на второе звено (т. е. индекс первого элемен- та второго звена). 313]=Л0Д („а”) Sl41=6 — ссылка на третье звено. 3151=КОД („1”) 3[6]=0 — ссылка 0 означает, что это звено последнее. 3171=Д0Д („5") Однако мы можем разместить звенья совсем по-другому: напри- мер, первое звено в элементах 31751 и 31761, второе в 31311 и 3132), а третье в 311061 и S11071. Тогда эти элементы массива S заполнят- ся следующим образом: 31311=106 — ссылка на третье звено. 31321=/<ОД („1”) 31751=31 — ссылка на второе звено. 3176]=ДОД („а”)
3.3) СТРОКИ £[1061==0— признак, что звено последнее. £[1071=КОД („5”) С точки зрения этой цепочки, совершенно не важно, чем запол- нены остальные элементы массива, и в частности, находящиеся меж- ду нашими звеньями элементы £[331, ..., £[74], £[77), ..., £[105]. Теперь, чтобы включить или исключить символ в строке, ничего не нужно сдвигать, а достаточно поменять некоторые ссылки. Для того, чтобы избежать нестандартной работы с первым зве- ном цепочки, удобно ввести фиктивное заглавное звено и хранить его всегда в постоянном месте. В нашем отображе- нии строки на алгольный массив £ заглавное звено цепочки, представляющей строку, будет занимать на- чальные элементы £[0) и £[1]. В £[01 мы поместим ссылку на первый элемент первого звена цепочки, а в £[1] для удобства вклю- чения новых символов по- местим значение индекса S[o>z $U]=S 5[з]-кодс,д”) sV/\=6 ^—индекс первого звена ' *—индекс первого свободного -J зпемента $[5}=К0Д(„1") 5И=0 $17^К0Д(„5”) S[fl Рис. 3.1. Представление строки а15. первого свободного элемен- та массива £. Например, представление в виде цепочки строки а15 отображается на массив £, как показано на рис. 3.1. Легко увидеть, что если индекс звена с символом х равен i, то в элементе £[<] находится индекс звена с символом, следующим за х, а в соседнем элементе £[£Ч-1 ] находится код символа х. В нашем примере индекс звена с символом а равен 2, индекс звена с символом I равен 4 и индекс звена с символом 5 равен 6. В частности, строка £ может оказаться пустой. В таком случае, ее заглавное звено состоит из элементов £[01—0 и £[11—2. 3.3.3. Включение символа в строку и исключение символа. Рассмотрим теперь операцию включения символа. Пусть I — индекс звена, после которого нужно вставить новый символ. Новое звено помещается на свободное место, указываемое значением £[1 ]. Код включаемого символа записывается во вторую позицию нового звена и становится значением элемента £[£[ 1 ]-Н ]. Перед ним нужно записать в элемент £[£[1]] ссылку на следующее звено. Раньше это звено следовало за звеном с индексом I. Поэтому нужная нам ссыл- ка хранится в первой позиции звена с индексом i, т. е. в элементе £[i]. Итак, нужно выполнить присваивание £[£[111: =£[»]. Предшествующее разрыву звено, которое занимает элементы £[»] и £[»+!], должно теперь содержать в своей первой позиции ссылку на вновь включенное звено, которое мы поместили в элемен-
148 СТРУКТУРЫ ДАННЫХ 1Гл 3 ты массива S с индексами 5(11 и S[ll+1. Поэтому выполняется при- сваивание S[il:=5lll. Поскольку мы заняли два новых элемента в массиве S, то соответственно изменяется указатель первого свободного элемента: 5(11:=5(11+2. Например, пусть в строку а!5 нужно вставить 2 так, чтобы получилась строка в!25. В схематическом изображении представление строки до включения символа было после включения символа 2 стало I I а —1 5 2 ♦ I Символ 2 вставляется после символа I, т. е. после звена, индекс которого ра- вен 4. Итак, i=4, код символа 2 занесет- ся в 5(9], и массив 5 будет содержать элементы, изображенные на рис. 3.2. Напишем на алголе процедуру вклю- чения символа. procedure В СТРОКУ (5) символ-, (х) после-. (t); value х, i; integer array 5; integer*, i; begin 51511 ]+!]:=*; comment символ x заносится в новое звено-, 5[S[1 U:=S[j]; comment в первую позицию нового звена зано- сится ссылка на звено после разрыва-, Sit]:—5(11; comment в звено перед разрывом заносится ссыл- ка на новое звено-, 5(1]: =5(11+2; comment указатель первого свободного эле- мента продвигается вперед-, end Можно ввести для наглядности вспомогательную переменную нов и помещать в нее индекс 511 ] начала нового звена. Тогда проце- дура В СТРОКУ будет выглядеть так: procedure В СТРОКУ (S) символах) после:(1); value х, i; integer array 5; integer x, i; begin integer нов; woe: =5(1]; 5(нов+1]:=х; 5l«oe]:=Sltl; S(tl:= нов; 5(11:=5(11+2 end Теперь рассмотрим задачу исключения символа из строки. Пусть задано значение i и нужно исключить из строки символ, который содержится в звене, следующем за звеном с индексом I.
8.31 z СТРОКИ '''мэ Тогда индекс звена с исключаемым символом равен значению 5Ь‘1. Иначе говоря, исключаемое звено цепочки занимает в массиве S элементы с индексами Sh'l и 5[«]-Н. В элементе с индексом 51Л, т. е. в 515U']], находится ссылка на звено, следующее за исключае- мым. После исключения это звено должно следовать за звеном с ин- дексом t. Поэтому операция исключения символа состоит в том, что значение 5h’] заменяется на значение 5lS[i]]. Изменение ссылок при вычеркивании символа 1 из строки а15 можно пояснить следующей схемой: было о —► 1 —> 5 стало ауИ ->5 Процедура исключения символа: procedure ИЗ СТРОКИ (5) после:(Г); value i; integer array 5; integer i; 5[tl:=S[5(i]] Исключенное звено остается в массиве 5, однако с точки зрения стро- ки оно перестало существовать, так как на него теперь нет ссылок из цепочки. Замечание. Если мы включаем или исключаем символ в начале строки, то при обращении к процедуре В СТРОКУ или ИЗ СТРОКИ указывается параметр 1=0, означающий, что символ вклю- чается или исключается после заглавного звена цепочки. 3.3.4. Более сложные операции над строками. 3.3.4.1. Включение строки в строку. Нужно включить в строку S после звена с индексом i группу символов, представленную в виде строки 51. Например, если включить в строку а!5 после символа 1 строку &3г, то схема ссылок в полученной строке будет такой: I I а—► 1-т* 5 Ь—*3—»г t I Будем считать, что при включении строки 51 в строку 5 строка 51 не должна изменяться, и следовательно, ее элементы нужно ско- пировать в строку 5. Будем вставлять строку S1 «посимвольно», перебирая последо-’ вательно содержащиеся в ней символы. Первый символ строки S1 вставляется после символа строки 5, находящегося в звене с индек- сом i, а каждый следующий — после только что вставленного. Ниже приводится описацие соответствующей процедуры, в котором пере- менная k служит указателем в строке 51 очередного ее звена. procedure ВКЛСТР (51) в строку.(8) после-.Ц)-, value i; integer array 5, 51; integer i;
150Г СТРУКТУРЫ ДАННЫХ 1Гл. 3 begin integer k;k:—O-, for k: =31 [61 while 6=#0 do begin В СТРОКУ (3, SI [6+11, i); K=S[i] end end ВКЛСТР Предполагается, что каждая строка 3 и 31 хранится в соответ- ствующем массиве стандартным образом: заглавное звено занимает элементы с индексами 0 и 1. В частности, в элементе 3110] находится указатель первого звена строки 31. При первом выполнении цикла в его заголовке производится присваивание k: =31 [01, т. е. значе- нием k становится индекс первого звена. Во второй позиции этого звена (в элементе 31 [6+11) находится код первого символа строки 31, который заносится в строку 3 оператором процедуры В СТ РОКУ (S, 31 [6+П, <)• Поскольку новое звено включается вслед за звеном с индексом I, то его индекс оказывается в S[i]. Поэтому, выполнив присваивание i:=S[t], мы тем самым занесли в i индекс нового зве- на в строке 3. (В рассмотренном примере включения строки ЬЗг в строку «15 на этом этапе строка 3 имеет уже вид al Ь5, а перемен- ная i содержит индекс звена с символом b в строке 3). Таким обра- зом,- следующий, второй, символ из строки 31 нужно опять заносить в строку 3 после звена с индексом i. Индекс второго звена в цепочке символов строки 31 находится в 31 [6]. При втором выполнении цик- ла в его заголовке производится присваивание 6: =31 [6], т. е. зна- чением 6 становится индекс второго звена строки 31. (В нашем при- мере это индекс звена строки ЬЗг, содержащего символ 3.) Теперь оператор В СТРОКУ занесет в строку 3 второй символ из S1. (В при- мере строка S примет вид al 635.) Таким образом, в строку 3 будут последовательно включены все символы строки 31. Когда будет включен последний символ из 31, то в первой позиции соответст- вующего звена цепочки 31 окажется значение 31 [61=0, так что после присваивания 6: =31 [6] условие заголовка цикла while 6=#0 не вы- полнится, и на этом выполнение цикла (и всей процедуры ВКЛСТР) завершится. В конце процедуры после символа end написан комментарий ВКЛСТР, который показывает, какая процедура кончилась. Такие комментарии часто используются для большей наглядности алго- - ритма. 3.3.4.2. Исключение подстроки из строки. Нужно исключить из строки 3 идущие подряд п символов, следующие за звеном с индек- сом L Будем исключать по одному символу. Тогда очевидно, что каждый раз нужно исключать очередной символ, следующий за авеном с индексом i. Это реализуется следующей процедурой: procedure ИСКЛСТР (3) после-.(i) количестео-.(п)-, value i, п-, integer array 3; integer i, n; begin integer 6; for 6: = 1 step 1 until n do ИЗ CTPOKH(S, i) end ИСКЛСТР
3.3) СТРОКИ После каждого очередного исключения символа звено, предшествую- щее месту разрыва цепочки, будет содержать в своей справке 5h’l индекс нового звена, следовавшего в цепочке за только что исклю- ченным. 3.3.4.3. Поиск вхождения одной строки в другую. Пусть имеются строки 5 и 51. Нужно определить, является ли 51 подстрокой стро- ки 5, и если да, то после какого элемента в 5 расположена подстро- ка, совпадающая со строкой 51. Ответ будет состоять из двух значений: 1) логического (истина, если S1 входит в 5); 2) числового (индекса i предшествующего подстроке звена в це- почке 5). Заметим, что поиск вхождения одной строки в другую — это «базовая операция» нормального алгоритма. Опишем процедуру ПОДСТРОКА решения этой задачи. Снача- ла дадим неформальное описание процедуры. Будем последовательно проверять, не входит ли строка 51 в строку 5 с самого начала строки 5, после первого ее символа, после Рис. 3.3. Блок-схема процедуры ПОДСТРОКА, второго символа и т. д. В соответствии с этим будем присваивать пе- ременной i значения индексов заглавного звена цепочки 5, первого звена цепочки 5 и т. д. При каждом новом значении i будем сравни- вать посимвольно строку 51 с очередным участком строки 5 (сле- дующим за звеном с индексом 0. Индексы участвующих в сравнении звеньев будем указывать соответственно значениями переменных / и k. Начальные значения этих переменных должны быть /==5110] и
152 СТРУКТУРЫ ДАННЫХ 1Гл. » k=S[t 1. Коды символов, которые нужно сравнивать, находятся со* ответственно в элементах Sil j+1J и 1 ], а в SI [/1 и 3[&] находятся ссылки на следующие звенья. Последующие сравниваемые символы также будут браться из звеньев, индексы которых являются значени- ями переменных / и k; для этого после каждого удачного сравнения символов из 31 [/+1 ] и S[£+11 мы выполняем присваивания /:=31[/] и k: — SIZsJ. Если в процессе сравнений оказывается, что строка 31 исчерпана (т. е. j=0), то тем самым мы нашли вхождение строки 31 .в строку S. (Если строка 31 пустая, то вхождение обнаружива- ется сразу.) Если же строка 31 еще не исчерпана, а строка 3 кон- чилась (т. е. Л=0), то дальнейший поиск бесполезен, вхождения строки 31 в 3 нет. Если же до исчерпания строк 31 и 3 обнаружи- вается несовпадение очередных сравниваемых символов, то в дан- ном месте строки 3 нет вхождения строки 31 и мы переходим к следующему начальному месту сравнения в строке 3, выполняя при- сваивание t:=SUJ. Введем логическую переменную входит, которой будем при- сваивать значение true, если строка 51 входит в строку 3, или зна- чение false, если такого вхождения нет. На рис. 3.3 изображена блок-схема процедуры ПОДСТРОКА. На алголе эта процедура может быть записана так: procedure ПОДСТРОКА (S1) в строке. (S) после'. (О признаку (входит)-, integer array 31, 3; integer i; boolean входит; begin i:=0; след: begin integer /, k; /:=S1[O]; £:=S[t|; npod: if /=0 then begin входит:—true; go to выход end; if k=Q then begin входит: =false; go to выход end; if 31 [/+1]=S^+1] then begin /:=S1 (/]; fe:=S[&]; go to npod end else begin i:=S [i]; go to след end end; выход: end ПОДСТРОКА Блок с меткой след будет выполняться для каждого значения i, т. е. при каждом изменении начального места сравнения в иссле- дуемой строке 3. В i остается информация о том, какая часть строки 3 подошла при сравнении. Если строка S1 входит в 3 несколько раз, то наша процедура обнаружит первое вхождение. 3.3.5. Реализация универсального нормального алгоритма. В качестве примера работы со строками рассмотрим реализацию на алголе универсального нормального алгоритма. Как мы уже знаем (см. раздел 1.3), запись нормального алго- ритма— это последовательность формул вида Л;{^}Вг. Применяя очередную формулу, мы ищем в слове, к которому применяется ал-
СТРОКИ 153 3.3J горитм, вхождение ее левой части. Если такое вхождение найдено, то оно исключается, а вместо него подставляется правая часть фор- мулы. Если данная формула применилась успешно (вхождение най- дено), то начинается проверка на вхождение снова с первой форму- лы, в противном случае происходит переход к следующей формуле. Выполнение алгоритма заканчивается, если после некоторого действия не применилась ни одна формула или если успешно при- менилась завершающая формула (формула, в которой левая и пра- вая части разделены стрелкой >-*). Пусть исходное слово хранится и преобразуется в массиве слово как строка, представленная в виде цепочки (со ссылками). Нор- мальный алгоритм будем хранить в массиве алг, в векторном пред- ставлении без ссылок. (Сцеплять символы ссылками нет необхо- димости, так как эту строку не придется изменять.) Можно счи- тать, что при записи исходного слова и нормального алгоритма ис- пользуются символы из алфавита языка алгол. Для этого будем кодировать стрелку—»• как разделитель f, а стрелку •—> как символ :=. Между формулами будем ставить запятую, а за последней фор- мулой поставим код 999. Левую и правую части формулы, которая применяется в данный момент, будем хранить в специальных рабочих массивах лев и прав в виде цепочек, чтобы удобно было сопоставлять их с цепочкой пре- образуемого слова в массиве слово. При выделении из массива алг очередной формулы используем процедуру ЧАСТЬ ФОРМУЛ Ы(Х), которая при подстановке вместо X фактического параметра лев или прав будет выделять соответствующую левую или правую часть формулы и формировать из нее цепочку символов в массиве лев или прав. Перед обращением к процедуре ЧАСТЬ ФОРМУЛЫ в глобаль- ную переменную п заносится индекс в массиве алг того элемента, вслед за которым начинается нужная часть формулы. Эта часть выделяется символ за символом, пока не встретится разделитель стрелка или запятая или код 999. (Стрелка означает, что кончилась левая часть формулы, а запятая или 999 означает конец правой час- ти, т. е. всей формулы.) При перенесении очередной части формулы из массива алг в массив X (т. е. в массив лев или прав) процедура ЧАСТЬ ФОРМУЛЫ одновременно преобразует эту информацию из векторного представления в представление в виде цепочки. После своей работы процедура ЧАСТЬ ФОРМУЛЫ оставляет измененное значение л: теперь оно равно индексу элемента масси- ва алг, содержащего тот разделитель, который следует за выделен- ной частью формулы. Работа процедуры ЧАСТЬ ФОРМУЛЫ состоит в следующем: 1°. Образуется пустая цепочка в массиве X. Для этого в заглав- ное звено заносятся значения Л10]=0 и Х[ 11=2. Указателю k зве- на в цепочке X присваивается начальное значение £:=0. (Тем са-
154 СТРУКТУРЫ ДАННЫХ 1Гл. а мым мы направляем этот указатель на заглавное звено цепочки.) К значению п прибавляется 1. (Теперь указатель п направлен на первый символ выделяемой части формулы в массиве алг.) 2°. Из массива алг выбирается код очередного символа алгШ, и если это не КОД („!”)> или КОД („:=”), или КОД („,”), или 999, то он заносится в очередное свободное звено цепочки X оператором В СТРОКУ (X, алгМ, fe). После этого указатель п направляется на следующий символ в векторе алг (присваивается значение n:=n-H) и указатель k продвигается на одно звено впе- ред присваиванием k'.=X\k\. Таким образом, указатель k всегда направлен на последнее заполненное звено цепочки X. Выполнение пункта 2° повторяется до тех Нор, пока очередной символ алгЬг] не окажется разделителем. Если же алг(п) — это КОД („f”), КОД („:=”), КОД („,”) или 999, то выполнение процедуры ЧАСТЬ ФОРМУЛЫ заканчивается. Запишем это на алголе: procedure ЧАСТЬ ФОРМУЛЫ(Х)\ integer array X; begin integer k, p\ X[01:=0; XUJ:=2; Л:=0; n:=n-|-l; for p:=0 while алг\п\=£КОД(„ f ”)Д<мгЫ «/=ДОД(„:=”)Далг 1п^К0Д(„, ")Г\алг [nl¥=999 do begin В СТРОКУ (X, алг1п],к)-, n:=n+l; k:=Xlk] end end Переменная p введена для использования ее в качестве фиктив- ного параметра оператора цикла (в соответствии с требованием син- таксиса). Переменные п и k нельзя использовать для этой цели, по- тому что после выхода из цикла значение его параметра становится неопределенным. Фактически мы, идя снизу вверх, уже выполнили большую часть разработки процедуры УНА (слово, алг) применения нормального алгоритма из массива алг к слову из массива слово. Мы последова- тельно вводили и реализовывали все более крупные базовые опе- рации: сначала В СТРОКУ и ИЗ СТРОКИ, а затем ВКЛСТР, ИСКЛСТР, ПОДСТРОКА и, наконец, ЧАСТЬ ФОРМУЛЫ. Те- перь, опираясь на эти операции, будем разрабатывать процедуру УНА по принципу сверху вниз. Естественно начать с составления общей блок-схемы разрабатываемой процедуры. Такая блок-схема приведена на рис. 3.4. Основываясь на этой блок-схеме, можно разработать более де- тальную схему процедуры УНА. Такая схема приводится ниже. В ней пункт Г соответствует первому прямоугольнику блок-схемы, пункты 2°—6° являются детализацией выбора очередной фор- мулы. Пункты 7° и 8° соответствуют первому ромбу блок-схемы, пункт 9° — последнему прямоугольнику (т. е. замене Лг->Вг). Проверка, была ли формула завершающей, реализуется пунк-
3.3] СТРОКИ 155 том 10°. Проверка, была ли формула последней, выполняется в пункте 11°. Схема процедуры УНА. Г. Присвоить значение п:=0. (Чтобы выделять левую часть первой формулы, которая хранится в массиве алг, начиная с эле- мента алг [1], направляем указатель п на предыдущий элемент.) Рис. 3.4. Блок-схема процедуры УНА. 2°. Запомнить значение п присваиванием j:=n. 3°. Выделить левую часть очередной формулы оператором ЧАСТЬ ФОРМУЛЫ (дев). (Теперь значение п указывает индекс эле- мента в массиве алг, содержащего разделительную стрелку после левой части.) 4°. Вычислить длину левой части /:=п—/—1. (Значение / нам понадобится как параметр для обращения к процедуре ИЗ СТРОКИ, - если мы найдем вхождение этой левой части в слово и будем исклю- чать это вхождение, чтобы заменить его на правую часть формулы.) 5°. Если алг [п]=КОД („:=”), то формула является завершающей. В этом случае присвоить переменной зав значение true. В против- ном случае присвоить зав значение false. (Этот пункт выполняется одним оператором присваивания зав: =алг[п]=КОД(„:
156 СТРУКТУРЫ ДАННЫХ (Гл. 3 6°. Выделить правую часть формулы оператором ЧАСТЬ ФОР- МУЛЫ (прав). (Теперь значение п указывает на разделитель после формулы.) 7°. Искать вхождение левой части формулы в слово оператором ПОДСТРОКА (лев, слово, вхож, т). 8°. Если вхождение не найдено (вхож—false), то перейти к пунк- ту 11°. (Если же вхож=true, то в результате работы процедуры ПОДСТРОКА мы получили в переменной т индекс звена в цепочке слово, предшествующего этому вхождению.) 9°.Выполнить процедуру исключения левой части оператором ИСКЛСТР (слово, tn, I) и процедуру включения на то же место пра- вой части формулы оператором ВКЛСТР (прав, слово, т). 10°. Если данная формула завершающая (sae=true), то окончить выполнение процедуры УНА. В противном случае перейти к п. Г. ' 11 °. Если алг [п]=999 (т. е. очередная формула оказалась послед- ней), то окончить выполнение процедуры УНА. В противном слу- чае перейти к п. 2°. Теперь можно написать процедуру УНА на алголе: procedure УНА(слово, алг)-, integer array слово, алг-, begin integer j, I, m, n; boolean вхож, зав-, integer array лев, прав (0:100]; нач:п:—0: npod:j:=n-, ЧАСТЬ ФОРМУЛЫ (лев)-, 1-.—п—j—1; зав:=алг(п]=К0Д („:=”)', ЧАСТЬ ФОРМУЛЫ (прав)-, ПОДСТРОКА (лев, слово, вхож, т); if вхож then begin ИСКЛСТР(слово, т, /); ВКЛСТР (прав, слово, т); go to if зав then конец else нач end else if алг[/г|=/=999 then go to npod-, конец: end УНА Мы не включили в процедуру УНА описания используемых в ней процедур, предполагая, что эти описания будут включены в программу, в которой применяется процедуру УНА. (Процедуры ВКЛСТР, ИСКЛСТР, ПОДСТРОКА, ЧАСТЬ ФОРМУЛЫ и КОД используются в процедуре УНА явно, а описания процедур В СТРО- КУ и ИЗ СТРОКИ должны быть включены потому, что эти процеду- ры используются в процедурах ВКЛСТР и ИСКЛСТР.) 3.4. Списки Список — это упорядоченный набор объектов произвольных размера и природы. Эти объекты называются записями. Упорядо- ченность записей определяется программистом и зависит от назна-
«.4) СПИСКИ - чения списка. Понятие списка естественно обобщает привычные нам по повседневной жизни такие структуры, как список абонентов телефонной сети, список (каталог) книг в библиотеке, список со- трудников учреждения, список городов в стране и т. д. Основными операциями над списками являются — переход к соседней записи, — включение новой записи, — исключение записи. В определенном смысле рассмотренные нами структуры очереди, стека и строки представляют собой частные случаи списков. Однако в очереди и стеке доступны только краевые записи (первая или по- следняя по времени поступления), а в строке объектами (записями) являются только отдельные символы (точнее, их коды). Общая структура списка не предусматривает подобных ограничений. Как правило, списки представляются в виде цепочек, состоя- щих из звеньев со ссылками. Такими цепочками мы пользовались для представления строк, но там каждое звено имело одинаковую жесткую структуру и состояло из двух значений: ссылки на следую- щее звено и кода символа. Когда мы имеем дело с произвольным списком, может возникнуть потребность снабжать каждую его за- пись различной справочной информацией, характеризующей как саму эту запись (например, ее размер), так и взаимосвязи данной записи с другими записями списка. Сама запись становится телом очередного звена, а соответствующая ей справочная информация образует справку звена. Первая запись списка хранится в первом звене цепочки, которому предшествует специальное заглавное звено, содержащее некоторые начальные данные для работы с данной це- почкой. Для единообразия это звено условно тоже разделяется на две части: справку и тело. Существует много разных типов списков. Мы разберем наиболее распространенные типы и рассмотрим их реализацию на алго- ле. Каждый тип списка характеризуется особенностями инфор- мации, содержащейся в справках звеньев, а также в заглавном ввене. 3.4.1. Однонаправленные списки. Наиболее естественным и простым типом списка является одно- направленный список, предназначаемый для того, чтобы просматри- вать его в одном направлении, от начала к концу. Однонаправленный список представляется в виде цепочки, в которой справка каждого звена состоит из двух значений. Первым значением является длина Nt звена, которая складывается из дли- ны записи и длины справки, равной 2. Вторым значением является ссылка на начало следующего звена. Далее следует тело звена (за- пись). Заглавное звено состоит из трех значений:
158 СТРУКТУРЫ ДАННЫХ [Гл. 3 /Vo=3 Ссылка на начало первого звена Ссылка на начало свободного места Первые два значения образуют справку заглавного звена, а ссылка на свободное место представляет собой тело этого звена. В справке последнего звена ссылка равна нулю. Схема представления однонаправленного списка изображена на рис. 3.5. Заметим, что рассмотренное ранее представление строки в виде Рис. 3.5. Схематическое изображение однонаправленного списка. списка, в котором все записи содержат по одному значению и за не надобностью в справках отсутствуют указания длин звеньев (по- скольку все звенья занимают стандартно по два элемента массива). Однонаправленный список можно отобразить в одномерный ал- гольный массив S следующим образом. Заглавная запись будет за- нимать элементы S[0], S(l] и 3(2 J. Далее размещаются остальные записи. Если индекс звена, содержащего запись х, равен i, то в элементе SU) находится длина этого звена, в элементе S[i+11 находится ин- декс звена со следующей записью списка (или нуль, если запись х последняя). Далее, начиная с элемента SU4-2], содержится сама за- пись х. 3.4.1.1. Включение записи в список. Пусть i — индекс звена, пос- ле которого нужно включить новую запись. Новое звено помеща- ется в массив 3 на свободное место, указываемое значением S12]. Таким образом, справка нового звена занимает в массиве 3 элементы с индексами S[2] и 3121+1. В первый из них элемент S[S[2]1 зано- сится значение длины нового звена. В следующий элемент 3[3[21+1] нужно занести ссылку на звено, которое прежде следовало за зве- ном с индексом I, т. е. нужно переписать туда S[i+11: S[S[21+ll:=S[i+H
».4) списки 15Э Далее расположится сама включаемая запись (тело нового звена). Само же звено с индексом t должно быть теперь сцеплено со вновь включенным звеном, и поэтому мы заносим в его справку индекс но- вого звена, равный 312]: S[< + 1]: = S[2] Схема включения новой записи изображена на рис. 3.6. Пусть новая запись хранится в массиве инф и занимает п эле- Рис. 3.6. Схема включения новой записи в однонаправленный список. кой займет (п+2) элемента. Поэтому в начало его справки (в эле- мент SISI2J1) мы заносим значение п+2. Теперь свободная часть массива 5 начинается за новым звеном, и нужно изменить значение указателя первого свободного элемента: S[2]: = S[2] + n + 2 Ниже приводится соответствующая процедура В СПИСОК на алголе. Цикл в теле процедуры служит для перенесения элементов массива инф в массив S. Для наглядности вводится вспомогатель- ная переменная нов, в которую помещается индекс S(2] начала но- вого звена. procedure В СПИСОК(З) запись: (инф) длина: (п) после: (i); - value n, i; integer array S, инф-, integer n, i\ begin integer нов, k; нов: =3(2]; Since]:=n+2; S[noe+l]:=S[i+ll; for k: = \ step I until n do S(noe+l+^]:=un0[^]; S[i+l]:=noe; S(2]:=S[2J+n+2 end В СПИСОК 3.4.1.2. Исключение записи из списка. Пусть задано значение t и нужно исключить из списка запись, которая содержится в звене, следующем за звеном с индексом i. Индекс исключаемого звена ра- вен значению Sli'+H. Исключение сводится к изменению этого зна- чения и аналогично изменению ссылки при исключении символа из строки. Иначе говоря, значение SU+1 ] заменяется на значение, ко- торое хранилось в ссылочной части справки исключаемого звена. Схематически это показано на рис. 3.7.
160 СТРУКТУРЫ ДАННЫХ [Гл. 3 Для наглядности введем переменную след, значением которой будет индекс звена, следовавшего за звеном с индексом I: след:—5И+1] Тогда исключение записи сводится к выполнению присваивания S[t + 1]: = 5[сл^+ 1] (Напомним, что эта пара операторов эквивалентна одному присваи- ванию S[t+l]:=S[SU+l]+l ]). Рис. 3.7* Схема исключения записи из однонаправленного списка. Соответствующая процедура записывается так: procedure ИЗ СПИСКА^) после:(1)\ value i\ integer array S; integer i\ begin integer след; c^d:=SU+l]; 5[/+1]:=Зкжд+1] end Как и в случае исключения символа из строки, исключаемое из цепочки звено остается в массиве 5. Заметим, что поскольку первые элементы заглавного звена офор- мляются так же, как справка любого звена (длина и ссылка), то процедуры включения и исключения выполняются правильно и при значении f=sO, т. е. при включении или исключении первой записи списка. Когда из списка исключаются записи, возникает вопрос об ис- пользовании места хранения этих уже не нужных записей. Если все записи имеют одинаковую длину, то вновь включаемые записи можно помещать на место исключенных; для этого из исключенных звеньев обычно образуется специальный список свободных звеньев. Если же длины записей разные, то проблема использования обра- зующихся пустот оказывается довольно сложной. Один из способов ее решения состоит в том, чтобы время от времени образовывать на новом месте копию списка, последовательно включая в нее только те записи, которые фактически присутствуют в списке. 3.4.2, Двунаправленные списки. * Рассмотренные выше однонаправленные списки предусматрива- ют жесткий порядок перебора записей, от первой к последней. Бы- вает так, что возникает потребность не только двигаться вперед по списку, но и возвращаться назад, чтобы посмотреть, а возможно, и изменить содержимое предыдущих записей. Эту возможность обес- печивает структура двунаправленного списка.
8.4J СПИСКИ 161 с Такой список представляется в виде цепочки, в которой каждое звено содержит ссылку на только на следующее, но и на предшест- вующее ему звено. Эта цепочка размещается в одномерном массиве и начинается со стандартного заглавного звена из четырех значений в элементах с индексами 0, 1, 2 и 3: JV.=4 Ссылка на первое звено (сначала «4») Нуль Указатель первого свободного элемента Каждое звено начинается со справки стандартного вида из трех вначений: Длина звена Ссылка на следующее звено Ссылка на предыдущее звено Схематически двунаправленный список показан на рис. 3.8. В заглавном звене пуста ссылка на предыдущее звено, а в пос- Рис. 3.8. Схематическое изображение двунаправленного списка» Такие списки позволяют удобным образом перебирать записи не только слева направо, но и справа налево. Поскольку можно пе- ребирать записи в любую сторону, мы получаем возможность, на- пример, в операции исключения записи указывать саму эту запись, а не предшествующую, как это приходилось делать в случае одно- * >• 3. Любвмекяй др.
162 СТРУКТУРЫ ДАННЫХ (Гл. 3 направленных списков. (Предшествующая запись нам понадобит* ся, но мы найдем ее по соответствующей ссылке в исключаемой за* писи.) 3.4.3. Кольцевые списки. Самой распространенной разновидностью двунаправленных списков являются кольцевые списки. В двунаправленном кольце- вом списке последнее звено ссылается как на следующее на заглав- ное звено, а то в свою очередь, ссылается на последнее звено как на Рис. 3.9. Схематическое изображение кольцевого списка. предыдущее. Такая организация списка упрощает процедуру по- иска или перебора записей с любого места с автоматическим перехо- дом от конца к началу или наоборот. Для краткости будем называть двунаправленный кольцевой список просто кольцевым списком. (Бывают и однонаправленные кольцевые списки, однако здесь мы не станем их рассматривать.) Схематически кольцевой список показан на рис. 3.9. 3.4.3.1. Включение записи в кольцевой список. Пусть требуется вставить звено с новой записью после звена с индексом i. Новое зве- но помещается на свободное место и включается в список путем кор- рекции ссылок в звене с индексом i и в том звене, которое раньше за ним следовало. Напишем процедуру В КОЛЬЦО включения записи в кольце- вой список. Запись длины п находится в массиве инф и вставляется в список после звена, начинающегося с элемента S[i]. Схема процедуры. Г. Заполнить первый элемент справки нового звена (его индекс находится в S[31) числом п+3. 2°. Заполнить второй элемент справки нового звена ссылкой на следующее за вставляемым звено. 3°. Заполнить третий элемент справки нового звена ссылкой на предшествующее вставляемому звено. 4°. Перенести в массив S запись из массива инф.
8.4] списки 163 5°. Изменить ссылку на предшествующее звено в справке звена, следующего за вставляемым. 6°. Изменить ссылку. на следующее звено в звене, указываемом значением i. 7°. Изменить указатель начала свободного места. Как и в случае однонаправленного списка, для наглядности ис- пользуем переменные нов и след. procedure В КОЛЬЦО (S) зтись: (инф) длина: (п) после: (i); value п, i; integer array 5, снф: integer n, i\ begin integer нов, след, k\ нов:=513); Med:=SU+l]; S [woel:=n+3; S[Hoe+ll:=ctfd; SlHoe-i-2]:==i; for k: — l step 1 until n do S [нов+2+к]:=инф [Л]; 3{след+2\:=нов\ SU+1 \:—нов\ S[3]:=S[3]+n+3 end 3.4.3.2. Исключение записи из кольцевого списка. Чтобы исклю- чить запись из кольцевого списка, нужно изменить две ссылки: у «соседа слева» изменяется ссылка на следующее звено, а у «соседа справа» — ссылка на предыдущее звено. Как и всякий двунаправленный, кольцевой список имеет то преимущество перед однонаправленным, что можно указывать ин- декс начала исключаемого звена, а не индекс начала звена, предше- ствующего исключаемому. (Этот последний индекс не обязательно указывать явно, так как его можно найти в справке исключаемого звена.) Схематически исключение записи показано на рис.3.10. При описании процедуры ИЗ КОЛЬЦА исключения записи вос- пользуемся переменными tiped=SU+2] и след=5[Н-11, значениями которых являются соответственно индексы звеньев, предшествую- 6*
164 j СТРУКТУРЫ ДАННЫХ [Гл. 3 ' щего и следующего за исключаемым звеном, которое указывается значением его индекса I. procedure ИЗ КОЛЬЦА (S) звено: (0; value 0 integer array S; integer i; begin integer след, пред', след:=5[Ж1; nped:=SU+2]; 3[пред+1]:=след; S [след+2]:=пред end 3.4.4. Иерархические списки. Когда мы объединяем информационные записи в список, тем самым образуется группа записей с определенным внутренним по* рядком. Можно рассматривать эту группу как единый информацион- ный объект и образовывать списки из таких составных объектов. Например, можно организовать список высших учебных заведений, где каждому вузу соответствует список факультетов. Такая иерар- хия может быть многоступенчатой, информацию о каждом факуль- тете можно представить как список курсов, информацию о курсе —> как список групп, а информацию о группе как список студентов. Если элементами списка верхнего уровня являются подчиненные списки, то удобно считать, что с точки зрения списка верхнего уровня входящая в него запись представляет собой ссылки на под- чиненный список, оформленные как заголовок этого подчиненного списка (заглавное звено цепочки, представляющей подчиненный список). При этом в теле заголовка подсписка может быть и некото- рая смысловая информация общего характера. Например, в заго- ловке списка студентов группы может указываться номер этой группы, фамилия старосты и т. д.
3.41 списки 165 Список верхнего уровня может содержать наряду с заголовками подчиненных списков и самостоятельные элементы (обычные записи). Таким образом, список верхнего уровня представляется в виде це- почки, в которой каждое звено состоит, во-первых, из справки и, во-вторых, из обычной записи или же заголовка подсписка (спис- ка с нижнего уровня иерархии). В иерархическом списке, схематически изображенном на рис. 3.11, двунаправленный список самого верхнего уровня состоит из четырех элементов, причем первый и третий элементы являются обычными записями. Второй элемент этого списка представляет со- бой однонаправленный список, в котором третий элемент — это двунаправленный кольцевой список, состоящий из обычной записи, однонаправленного кольцевого списка и еще одной обычной за- писи. Четвертый элемент списка самого верхнего уровня является двунаправленным кольцевым списком.из двух записей. Таким об- разом, иерархически связанные между собой списки могут быть разных типов. 3.4.5. Ассоциативные списки. Часто одни и те же объекты представляют интерес с различных точек -зрения, и возникает естественная потребность включать их в различные списки, составляемые по разным признакам. Напри- мер, одни и те же члены некоторого коллектива могут входить в раз- нообразные списки (по возрасту, месту рождения, месту учебы и другим признакам). Если в разных списках нужна одна и та же информация о каждом объекте, то желательно не дублировать за- писи. Избавиться от такого дублирования позволяют ассоциативные списки. Они организуются на одном общем наборе записей, причем каждый из ассоциативных списков объединяет в определенном по- рядке те записи из этого набора, которые обладают некоторым ха- , рактерным признаком. (Слово «ассоциативный» выбрано потому, что мы объединяем записи в список, ассоциируя их по некоторому - признаку.) Каждый ассоциативный список представляется в виде отдель- ной цепочки со своим заглавным звеном, которое помещается на фиксированном месте и позволяет производить операции над этим списком в отдельности. Эти цепочки переплетаются, поскольку од- ни и те же звенья входят в различные цепочки. Каждое такое зве- но, как и обычно, состоит из информационной части (записи) и стан- дартной справки. В справке находятся несколько ссылок или пар ссылок (по числу ассоциативных списков, в которые входят эти за- писи). Объединение различных списков на базе общего набора запи- сей называется также информационной сетью и представляет собой наиболее общую форму организации информации, соответствую- щую многосвязности объектов в окружающей среде. Как мы внаем, элементы данных, описывающие связанные элементы среды, также
166 СТРУКТУРЫ ДАННЫХ (Гл. 3 должны быть связаны между собой (либо через индексную арифме- тику, либо через сцепление.) Информационная сеть обеспечивает одновременную реализацию различных связей элементов данных. 3.4.6. Пример информационной сети с иерархическими и ассо- циативными связями. Составим список сотрудников некоторого подразделения с раз- биением на занимаемые ими служебные комнаты. Организуем так- же списки всех сотрудников данного подразделения с высшим обра- зованием, сотрудников мужского пола и сотрудниц. В качестве главного списка возьмем однонаправленный спи- сок комнат. Запись, представляющая комнату, является заголов- ком ассоциативного списка сотрудников, работающих в этой ком- нате. Этот и остальные ассоциативные списки будут основываться на одном общем наборе записей о сотрудниках. Информация о со- труднике может включать фамилию, имя, отчество, год рождения, место жительства и другие данные. Каждая запись о сотруднике по- падет в список одной из комнат, в список мужчин или женщин и мо- жет попасть в список имеющих высшее образование. Все эти ассо- циативные списки мы сделаем двунаправленными кольцевыми. За- главное звено списка каждой комнаты входит в список комнат и поэтому должно содержать ссылку^ вперед по списку комнат. Каж- дая запись о сотруднике представляет собой информационную часть соответствующего звена со4справкой стандартного вида, отражаю- щей связи этого звена в различных ассоциативных списках. Посколь- ку удобно, чтобы справка заглавного звена списка сотрудников в комнате не отличалась от справок остальных звеньев, относящихся к конкретным сотрудникам, то мы введем следующую единую струк- туру звена: SU’] — длина звена. SU+U — ссылка вперед по списку комнат (для звена, описы- вающего сотрудника, она не заполняется). SB+2] — ссылка вперед по списку сотрудников в комнате. SU+3] — ссылка назад по списку сотрудников в комнате. SU+4] — ссылка вперед по списку имеющих высшее образова- ние (для сотрудников без высшего образования она не заполняется). SU+5] — ссылка назад по списку имеющих высшее образова- ние (для сотрудников без высшего образования она не заполняется). S[i+6] — ссылка вперед по списку принадлежащих к мужскому или женскому полу. SU+7] — ссылка назад по списку принадлежащих к мужскому или женскому полу. SU+8] — начало информационной части звена. Элементы SU+6] и SU+7] будут использоваться по-разному в •ависимости от пола сотрудников. Такие ассоциативные списки называются альтернативными (в одних и тех же элементах справки
списки 167 в зависимости от пола сотрудников будут находиться ссылки, отно- сящиеся либо к списку мужчин, либо к списку женщин). Разумеет- ся, такое альтернативное использование места под ссылки возможно при строго непересекающемся членстве. Аналогично элементы SU+4] и SU+5] можно было бы использовать для организации ка- ких-либо списков сотрудников, заведомо не имеющих высшего об- разования,— например, для списка студентов-заочников. В звене, входящем в список комнат, заполняется элемент SU+11. В этом звене элементы SU+2] и SU+3] тоже заполняются, потому что оно входит в список сотрудников данной комнаты, яв- ляясь его заголовком. В его теле в элементе SU+8] договоримся хранить номер комнаты. Элементы справки этого звена S[i+4], SU’+5], SU+6] и SU+7] не заполняются, потому что они имеют смысл только для звеньев, относящихся к конкретным сотруд- никам. Заведем заголовки для главного и ассоциативных списков. Заго- ловки должны содержать ссылки в тех же позициях, что и обычные звенья соответствующих списков, поэтому в них образуются не за- полненные позиции. Положение всех заголовков фиксируем в по- следовательных элементах в начале массива S. 1) Заголовок списка комнат: S101 3. S[ll — ссылка вперед по списку комнат. S[2] — указатель свободного места. 2) Заголовок списка имеющих высшее образование: S[3] 6. S[4] не заполнено. S[5] не заполнено. S[6] не заполнено. S[7] — ссылка на первое звено в списке обладающих высшим образованием. S[8] — ссылка на последнее звено в списке обладающих высшим образованием (так как список кольцевой). 3) Заголовок списка мужчин: SI9] 8. S110] не заполнено. SI11] не заполнено. SI 12] не заполнено. SI 13] не заполнено. SU4] не заполнено. SI 15] — ссылка на первое звено в списке мужчин. SI16] — ссылка на последнее звено в списке мужчин. 4) Заголовок списка женщин выглядит так же: SU7] 8. S[181 не заполнено. S119] не заполнено.
168 СТРУКТУРЫ ДАННЫХ [Гл. 8 S[201 не заполнено. S1211 не заполнено. S122] не .заполнено. <S[23] — ссылка на первое звено в списке женщин. S124] — ссылка на последнее звено в списке женщин. Для иллюстрации сказанного рассмотрим пример списка подраз* деления, которое занимает две комнаты. В первой комнате работают две сотрудницы, во второй — одна. Первая сотрудница из первой комнаты и единственная сотрудница из второй комнаты обладают высшим образованием. Сотрудников мужского пола в подразделе- нии нет. На рис. 3.12 изображена схема объединения всех записей Рис. 3.12. Схема объединения записей о сотрудниках в списки. в списки, а в табл. 3.3 приведены значения всех элементов масси- ва S, соответствующие этой схеме объединения. С такой информационной сетью можно работать, по мере надоб- ности включая в нее или исключая записи о сотрудниках или за- писи о комнатах. Она позволяет перебирать информацию обо всех сотрудниках подразделения или отдельной комнаты, обо всех со- трудниках, имеющих высшее образование и т. д. Составим процедуру ВКЛСОТР включения записи о сотруднике во все ассоциативные списки, к которым относится этот сотрудник. Предварительно напишем вспомогательную процедуру ВКЛ включения записи о сотруднике в один (любой) из ассоциативных списков, организованных на данной совокупности записей. Одним из параметров этой процедуры будет указатель места, которое в стандартной справке каждого звена отводится для ссылок, относя- щихся к данному списку. Если указатель места ссылок равен k, то это означает, что в каждом звене, которое начинается с элемента
3.4} СПИСКИ 189 Таблица 3.3 1 s Ш Пояснения 0 1 2 3 25 81 Длина заголовка Ссылка на заголовок списка комнаты 1 Указатель свободного места Заголовок глав- ного списка 3 4 5 6 7 8 6 34 68 Длина заголовка Ссылка вперед по списку имеющих высшее образова- ние Ссылка назад по списку имеющих высшее образова- ние Заголовок спи- ска имеющих высшее образо- вание 9 10 11 12 13 14 15 16 го со 1 1 1 1 1 со Длина заголовка Ссылка вперед по списку мужчин Ссылка назад по списку мужчин Заголовок спи- ска мужчин 17 18 19 20 21 22 23 24 ® 1 1 1 1 IS $ Длина заголовка Ссылка вперед по списку женщин Ссылка назад по списку женщин Заголовок спи- ска женщин 25 26 27 28 29 30 31 32 9 46 34 55 Длина звена Ссылка на заголовок списка комнаты 2 Ссылка на 1-го сотрудника из комнаты Ссылка на последнего сот- рудника из комнаты Заголовок спи- ска комнаты 1 (справка звена главного спи- ска) Звено главного Списка
170 СТРУКТУРЫ ДАННЫХ (Гл. 3 Продолжение табл. 3.3 i SH1 Пояснения 33 1 Номер комнаты Тело звена 34 35 36 37 38 39 40 41 12 55 25 68 3 68 55 Длина звена |Ссылки по списку комнаты ^Ссылки по списку имею- /щих высшее образование jСсылки по списку женщин Справка звена Звено ассоциа- тивных списков 42 43 44 45 Информация о 1-й сотруд- нице из комнаты 1 Тело звена 46 47 48 49 50 51 52 53 9 0 68 68 Длина звена Следующей комнаты нет ^Ссылки по списку сотруд- / ников из комнаты 2 Заголовок спи- ска комнаты 2 (справка звена главного спи- ска) Звено главного списка 54 2 Номер комнаты Тело звена 55 56 57 58 59 60 61 62 13 25 34 0 0 34 17 Длина звена ^Ссылки по списку комнаты | Ссылки по списку имею- /щих высшее образование ^Ссылки по списку женщин Справка звена Звено ассоци- ативных спи- сков 63 64 65 66 67 Информация о 2-й сотруд- нице из комнаты 1 Тело звена
3.4] списки 171 Продолжение табл. 3,3 1 Пояснения 68 69 70 71 72 73 74 75 13 46 46 3 34 17 34 Длина звена |Ссылки по списку комнаты ^Ссылки по списку имею- j щих высшее образование |Ссылки по списку женщин Справка звена Звено ассоциа- тивных списков 76 77 78 79 80 Информация о 1-й сотруд- нице из комнаты 2 Тело звена 81 82 Свободное место в массиве S[f], элемент Sli+fel содержит ссылку вперед, a SU+&+1] содер- жит ссылку назад по данному ассоциативному списку. Поэтому для данного ассоциативного списка введенные нами ранее величины пред и след вычисляются так: nped:~Sli+k+\ 1; CAed:=S\i+k\ В нашем примере указатель места ссылок для списка сотрудников в комнате равен 2, для списка имеющих высшее образование равен 4, для списков мужчин или женщин равен 6. Будем писать вспомогательную процедуру ВКЛ, предполагая, что новая информация о сотруднике уже перенесена в массив S и начинается с элемента Мы включаем новое звено в конец каждого списка, а поскольку список кольцевой, то это означает, что новое звено включается перед заглавным звеном соответствую- щего списка. Поэтому удобно при обращении к процедуре BKJI указывать индекс след звена, перед которым нужно включить в список новое звено. (Прежде в аналогичных процедурах мы вклю- чали новое звено не перед, а после указанного звена.) Необходимо сформировать соответствующие данному списку ссылки в новом звене и откорректировать ссылки в тех звеньях, между которыми
172 СТРУКТУРЫ ДАННЫХ [Гл. 3 оно вставляется. Индекс пред звена, предшествующего включаемо- му, определяется присваиванием пред:=8(след+к+\]. Процедура ВКЛ может иметь вид: procedure ВКЛ (ное) перед: (след) место ссылок: (k); value ное, след, k; integer ное, след, k; begin integer пред’, пред :=5[след-|-Н-11; 8[нов+к]:=след; S (нов+к+1]:=пред; S\nped-\-k): =нов; 8[след+к+1 ]: =ное end При описании процедуры ВКЛСОТР будем пользоваться сле- дующими обозначениями: g — номер комнаты, в которую помещается новый сотрудник; высш — логическая переменная; истина, если сотрудник имеет высшее образование; муж — логическая переменная; истина, если сотрудник муж- ского пола; инф — массив, содержащий информацию о сотруднике; п — длина массива инф. Схема процедуры ВКЛСОТР. 1°. Занести длину п нового звена в первый элемент его справки. Это звено будет расположено на свободном месте массива S, начи- ная с элемента с индексом нов=5[2]. 2°. Найти индекс i заглавного звена для комнаты g путем после- довательного перебора списка комнат. 3°. Включить новое звено в список сотрудников этой комнаты, т. е. заполнить соответствующие ссылки в справке этого звена. (Включаемое звено попадет в конец списка, т. е. будет предшество- вать заглавному звену, индекс которого равен SU1). 4°. Включить то же звено в список имеющих высшее образова- ние, если это необходимо. (Звено включается перед заглавным зве- ном этого кольцевого списка, индекс которого есть 3.) 5°. Включить это звено в список мужчин или женщин (перед заглавным звеном с индексом 9 или 17). 6°. Переписать из инф в S информацию о сотруднике. 7°. Пересчитать указатель свободного места в элементе 5[2]. Процедура может выглядеть так: procedure ВКЛСОТР (инф, п) е список: (S) комната: (g) образование: (высш) пол: (муж); value п, g, высш, муж; integer array инф, S; integer п, g; boolean высш, муж; begin integer нов, i; rtoe:=S[2]; S [мов]:=п-|-8; i:=0; Af:t:=S(i-|-l]; if S[i+8]#=g then go to M; ВКЛ(нов) перед: (i) место ссылок: (2); if высш then ВКЛ (нов, 3, 4); if муж then ВКЛ (нов, 9, 6) else ВКЛ (нов, 17, 6);
ТАБЛИЦЫ 17» В.Б1 for f:=l step 1 until n do S[ ное 4-7 S[2]:=S[2]4-n+8 end ВКЛСОТР Замечание. Напомним, что оператор процедуры ВКЛ (ное) перед' (0 место ссылок'. (2) означает то же самое, что и оператор ВКЛ (ное, i, 2). Опишем теперь процедуру ИСКЛСОТР исключения сотрудника из списков. В ней описывается и используется вспомогательная процедура ИСКЛ исключения записи о сотруднике из одного ас- социативного списка. Как и в процедуре ВКЛ, параметром этой вспомогательной процедуры является указатель k места ссылок, относящихся к данному ассоциативному списку. В процедуре ИСКЛСОТР одним из параметров является ин- декс i звена, соответствующего исключаемому сотруднику. Последо- вательным применением вспомогательной процедуры ИСКЛ со- трудник исключается сначала из списка сотрудников в комнате, по- том из списка обладающих высшим образованием (если он там был), а затем из списка мужчин или женщин данного подразделения, procedure ИСКЛСОТР (0 образование: (высш) из списка: (S); value i, высш; integer t; boolean высш; integer array <S; begin procedure ИСКЛ(1г); value k; integer k; begin integer пред, след; пред:CAed:=S(i+k}; S[nped+k}:—cAed; S[cAed+k+l]:=nped end ИСКЛ; ИСКЛ(2); if высш then ИСКЛ (4); ИСКЛ(6) end ИСКЛСОТР Заметим, что нам не понадобилось вводить для вспомогательной процедуры ИСКЛ параметр, указывающий исключаемое звено, так как это звено указывается параметром i объемлющей процедуры ИСКЛСОТР. Когда мы обращались к вспомогательной процедуре ВКЛ, то указывали параметром след следующее звено списка, по- тому что для каждого списка оно было своим и поэтому изменялось _ от обращения к обращению. 3.5. Таблицы Широко распространенным видом деятельности, особенно при- годным для передачи его вычислительным машинам, является справочно-информационное обслуживание, которое включает в се- бя накапливание сведений, прием новых сведений и выдачу сведе- ний по запросам. Типичная задача справочно-информационного обслуживания со- стоит в том, чтобы организовать совместное хранение различных записей и выдавать по требованию любую запись вне зависимости
174 СТРУКТУРЫ ДАННЫХ [Гл. 3 от того, какие записи и в каком порядке выдавались ранее. Стан- дартной операцией является требование записи, и было бы обреме- нительно знать и при каждой такой операции указывать место хра- нения нужной записи. Избежать таких неудобств позволяет струк- тура данных, которая называется таблицей и в которой каждой за- писи соответствует определенное имя. Требуя запись, мы указы- ваем ее имя, а сама структура данных должна обеспечить достаточ- но быстрый поиск записи с искомым именем. Итак, таблица представляет собой набор именованных объектов (записей) произвольной природы. С каждым объектом однозначно связано его имя, которое и используется при работе с таблицей. «Именами» могут быть любые коды, но, чтобы в таблице можно было организовать эффективный поиск, нужна возможность сравнивать любые два имени и устанавливать, какое из них «меньше», а какое «больше». Существенно также, чтобы имена всех записей были раз- ными. Мы будем использовать в качестве имен числа. Имя записи часто называют также ключом этой записи. Каждая запись содер- жит свой ключ и некоторую информацию, связанную с этим клю- чом (текст записи). Над таблицами выполняются следующие операции. 1) Найти в таблице запись с заданным ключом. 2) Включить в таблицу запись с заданным ключом. Иногда вы- деляют отдельную операцию замены записи с заданным ключом, т. е. изменения содержимого записи, уже имеющейся в таблице. 3) Исключить из таблицы запись с заданным ключом. Существует много способов организации таблиц. Выбор способа должен определяться характером использования таблицы. Рассмот- рим некоторые из таких способов и оценим их в отношении трудо- емкости выполнения операций «найти», «включить» и «исключить». Обозначим через N число записей в таблице, а через М — размер алгольного массива, необходимый для представления таблицы. 3.5.1. Простая цепочка. Таблица может быть организована как однонаправленный спи- сок, который представляется в виде цепочки, где каждое звено со- держит следующие данные: Число элеменюв в звене 1 Ссылка на следующее звено | р Информационная часть (текст) записи } тело (запись) Будем оценивать трудоемкость операции над таблицей коли* чеством записей, просматриваемых при выполнении этой операции.
i.sl ТАБЛИЦЫ 175 Если искомая запись имеется в таблице, то операция «найти» требует в среднем просмотра половины списка, т. е. 0.5N записей. Если записи нет в таблице, то это обнаруживается лишь после пере- бора всех W записей списка. Если р — вероятность того, что запись есть в таблице, то среднее количество просматриваемых записей вы- ражается следующей формулой: 0.5JV.p-HV.(l—р) Оценим трудоемкость включения записи в таблицу-список. Ес- ли заранее известно, что записи нет в списке, т. е. р=0, то необхо- димо выполнить только операцию добавления записи в начало или конец списка, т. е. просмотреть одну запись. Если неизвестно, есть ли запись в списке, то сначала надо попробовать ее найти; и общее число просматриваемых записей, как мы выяснили, составляет: 0.5W-p-HV-(l—р)+1 Исключение записи занимает в среднем примерно столько же вре- мени, сколько и включение новой. Таким образом, при данной организации таблицы выполнение табличных операций трудоемко. Легко вносить только те записи, о которых известно, что их в таблице раньше не было. Размер массива можно оценить как M—N -(k+З), где k — сред- няя длина записи. 3.5.2. Цепочка с упорядоченными записями. Выше мы рассматривали таблицу-список, в которой записи сле- довали друг за другом в произвольном порядке, и поэтому, чтобы убедиться в отсутствии нужной записи, приходилось перебирать все имеющиеся У записей. Можно ускорить эту процедуру, если под- держивать в списке порядок следования записей по возрастанию' ключей, занося каждую новую запись в соответствии с этим поряд- ком. Например, если в такую таблицу заносились записи с ключами 17, 5, 9, 24, 2, то из них образуется список в виде цепочки, в которой первое звено будет содержать запись с ключом 2, второе — с клю- чом 5, третье — с ключом 9, четвертое — с ключом 17 и пятое — с ключом 24. Поиск записи с заданным ключом в такой таблице требует в среднем N/2 операций просмотра записей, независимо от того, имеется эта запись в таблице или нет. Просматривать список до конца в том случае, когда записи нет в таблице, не требуется. До- статочно последовательно дойти по списку до первой записи, ключ которой больше искомого, и тем самым убедиться, что искомой за- писи в таблице нет. При любой вероятности р<1 того, что запись есть в таблице, средняя трудоемкость поиска сокращается по срав- нению с обычным однонаправленным списком, поскольку, как не- трудно видеть, при O^pd 0.5N<0.5N-p+N-(\—p)
170 СТРУКТУРЫ ДАННЫХ [Гя.» Внесение записи требует в среднем ЛГ/2 -Н операций просмотра. Оно также сводится к перебору записей, пока не будут найдены за* писи с ключами Ki-t, Ki, для которых заданный ключ К удовлет- воряет условию Ki-iCKCKt Между ними и надо включить новую запись. Исключение записи аналогично требует в среднем ЛГ/2+1 опе- раций просмотра. Как видим, при втором способе организации таблицы табличные операции менее трудоемки, чем при первом. Но при внесении за- ведомо новой записи первый способ дешевле. Размер массива можно оценить как M=N-(k+3), где k — сред- няя длина текста записи. 3.5.3. Хранение ключей в отдельном массиве. Как мы видели на примере предыдущей структуры, для ускоре- ния поиска желательно установить определенный порядок следо- вания ключей. Однако упорядочение записей в рамках цепочки дало сокращение трудоемкости поиска не более, чём вдвое. Более быст- рые способы поиска удобнее реализуются, если хранить раздельно ключи и тексты записей. При этом для отображения таблицы исполь- зуются два одномерных массива, назовем их S и Т. Массив S пред- назначается для хранения ключей (точнее, ключевых записей), а тексты соответствующих записей (точнее, текстовые записи) поме- щаются в Массиве Т. Текстовая запись состоит из длины текста и самого текста. Клю- чевая запись состоит из двух элементов: самого ключа и относящей- ся к массиву Т ссылки на начало места хранения соответствующей текстовой записи. Доступ к текстовым записям разрешается только через соответствующие ключевые записи. При этом все операции над таблицами сводятся к работе с ключевыми записями, для кото- рых можно использовать разные способы представления. Удобство такой организации в том, что ключевые записи, во-первых, намного меньше текстовых и, во-вторых, имеют одинаковую длину 2. В ча- стности, это позволяет использовать для них векторное представ- ление, при котором соседние записи помещаются в соседние элемен- ты массива, т. е. порядок следования записей определяется порядком индексов содержащих эти записи элементов массива S. Нам будет удобно считать, что массивы S и Т начинаются с эле- ментов S[l) и Т(1], в которых указывается начало свободного места в этих массивах. Например, таблица из трех записей с ключами KI, К2 и КЗ мо- жет быть размещена в массивах S и Т следующим образом: S11) = 8 — индекс первого свободного элемента массива S. <S[2]=K1 — ключ первой записи. S[3] » 2 — индекс начала'первой текстовой записи в массиве Т.
•.в) ТАБЛИЦЫ 177 £[41=Д2 — ключ второй записи. £[51=38 — индекс начала второй текстовой записи в массиве Т. £[6]=КЗ — ключ третьей записи. £[71=66 — индекс начала третьей текстовой записи в массиве Т, £[8] — начало свободной части массива £. Т{ 11=91 — индекс начала свободной части массива Т. Т[2]=35 —длина текста первой записи. Т[3] ) ... > — текст первой записи. 7137] ) Т\38>\—21 — длина текста второй записи. 7139] ... • — текст второй записи. 7’165] 7166]=24 — длина текста третьей записи. 7167] 1 ... > — текст третьей записи. 7190) ) 7191 ] — начало свободной части массива Т. Таким образом, в массиве £ последовательно располагаются ключевые записи, каждая из которых состоит из двух элементов: ключа и указателя соответствующей текстовой записи в массиве Т. Записи, находящиеся в массиве Т, могут быть разных длин, и поря- док их хранения может не совпадать с порядком следования клю- чей в массиве £. В дальнейшем все рассмотрения будут основываться на таком отдельном хранении ключей и фактически будут касаться органи- зации и обработки таблиц ключевых записей. При этом, оценивая размеры массива, необходимые для хранения, мы будем иметь в виду только массив £, так как размер массива Т не зависит от метода ор- ганизации таблицы. 3.5.4. Дихотомический поиск по таблице в векторном представ- лении. Неудобство рассмотренных в разд. 3.5.1 и 3.5.2 способов орга- низации поиска с использованием сцепления состоит в том, что при- ходится перебирать записи подряд. При отдельном хранении Клю- чевых записей в векторном представлении- можно разместить их в порядке возрастания ключей. Если i — порядковый номер ключа в таком упорядоченном списке, то индекс этого ключа есть 2Xt (т. е. /Ct=£[2xtl) н, кроме того, выполняется условие £>/’— K.J. Полученная нами возможность непосредственного доступа к лю- бому ключу по его порядковому номеру есть главное достоинство векторного представления структуры данных. Она вместе с'указан- ным условием позволяет применить весьма эффективный способ
178 СТРУКТУРЫ ДАННЫХ (Гл. S поиска, который называется дихотомическим поиском (или просто дихотомией). Дихотомия проводится следующим образом. Пусть нам нужно найти ключ К в таблице из N ключей Kt, Kt, . • Сравниваем К с Кn/s (если N/2 не целое, то оно округляется в любую сторону, например, в большую). Если К=Кыц, то искомый ключ найден. Если то ключ К следует искать только во второй половине таблицы, т. е. среди ключей Kjv/s+i,. . .,Кк> а если К.<Кщъ, то его следует искать среди ключей Къ .... Кыц—». В выбранной половине снова выбираем средний ключ и сравниваем его с К и т. д. Таким образом, каждое сравнение уменьшает зону поиска вдвое. Из этого следует, что не более чем через 14-logs N сравнений в зоне поиска останется всего один ключ (если мы не встретим ключ К еще раньше). И либо этот ключ и есть К, либо ключа К вообще нет в таблице. В связи с такой оценкой трудоемкости дихотомию иногда называют логарифмическим поиском. Пусть,- например, таблица состоит из 17 записей с ключами 5, 7, 11, 18,26, 32, 44, 57,81, 90,94, 97,107,116, 129, 147,179 и требуется найти запись с ключом 129. В данном случае #=17 и Л’/2г«9. Мы нач- нем со сравнения К=129 и К,=81. Так как /£>#,, то зона поиска ограничивается участком таблицы от девятой до 17-й записи. Этот участок состоит из восьми записей, и в его середине находится три- надцатая запись. Сравниваем /(is=107 и #=129. #>#13, и зона поиска ограничивается участком от 14-й до 17-й записи. Берем из его середины ключ пятнадцатой записи #15=129, и, так как этот ключ совпадает с искомым, то нужная запись найдена. Опишем процедуру ДИХОТОМИЯ поиска записи о ключом К. по таблице, ключевые записи которой расположены в массиве S, начиная с элемента SI2] и кончая элементом S(S[1 ]—1 ]. Если в ре- зультате работы процедуры будет найдена запись с искомым ключом К, то на место формального параметра i занесется содержащийся в ключевой записи начальный индекс места хранения соответствую- щей текстовой записи в массиве Т. Если же такой записи не ока- жется, то идентификатору i будет присвоено значение 0. При ра- боте процедуры используются вспомогательные переменные млад и стар, которые представляют собой границы зоны поиска в таб- лице S. Их значениями являются соответственно наименьший и наибольший среди индексов ключей в массиве S, принадлежащих участку таблицы, в котором продолжается поиск. В начале выпол- нения процедуры производятся присваивания млад: =2 и стар:= S[1 ] —2, т. е. зоной поиска становится вся таблица; устанавливает- ся начальное значение i‘=0. Присваиванием /:=2х(ллад+елщр)-г- 4 мы вычисляем индекс / в массиве S ключа, находящегося в сере- дине очередной зоны поиска. Этот ключ S[/] сравнивается с иско- мым ключом К. При совпадении значений этих ключей произво- дится присваивание <:=/ и работа процедуры завершается. Если
3.5] ТАБЛИЦЫ 179 ключи не равны, то нужно сократить зону поиска, отбросив ее пер- вую или вторую половийу. Если ключ К больше, чем ключ SI/J, то отбрасываем первую половину зоны поиска присваиванием лма<Э:=/+2, иначе отбрасываем вторую половину зоны присваива- нием cmap:=j—2. Затем выполняем те же действия над новой, со- кращенной зоной поиска, возвращаясь к началу цикла. Цикл вы- полняется, пока не найдена запись с искомым ключом К (т. е. пока 1=0) и пока не исчерпана зона поиска (пока млад^стар). Зона поиска исчерпывается, если предыдущая зона поиска состоит из одной ключевой записи, что определяется отношением ]=млад— стар. Тогда при S[/]=/=/<, мы исчерпали всю таблицу, не найдя в ней нужного ключа X, а это означает, что такого ключа в таблице нет. При этом условный оператор if K>S[/1 then млад:=]+2 else стар:—]—2 в теле цикла либо увеличивает значение млад, либо уменьшает значение стар, и в обоих случаях нарушается ус- ловие млад^стар. procedure ДИХОТОМИЯ (S) ключ: (К) ответ: {Г); comment при отсутствии ключа i=0, value X; integer array S; integer X, i; begin integer млад, стар, j; г.—0;млад:=2; cmap:=S{\}—2; for /:=2 X{{младA-cmap)-^^) while млад^стар/\i=Q do if S[/]=K then i:=j else if /£>S(/] then млад:=]4-2 else cmap:=j—2 end Теперь напишем процедуру выборки записи из массивов S и Т по заданному ключу К с использованием дихотомии. Искомая запись будет выбираться в массив, которому соответствует формаль- ный параметр зап, причем в начальный элемент зап[01 заносится длина записи. Если записи с ключом X не найдется, то занесется значение зап [0]=0. В теле этой процедуры ВЫБРАТЬ ИЗ ТАБ- ЛИЦЫ выполняется оператор процедуры ДИХОТОМИЯ (S, К, I), который заносит в переменную i индекс ключа искомой записи в мас- сиве S (или 0, если такой записи нет). Затем, если i=/=0, то элементы записи циклом-последовательно, переносятся из массива Т в массив зап. Указателем начала текстовой записи в массиве Т служит пе- ременная нач, которой присваивается значение SU4-1L Длина за- писи получается присваиванием п:—Т1нач]. Цикл выполняется (n-Ы) раз, занося в зап [0] длину записи, а в последующие элементы массива зап сам текст записи, найденной по ключу X. procedure ВЫБРАТЬ ИЗ ТАБЛИЦЫ (S, Т) запись: {зап) по ключу: {X) value X; integer array S, Т, зап; Integer К; begin integer i, нач, n; ДИХОТОМИЯ (S, X, i); . if i=0 then san [01: “0 else
180 СТРУКТУРЫ ДАННЫХ [Гл. 8 begin «O4:=S(t+l]; п:=71яоч]; for г.=0 step 1 until n do sah[i]'.— T[Ha«4-i] end end Дихотомический поиск особенно удобен в том случае, если таблица сначала составляется, а затем многократно используется. Если же приходится часто производить не только поиск, но и занесение но- вых записей, то эффективность работы снижается, так как для зане- сения новой ключевой записи в монотонно возрастающую по ключам последовательность в массиве S потребуется произвести раздвижку этой последовательности. При такой раздвижке потребуется ~N операций перемещения помимо тех ~log2 N операций, которые бу- дут затрачены на дихотомический поиск места в массиве S, куда нужно вставить новую ключевую запись. Преимущество раздельного хранения информации в массивах S и Т состоит в том, что при разд- движке ключевых записей в массиве S нет надобности перемещать тексты записей в массиве Т, поскольку порядок следования этих текстов записей не имеет значения. 3.5.5. Двоичное дерево. В общем случае, когда поиск и включение записей могут произ- водиться одинаково часто, удобно использовать таблицу в виде структуры двоичного дерева, организованного при помощи сцепле- ния. При этом трудоемкость обращения к таблице как для поиска, так и для включения или ис- ключения записи обычно со- поставима с трудоемкостью дихотомического поиска. Двоичное дерево можно изобразить следующей схе- мой. Имеется набор вершин, со- единяемых стрелками. Из каж- дой вершины выходит не бо- лее двух стрелок (ветвей), направленных вниз влево или вниз вправо. Верхняя вер- Рис. 3.13. Пример двоичного дерева. шина, в которую не входит никакая стрелка, называется корнем. В остальные вершины входит по одной стрелке. Каждая вершина дерева представляет собой звено, относящееся к записи о определенным ключом. Такое звено содержит ключ соответствую- щей записи, ссылку на место хранения текста записи в массиве И две ссылки, задающие сцепление по левой и правой ветвям, выхо^ дящии из этой вершины (если таковые имеются). Вершины (зве-
3.5] ТАБЛИЦЫ 181 нья) связываются в дереве так, что для каждой вершины с клю- чом К с левой ее ветви могут «свисать» только звенья, у которых ключи меньше К, а с правой — только звенья с ключами больше К. Например, см. рис. 3.13. (В каждом квадрате, изображающем вер- шину дерева, указан ключ соответствующей записи.) Поиск записи с заданным ключом К начинаем с корня дерева. Если К не совпадает с ключом Kt рассматриваемой записи, то в слу- чае K<Kt идем по левой ветви, в случае K>Kt — по правой и так далее. Так, для дерева, изображенного на нашем рисунке, если нуж- но найти запись с ключом К=47, мы сравниваем значение К с клю- чом Kj=50 из корневого звена. Поскольку /«50, то мы идем по левой ветви и сравниваем К с ключом К\=45. Теперь /<>/<», мы идем по правой ветви и приходим к записи с искомым ключом 47. Если на каком-то этапе поиска оказалось, что ветвь, по которой нам следовало бы двигаться, отсутствует, то искомой записи нет в таб- лице. Звено с ключом /Q считается конечным звеном поиска, если из него не выходит нужной для дальнейшего поиска ветви (левой при К<К} или правой при K>Kj). Если требуется включить в таблицу-дерево новую запись с клю- чом К, то выполняется аналогичный поиск звена с этим ключом. Ес- ли такая запись найдется, то вместо нее заносится новая запись.Если же мы дойдем до конечного звена поиска с некоторым ключом Kj, не встретив записи с ключом К, то присоединяем к этому звену ветвь с новой записью, причем новая ветвь направлена влево или вправо ' в зависимости от выполнения условия K<Kj или Для хра- нения таблицы, организованной посредством дерева, мы будем ис- пользовать два массива S и Т. Как и в предыдущих разделах, мас- сив Т используется для хранения текстов записей. Дерево ключевых записей будет располагаться в массиве 5. Каждое звено этого дерева состоит из четырех элементов: <S[i] — ссылка на левый преемник (индекс звена по левой ветви). S[i'4-1]— ссылка на правый преемник (индекс звена по пра- вой ветви). SU+21 — ключ записи. S[i+3] — ссылка на текст записи в массиве Т. Если из данной вершины не выходит левая или правая ветвь, то соответствующий указатель SU] или SU+1] равен нулю. Два на- чальных элемента массива S отводятся для заглавного звена: S10] — ссылка на звено, являющееся корнем дерева. Sill — ссылка на начало свободного места в массиве S. 3.5.5.1. Поиск и включение записей в дереве. Рассмотрим процеду- ры поиска по ключу звена в двоичном дереве и включения записи в таблицу-дерево. Начнем с описания процедуры ПОИСК. Эта процедура по за- данному ключу К находит индекс i в массиве S звена с данным клю-
СТРУКТУРЫ ДАННЫХ (Гл. 3 182 чом, а также находит индекс пред предшествующего ей звена (со- держащего ссылку на искомое звено). Если искомого звена не ока- жется в дереве, то идентификатору i присваивается значение 0. Если искомой записью оказывается корень дерева, то выдаются значения i=S(01 и пред=0. Работа процедуры начинается с того, что устанавливаются зна- чения пред=0 и i==S[0], соответствующие корню дерева. Затем вы- полняется цикл поиска по дереву нужного звена. Для каждого очередного звена с индексом i ключ этого звена S[i+21 сравнивает- ся с искомым ключом К. В случае их равенства рассматриваемая запись оказывается искомой. Если же эти ключи не равны, то при- сваиванием пред:=1 мы делаем рассматриваемое звено кандидатом в предшественники искомого звена и ищем среди его преемников зве- но с требуемым ключом К. Если /(<S[t-|-21, то ключ К может ока- заться у звена с левой ветви дерева. Это звено указывается ссылкой из S[z], и мы устанавливаем значение i=S[i). Иначе проверять нуж- но правую ветвь, и тогда мы присваиваем t:=S[i4-l]. Если получи- лось новое значение 1—0, то у предыдущей записи не оказалось соответствующего преемника. Это означает, что искомой записи нет в таблице. Цикл выполняется, пока не будет найдено звено с клю- чом <S(i+2]=/( или пока мы не достигнем конечного звена поиска, т. е. пока не получим очередное значение i=0. procedure ПОИСК (К) в дереве-. (S) индекс: (i) предыдущий: (пред)-, comment если записи с нужным ключом нет, то (=0; value К; integer К, I, пред: integer array S; begin пред: =0; i:=S[0]; for /<:=/( while S[i+2]=#KA >V=0 do begin nped:=i‘, i:=if /«Sli+21 then S[il else S [i-Hl end end Здесь параметр цикла фактически не используется и вводится толь- ко для соблюдения синтаксической правильности оператора цикла. Заметим, что дерево может оказаться пустым и тогда SIO]=O. В этом случае работа процедуры заканчивается сразу после началь- ного присваивания i:=S[0], так как условие цикла while, включаю- щее требование i=/=0, оказывается невыполненным. Если даже в таб- лице не оказалось записи с ключом К, все равно после окончания работы процедуры ПОИСК параметр пред будет содержать индекс звена, которое должно предшествовать звену с ключом К, если оно появится в дереве. Этот факт используется в следующей процедуре В ДЕРЕВО. Опишем теперь процедуру включения записи в двоичное дерево. Пусть информационная часть включаемой записи содержится в мас- сиве инф и занимает п элементов инф [11, ...инф [nJ. Элемент инф
3.51 ТАБЛИЦЫ 183 [0] содержит значение п. Параметрами процедуры являются ключ К новой записи, идентификатор инф, идентификатор S массива, в ко* тором хранятся ключевые записи дерева, и идентификатор Т мас- сива, отведенного для текстовых записей. Текст новой записи зано- сится в массив Т на свободное место, указываемое значением новт=* ЛИ. К указателю ЛИ начала свободного места в массиве Т при- бавляется длина инф [0] +1 новой текстовой записи. Далее в цикле элементы массива инф переносятся на место в массиве Т, отведенное для записи,, начиная с Т [ноет]. Затем мы обращаемся к процедуре ПОИСК оператором ПОИСК (К, S, i, пред). Если в результате вы- полнения этого оператора получится значение i=#0, то в нашем дереве уже содержалось звено с ключом К, и тогда в элемент этого звена S[i+3] мы заносим указатель ноет новой текстовой записи в массиве Т. В противном случае» нужно сформировать новое звено с ключом К. Это звено занесется в массив S на свободное место, указываемое значением ковз=5[П. К указателю S[ll начала свободного ме- ста прибавляется длина 4 формируемого звена. У нового звена не будет преемников, и мы заносим на места его ссылок нули. В эле- мент 5[яовз+21 заносится ключ К, а в элемент 5[новз+31 заносится ссылка ноет на новую текстовую запись ,в массиве Т. В резуль- тате работы процедуры ПОИСК мы получаем в переменной пред индекс звена, которое предшествует (или должно предшествовать) в дереве звену с ключом К- Если nped=Q, то новое звено будет кор- нем дерева, и мы заносим в указатель корня SI0I значение индекса этого звена новз. Иначе нужно выяснить, по левой или правой вет- ви звено с ключом К является преемником звена с индексом пред, ключ которого находится в элементе Slnped+2]. Для этого мы срав- ниваем значения К и S[nped-\-2] и в зависимости от результата сравнения заносим значение указателя новз на место левой ссылки SInped] или правой ссылки Slnped+U. procedure В ДЕРЕВО (S, Т) ключ: (К) информация: (инф); value К', integer array S, Т, инф; integer К: begin integer i, пред, ноет, новз, /; новт:=Т[\\; Т|1|: = Л11+инф [01+1; for /:=0 step I until инф [0] do Т \новт+(\:=инф [/]; comment перенос информации в массив Т; ПОИСК (К, S, i, пред); if i=+0 then S [i+31:=woem else begin «овз:=5[11; S[11:=S[11+4; £[новз]:=£[«овз+1]:=0; 5[яовз+21:=К; 8\новз-\-3\:=новт; comment заполнение нового звена в дереве; if nped=0 then 5[0]:=яовз else if K<LS[/iped+2) then 8[пред]:=новз else £[лрл?+1]:=мовз end end
484 СТРУКТУРЫ ДАННЫХ 1Гл. 8 50 60 70 Средняя длина пути при поиске по двоичному дереву имеет порядок log,А/, где N — число вершин. Однако возможны и «плохие» де- ревья, в которых из большинства вершин выходит по одной ветви. В частности» может встретиться дерево, вы- родившееся в однонаправленный список. Пример такого вырожденного дерева изоб- ражен на рис. 3.14. Среднее число просмат- риваемых звеньев для вырожденного дере- ва составляет АГ/2. Такое дерево может возникнуть, например, если записи заноси- лись в него строго в порядке возрастания ключей. Но при случайном чередовании ключей заносимых в таблицу записей веро- ятность подобных вырожденных деревьев очень мала. • Необходимость хранения ссылок — це- на, которую мы платим за возможность организации быстрого включения и исклю- чения записей. Если информационная часть записей имеет доста- точнобольшой объем, то цена приемлема. Итак, трудоемкость поиска или включения записи при органи- зации таблицы в виде двоичного дерева в среднем пропорциональна log* N. Требуемый размер массива S ключевых записей M—4N. Рис. 3.14. Пример вырож- денного дерева. Рис. 3.15. Рис. 3.16. Если в процессе включения в дерево новых записей образуется «плохое» дерево с неравномерными разветвлениями, а следовательно, со слишком длинными путями от корня к конечным вершинам, то его можно корректировать. Пусть, например, последовательно по- ступали записи с ключами К\, К2, КЗ, где Kl<K2<ZK3. Если ис- ходное дерево было пустым, то после трехкратного применения про- цедуры В ДЕРЕВО получается «плохое» дерево (см. рис. 3.15). Од- нако это дерево можно перестроить, поменяв ссылки в записях та- ким образом, чтобы корнем стала запись с ключом К2. В результате мы получим «хорошее» дерево, разветвляющееся в обе стороны (см. рис. 3.16). Существуют различные и не очень сложные алгоритмы, включаю- щие подобную корректировку и обеспечивающие построение сба-
3.51 ТАБЛИЦЫ 185 лансированных деревьев, примем трудоемкость включения записи' по-прежнему пропорциональна log4 N. 3.5.5.2. Исключение записи из дерева. Мы не будем подробно опи- сывать процедуру исключения записи, а только наметим этапы ал- горитма: 1) поиск соответствующего звена, 2) его исключение. На этапе исключения возможны три варианта, в зависимости от того, сколько ветвей выходит из исключаемого звена (ни одной, одна или две). Вариант!. Из исключаемого звена не выходит ни одной вет- ви. Тогда исключение сводится к уничтожению ссылки на это звено. Вариант 2. Из исключаемого звена выходит одна ветвь. Тогда исправление ссылок, связанное с исключением, можно изобразить схемой, показанной на рис. 3.17 (исключаемое звено заштриховано). Заметим, что ветвь от исключаемой вершины может быть направ- лена и не в ту сторону, что ветвь, входящая в исключаемую верши- ну сверху, например, как показано на рис. 3.18. Однако и в этом случае производится «спрямление» ветви от предшественника к преемнику исключаемой вершины, причем новая ветвь идет в ту же сторону, куда шла ветвь к исключенной вершине. Вариант 3. Из исключаемого звена выходят две ветви. Тог- да нужно найти звено с таким ключом, чтобы было удобно подста- вить его на место исключаемого, и заменить в структуре дерева ис- ключаемую запись записью из этого найденного звена. Такое звено существует всегда. Чтобы найти его, нужно идти по дереву от ис- ключаемого звена один раз налево и потом все время .направо (можно и наоборот), и как только окажется, что идти неку- да, то это звено (из которого нет нужной ветви) годится для под- становки. Таким образом, когда требуется исключить звено, из которого выходят две ветви, мы находим и подставляем на его место звено, из которого выходит не более одной ветви. При этом нужно исклю-
186 СТРУКТУРЫ ДАННЫХ [Гл 3 нить это подставляемое звено из того места дерева, где оно находи- лось раньше. Однако такое исключение мы умеем делать, так как оно сводится к уже рассмотренному нами варианту 1 (если из под- ставляемого звена не выходило ни одной ветви) или варианту 2 (если выходила одна ветвь). Например, если требуется исключить запись с ключом 40 из дерева, изображенного на рис. 3.19, то вместо исключаемой записи в ту же вершину дерева можно подставить ключевую запись с клю- чом 35 или 50. При любой из этих замен получится правильное де- рево, в котором для любой вершины все ее прямые и косвенные пре- емники по левой ветви имеют меньшие ключи, а по правой ветви — большие ключи, чем ключ записи из этой вершины. Если подставить запись с ключом 35, то в результате преобразо- вания получится дерево, которое показано на рис. 3.20. Бывшие преемники исключенной записи (с ключами 30 и 50) становятся пре- емниками подставленной записи. В результате исключения из дере- ва, изображенного на рис. 3.19, записи с ключом 35 бывший ее пре- емник (с ключом 33) становится непосредственным преемником пред- шествующей записи с ключом 30. 3.5.6. Перемешанные таблицы. Описанная выше структура двоичного дерева позволяет сущест- венно сократить при поиске перебор записей, но определенный пе- ребор все же остается. Представляется весьма заманчивым без пе- ребора сразу вычислять по ключу /< указатель местоположения со- ответствующей записи. В массиве S с описанием integer array <$ [1 iAfl будем размещать ключевые записи, состоящие из двух эле- ментов: ключа и ссылки на текстовую запись. Массив S разби- вается на пары соседних элементов S[il, SU+l'], предназначаемые для ключевых записей. Задача состоит в том, чтобы по ключу К определить порядковый номер F(/Q соответствующей ключевой за-
3.5) ТАБЛИЦЫ 187 писи и тем самым найти индекс i-l+2-F(K) элемента, содержа- щего ключ К. Простейший способ организации быстрого поиска за счет из- быточной памяти состоит в том, чтобы использовать ключ К непо- средственно как порядковый номер ключевой записи. Тогда F(K)~ К, и каждая новая запись будет попадать в массиве S не вплотную за предыдущими, как это было в рассмотренных нами ранее струк- турах, а на сколь угодно дальнее место, в зависимости от того, насколько велико значение ключа этой записи. Таким образом, нам потребуется массив S, длина которого М пропорциональна макси- мальной величине ключа, а не числу фактически имеющихся запи- сей ЛГ. При таком использовании ключа каждая запись отыскивает- ся совсем быстро, поскольку, если ключ равен К, то индекс записи есть 1 +2 • К. Однако требуется слишком много места для хранения ключевых записей. Например, в таблице сотрудников предприятия обычно содержится порядка 10s записей. Ключами записей служат фамилии. А число возможных фамилий ~101“. Метод перемешанных таблиц предлагает компромиссное решение. Размер М массива ключевых записей выбирается несколько боль- шим, чем 2-N, и за счет этой избыточности, как правило, обеспечи- вается возможность одной или несколькими операциями находить запись с нужным ключом. Для удобства рассуждений обозначим через Р число мест для ключевых записей в массиве S (т. е. Р=М/2). Как мы уже говорили, хотя Р и больше чем А/, но различие между ними не слишком вели- ко: оно измеряется не порядками, а процентами. Идея перемешива- ния состоит в том, что вводится функция от ключа F(K), которая по возможности равномерно отображает множество ключей на отрезок натурального ряда 0,1,. . ., Р—1. Функция F(K) называется функ- цией расстановки. Ее значением F(K) определяется место в масси- ве S .начиная с которого мы пытаемся искат-ь запись с ключом К. Если оказалось, что место, указываемое значением F(/C), занято записью с другим ключом, то мы рассматриваем следующее место и так далее, пока не встретим запись с искомым ключом. Если при этом нам встретится место, свободное от записи, то это означает, что записи с ключом К в таблице нет и поиск прекращается; в случае включения новой записи с ключом К соответствующая ключевая запись заносится на это свободное место. Если ключами служат це- лые числа и все их возможные значения одинаково вероятны, то достаточно быстрый поиск в перемешанной таблице обеспечивается применением простой функции расстановки R(K)—K той Р, кото- рая вычисляется как остаток от деления ключа К на число Р мест в 1аблице: integer procedure /?(Л); value К; integer К; R:=K—(K+P)xP-,
188 СТРУКТУРЫ ДАННЫХ [Гл. 3 Ниже в процедурах на алголе мы будем пользоваться этой функцией расстановки. При занесении новой записи с ключом К в перемешанную таб- лицу мы сначала пытаемся поместить соответствующую ключевую запись в элементах S[i] и SIi+1], где i—1+2-/?(/<).Если S[i]=0, то место свободно и мы занимаем его под нашу ключевую запись. Если S[il=K, то на этом месте уже находится ключевая запись с тем же ключом К, и мы заменяем ее на новую, оставляя прежнее содержимое элемента S[i]=X и заменяя значение SU+1] ссылкой на новый текст этой записи в массиве Т. Однако может оказаться, что 5[Л¥=0 и т. е. место, указываемое функцией расстанов- ки, занято записью с другим ключом. Например, пусть ключи — целые числа и Р=10, т. е. массив S имеет граничную пару [1 : 20]. Если в первоначально пустую таб- лицу поступают записи с ключами ПО, 15, 27, 9, 137, 42, 48, то при нашей функции расстановки R(K)=К mod 10 мы имеем 7?(110)= О, и (по формуле »=1+2«/?(К)) ключ ПО попадает в 311]; /?(15)= 5, и ключ 15 попадает в Sill]; /?(27)=7, и ключ 27 попадает в 3[ 15]; 2?(9)=9, и ключ 9 попадает в 3(19]; 7?(137)=7, i=15, но соответст- вующее место 3[15] уже занято ключом 27. В таком случае мы про- изводим перевычисление i=(i+2) mod 2Р, получаем 1=17 и зано- сим ключ 137 в 3117]. Далее вычисляем /?(42)=2 и помещаем ключ 42 в S[51. Затем вычисляем /?(48)=8, получаем i=17, но элемент 3117] уже занят ключом 137. Поэтому вычисляем i=(17+2) mod 20= 19. Элемент 3[19] тоже занят (ключом 9); мы снова вычисляем i= (19+2) mod 20=1 и, поскольку 3[11=110, еще раз вычисляем i= (1+2) mod 20=3. Элемент 3[3] свободен, и в него мы помещаем наш ключ 48. Поиск записи по перемешанной таблице производится аналогич- но, вычисляется i=l+2-R(K); если S[t]=/(, то запись с ключом /С найдена; если S[i]=0, то такого ключа нет в таблице. Иначе вычис- ляем i=(i+2) mod 2-Р и повторяем такие же рассмотрения для но- вого значения t. Обозначим через /п(/0 число позиций в таблице 3, просматриваемых при поиске ключа К. Для нашего примера /п(110)=1, /и(15)=1, /п(27)=1, /п(9)=1, Ц137)=2, /п(42)=1, /п(48)= 4. Значение функций ta(K) является оценкой трудоемкости поиска. Назовем коэффициентом заполнения перемешанной таблицы от- ношение N/Р числа мест, занятых ключевыми записями, к общему числу мест в таблице. В рассмотренном примере коэффициент за- полнения таблицы равен 0.7, а средняя трудоемкость поиска со- ставляет 11/7«1.4. При помощи моделирования перемешанных таблиц и теоретиче- ски получены хорошие оценки трудоемкости поиска в перемешанных таблицах. Некоторые такие оценки приведены в табл. 3.4. Средняя трудоемкость поиска определяется не количеством N имеющихся за-
9.5] ТАБЛИЦЫ 189 писей, а коэффициентом заполнения таблицы; чем больше этот коэф- фициент, тем больше позиций приходится просматривать в процессе поиска. При коэффициенте заполнения 0,8 (т. е. когда'заполнено 80% таблицы) расход места в перемешанной таблице и в'двоичном дере- ве (за счет ссылок дерева) примерно одинаков. Однако при работе с перемешанной таблицей среднее число просмотров 3 не меняется о Таблица 3.4 Коэффициент заполнения таблицы Среднее число просматриваемых записей 0,2 1,125 0,4 1,335 0,6 1,75 0,8 3 «1 «20 увеличением N, если одновременно и пропорционально увеличи- вается Р, тогда как при использовании дерева среднее число про- смотров растет как log, N. Заметим, что ускорение поиска в переме- шанных таблицах достигается за счет знаний, использованных при выборе функции R(K), и сводится на нет, если этот выбор окажется неудачным. Так, в рассмотренном примере, если будут поступать ключи 10, 120, 3500, 200, 220, .... то функция /?(К)=К modlO будет все время нулем и поиск сведется к последовательному пере- бору позиций со средним значением трудоемкости поиска tn=N/2. Рассмотрим еще один пример. Пусть в таблице работников ка- кого-либо предприятия ключами записей являются фамилии ра- ботников, а функцией расстановки служит порядковый номер пары букв, с которых начинается фамилия. При этом считается, что все пары упорядочены в алфавитном порядке: АА, АБ............АЯ, БА, ББ......ЯЯ и буквы нумеруются от 0 до 31. Иными словами, R (фамилия)=32Х (номер первой буквы)+ (номер второй буквы). В нашем массиве S должно быть 32s=1024 места для записей, и стало быть таблица рассчитана на предприятия с несколько меньшим числом сотрудников (500—600 человек). Пусть мы последовательно вносим в таблицу (первоначально пустую) записи с ключами ИВАНОВ, ИВАНЧЕНКО, ИГНАТЬЕВ. Запись с ключом ИВАНОВ расположится на 258-м месте в таблице, так как R (Иванов)=32 X (номер И) + (номер В)=32 x 8+2=258. Запись с ключом ИВАНЧЕНКО имеет то же значение функции
ISO СТРУКТУРЫ ДАННЫХ [Гл. 3 расстановки. Однако, поскольку 258-е место занято, она будет помещена на следующее 259-е место, соответствующее фамилиям с начальными буквами ИГ. В связи с этим запись об Игнатьеве по- падет на 260-е место. Поиск Иванова в таблице начнется с 258-го места и займет 1 просмотр. Поиск Иванченко начнется с 258-го ме- ста и займет 2 просмотра. Поиск Игнатьева — с 259-го места и тоже займет 2 просмотра. Если производится поиск несуществующей за- писи об Игумнове, то он также начнется с 259-го места и оборвется, когда будет обнаружено, что 261-е место свободно. Недостаток такой функции расстановки состоит в том, что не все ее значения оказываются одинаково вероятными. Например, комбинация ЬЬ в начале фамилии не встретится никогда, а комби- нация ИВ будет встречаться особенно часто. Для ускорения поис- ка по перемешанной таблице лучше было бы сложить или перемно- жить коды всех букв фамилии и взять в качестве функции расста- новки остаток от деления полученного результата на число Р мест в таблице. Опишем процедуру поиска в таблице S ключа К. Результатом работы процедуры будет индекс i элемента S[i], содержащего иско- мый ключ. Если ключа К не окажется в таблице, то i будет содер- жать взятый со знаком минус индекс свободного места, на которое следовало бы записать этот ключ. Мы дадим процедуре имя ХЭШ- ПОИСК, потому что в литературе перемешанные таблицы назы- ваются также ХЭ/Д-таблицами. Внутри процедуры ХЭШПОИСК описывается процедура-функция ХЭШ, вычисляющая функцию расстановки procedure ХЭШПОИСК (К)e:(S) размер (М) ответ: (i); comment если ключ К найден, то i=индекс этого ключа в таблице, в противном случае 1= — индекс свободного места для его вклю- чения-, value К, М; integer К, М, i; integer array S; begin integer /; integer procedure ХЭШ (К, P): value К P: integer К, P\ ХЭШ:=К—(К^Р)хР‘, i:=0; lor г. = \+2хХЭШ (К. M/2), if j<M—2 (hen i+2 else 1 while i=0 do if £[/] = /< then i:~j else if SI/]=O then i:=—/ end Работа этой процедуры начинается с того, что устанавливается значение 1=0, затем в цикле перебираются индексы /, начиная со значения }=\+2У.ХЭШ (К, М/2), получаемого вычислением функ- ции расстановки ХЭШ. Каждое следующее значение / указывает следующую позицию в перемешанной таблице и вычисляется сог- ласно второму элементу списка цикла присваиванием j:=if j<.M—2 then i+2 else 1, что эквивалентно применению формулы /=j+2
^.б) ТАБЛИЦЫ 19Л (mod М). Выполнение цикла продолжается до тех пор, пока не бу- дет найден элемент SI/1, содержащий искомый ключ К (что повле- чет за собой присваивание /:»/), или пока не будет найден свобод- ный элемент S[/]=0 (что повлечет за собой присваивание it——j). Заметим, что, заменив описание процедуры функции ХЭШ, мы могли бы применять эту же процедуру ХЭШПОИСК и при других видах перемешивания таблиц (с другими функциями расстановки). Теперь, воспользовавшись процедурой ХЭШПОИСК, мы можем описать процедуру ХЭШЗАПИСЬ включения в перемешанную таб- лицу новой записи с ключом К, текст которой хранится в массиве инф, причем элемент инф [0] содержит длину остальной части за- писи. Текст новой записи заносится в свободную часть массива Т, начало которой указывается элементом Л1]. Работа процедуры начинается с вычисления «ач=ЛП и длины текста записи п=инф[0]. После этого вычисляется новое начало свободной части массива Т оператором ЛП:=Л1]+п-Ы. Затем в цикле элементы массива инф переносятся в отведенную для них часть массива Т. Далее выполняется оператор процедуры ХЭШ ПОИСК (К, S, М, I). Независимо от того, была ли запись с ключом К в таблице S, абсолютная величина abs(i) указывает нам индекс элемента в S, куда нужно поместить ключ новой записи. Поэтому мы выполняем присваивание Slate (t)]:=K, а в следующий элемент Slate(i)+1] заносим указатель нач текстовой записи в массиве Т. Эти два элемента S[ata(i)] и S[ate(i)+H образуют ключевую за- пись, относящуюся к ключу К- procedure ХЭШЗАПИСЬ (К, инф) в: (Т, S, М)- value К, М; integer К, М; integer array инф, Т, S; begin integer i, п, нач-, яач: = Л1]; п:=инф[0]-, Л1]:=Л11+п+1; for г:=0 step 1 until n do T {нач-\-1\-.=инф lil; ХЭШПОИСК (К, S, М, i); S[abs(i)]:—K', S[ate(t)+l]:=«a4 end Если в массиве Т уже содержался текст записи с ключом К, то эти элементы остаются неиспользуемыми, поскольку новый текст записи мы помещаем после всех остальных текстов записей. Исполь- зование освободившихся частей массива затруднено тем, что дли- ны прежних и новых текстов записей могут не совпадать. Однако при переполнении массива Т можно производить уплотнение содер- жащейся там информации с соответствующей корректировкой ссы- лок на массив Т из таблицы S. И наконец, опишем процедуру ХЭШВЫ БОРКА, которая копи- рует текст записи с ключом К из массива Т в массив инф, причем в элемент инф [01 заносится длина текста. Работа этой процедуры начинается с выполнения оператора ХЭШПОИСК (К, S, М, »).
192 СТРУКТУРЫ ДАННЫХ (Гл. S Если получится КО, то нужной записи нет в таблице, и тогда уста- навливается значение инф [01=0. Иначе производятся присваива- ния нач: — S li+П я п:=Т [нач1, т. е. в переменную нам, заносится индекс начала искомой текстовой записи в массиве 7, а в Перемен- ную п заносится длина текста, затем оператором цикла текст записи вместе со своей длиной переносится в массив инф. procedure ХЭШВЫБОРКА (К, инф) из. (Т, S, М); value К, М; Integer К, М\ integer array инф, Т, S; begin integer i, п, нам-, ХЭШПОИСК (К, S, М, 0; if i<0 then инф [0]:—0 else begin hoh:—S li+П; n:=7* [нач]; for i:=0 step 1 until n do инф [яач+i] end end 3.6. Отображение многомерных массивов на вектор Векторная организация одномерного массива удобна тем, что она соответствует структуре машинной памяти, состоящей из по- следовательности ячеек с подряд идущими номерами. Сточки зрения машинного представления, структура многомерных массивов менее естественна, и поэтому желательно иметь возможность отображать их на Вектор, т. е. вспомогательный одномерный массив. Предположим, что нужно отобразить трехмерный массив А с описанием array A ILS : R3, L2 : R2, LA : /?1 ] на вектор S, начиная е элемента S |01. Число элементов вектора должно совпадать с числом различных элементов трехмерного массива, которое равно произведению (/?3—£3+1)Х(/?2—£2+1)Х(/?1—£1+1). Поэтому описание вектора S имеет вид array S[0 : (/?3—£3+1)Х(/?2—L2 +1)Х(/?1—£1+1)—11. Например, при исходном описании трех- мерного массива arrav ЛЮ: 2, 1 : 3,—1 : 0] описание вектора S будет таким: array S [0: 17]. При отображении многомерного мас- сива на вектор принято располагать элементы последовательно, строка за строкой. В S [01 заносится элемент А [13, L2, £11 с ми- нимальными значениями всех трех индексов. Затем первый и второй индексы остаются неизменными, а третий индекс последовательно пробегает значения от £1 до R1, причем строка Au,tLt, А^^ц^ ♦ • •> А[л Lt nt заполняет элементы вектора S [0], S [11, . . . 5 1/?1—£1 ]. Вплотную за этой строкой в векторе S помещается следующая строка, в которой первый индекс сохраняет минимальное значение L3, второй индекс увеличен на 1 и равен £2+1, а третий индекс снова пробегает значения от £1 до /?1. Таким образом, эле- ментами AUt Лд, £i+t, м+f, • • •» Au, Lt+t, п заполня- ются в векторе S позиции с индексами /?1—£1+1, /?1—£1+2, . . ., /?1—£1+/?1—£1+1=2(/?1—£1+1)—1. Аналогично в вектор пе-
6.3) ОТОБРАЖЕНИЕ МНОГОМЕРНЫХ МАССИВОВ НА ВЕКТОР 193 реносится третья строка (со вторым индексом £2+2) и так далее до исчерпания возможных значений второго индекса при размеще- нии элемента A [L3, R2, /?1] из (R2—£2+1)-й строки в позицию S 1(7?2—L2+1) X (R1—£1 + 1)—11. Тем самым мы расположили в векторе одну матрицу и далее, начиная со следующей позиции в векторе S, аналогично размещаем следующую матрицу, которая получается из нашего трехмерного массива при неизменном значе- нии первого индекса £3+1. Так, одна за другой, размещаются все (R3—£3+1) матриц. Например, отображение массива с описанием array Л [0 : 2, 1 : 3, —1 : 0] показано в табл. 3.5. Таблица 3.5 Элемент массива А Соответству- ющий элемент массива S Элемент массива А Соответст- вующий элемент массива S Элемент массива А Соответст- вующий элемент массива S А [0, 1,-1] А (0, 1,0] А 0,2,-1] А 0,2,0] А 0.3,—1] А 0,3, 0| GO Со Со GO Go Go ~ О А [1, 1,-1] Л [1, 1,0] А 1,2,—1] А 1,2,0] А 1,3,—1] А 1,3,0] $ [6] $[7 $18] $ [9] $ 10] $[11] А [2, 1,-1] А 12, 1,0] А [2, 2,-1] А [2, 2, 0] А [2, 3,-1] А [2, 3,0] $ [12] $[13 $ 14] $ [15] $[16] $[17 Нетрудно убедиться в том, что при таком способе отображения элемент A [t'3,12, il 1 попадает в позицию S [(t'3—£3) X (R2—£2+1) X Х(/?1—£l-|-l)+(i2—£2)Х(7?1—£l + l)+il—£1]. Это выражение в квадратных скобках, определяющее индекс в векторе S, назы- вается приведенным индексом элемента A h’3, i2, ill. Коэффициент (13—L3) представляет собой число полных матриц, предшествую- щих данному элементу, и умножается на размер матрицы. Коэф- фициент (t’2—£2) представляет собой число предшествующих строк в последней (неполной) матрице и умножается на размер строки. Последнее слагаемое Л—£1 в приведенном индексе является числом предшествующих элементов в последней (неполной) строке. Для примера, представленного в табл. 3.5, приведенный индекс имеет вид f3x6+(i2—l)x2+il + l. Пользуясь приведенными индексами, можно заменять в вычис- лениях многомерные массивы на одномерные. Пусть, например, в программе фигурирует /г-мерный массив А с описанием array A [Lk : Rk, . . ., £2 : R2 , £1 : 7?1]. Организуем для него вспомо- гательный массив array Г [1 : 2х&], элементам которого будем присваивать при входе в блок значения выражений из граничных пар из описания массива А: 1 Э. 3. Любинский и др.
194 СТРУКТУРЫ ДАННЫХ [Гл. 3 Г[1]:=£1; Г [2]:=7?1; Г [3]:=L2; Г l2xk]:=Rk Для примера из табл. 3.5 эти присваивания выглядят так: Г [11: =0, Г [21: =2; Г [3]: = 1; Г [41: =3; Г [51:=—1; Г [61: =0 Индексы нужного элемента A Uk..../2, ill будем засылать в другой вспомогательный массив И: И [ll:=il; Я [21:=i2; . . . H[k]:=-ik. • Зная значения в массивах Г и И, а также размерность k, можно вы- числить приведенный индекс, воспользовавшись следующей про- цедурой-функцией: integer procedure ИНДЕКС (И) границы'. (Г) размерность-. (Л); value k-, integer k\ integer array И, Г-, begin integer P, Q, г, Р'.=И [11—Г [11; Q:=r [21—Г[11+1; for r: =2 step 1 until k do begin Р:=Р+(Я[г|—Г [2Xr— 1])XQ; Q:=QX(P [2Xrl—Г [2xr—11+1) end; ИНДЕКС'.=Р end ИНДЕКС Таким образом, например, если границы трехмерного массива А хранятся во вспомогательном массиве G, а для хранения индексов отведен вспомогательный массив /, то оператор х:=А [i*3, i‘2, ill можно заменить операторами / [ll:=il; / [21:=i2; I [3]:=i3; x:=S [ИНДЕКС (/, G, 3)1; Предполагается, что описание трехмерного массива А заменено на описание эквивалентного ему одномерного массива S. Выполнение процедуры ИНДЕКС можно ускорить, если вынести из нее и произвести заранее те вычисления над элементами гранич- ных пар, которые не зависят от индексов конкретного элемента A lik.....ill. Для этого мы преобразуем формулу приведенного индекса к виду ik-ck+. . .+i2-cs+il-сг—с0, где сх=1. Вместо массива Г можно вычислить при входе в блок массив коэффициен- тов array С [0 : Л], где: с[21=/?1—L1+1 с[31=(Я2—L2+l)-c[2] с[41=(ЯЗ—L3+I).c[31 с [б]=да_1)_Щ_1)+1).с [Л:—11 с [O1=L1+L2 c [2]+L3-c [31+. . .+Lk-c [Л1
3.6] - ОТОБРАЖЕНИЕ МНОГОМЕРНЫХ МАССИВОВ НА ВЕКТОР ]95 Для примера из таблицы 3.5 с [3]=6, с [2]=2, с [0]=1. Процедура-функция вычисления приведенного индекса с ис- пользованием массива С может выглядеть так: integer procedure INDEX (И) коэфф-. (С) размерность-. (А); value k\ integer k; integer array И, C\ begin integer P, г; Р:=И 11]—c [0]; for r:=2 step 1 until k do P-.=P+H Ихс И; INDEX-.=P end INDEX Пользуясь этой процедурой-функцией, можно вместо оператора обращения к трехмерному массиву х:=А [i3, i2, il] записать /П1:=П; / [2]:=i2; I [3]:=t3; x:=S [INDEX (I, C, 3)1
Глава 4 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ Основное назначение алгоритмов заключается в их фактическом выполнении тем или иным исполнителем с целью решения конкретных задач, возникающих в практической деятельности че- ловека. До сих пор мы исходили из того, что таким исполнителем явля- ется человек — так оно и было на самом деле в течение многих веков. Однако по мере развития человеческого общества, прогресса в науке и технике приходилось решать все большее число все более сложных задач со все возрастающими объемами вычислений. Для упрощения и ускорения выполнения этой трудоемкой и монотонной работы было изобретено большое количество различных средств и механиз- мов — таблицы, счеты, арифмометры, логарифмические линейки и т. п. Одним из наиболее эффективных и широко распространенных средств вычислений к 50-м годам нашего столетия были электромеха- нические настольные клавишные полуавтоматы, которые полностью автоматизировали процесс выполнения каждой из четырех основных арифметических операций. После того, как человек набирал на кла- виатуре аргументы для очередной операции и нажимал одну из управляющих клавиш, полуавтомат включался в работу и выпол- нял операцию, соответствующую нажатой управляющей клавише. По окончании выполнения операции полуавтомат останавливался и в его счетчике можно было прочесть последовательность цифр, представляющую полученный результат. Таким образом, здесь имела место вычислительная система типа «человек-машина», при функционировании которой действия обоих компонентов (человека и машины) все время чередуются: полуавто- мат не может начать свою работу, пока человек не наберет на кла- виатуре аргументы для очередной операции, а человек не может прочесть результат и начать набор аргументов для следующей опе- рации, пока полуавтомат не закончит выполнение очередной опе- рации. А поскольку человек — в силу его физиологических осо- бенностей — является заведомо медленнодействующим компонен- том, то общее быстродействие такой системы было весьма ограни- ченным и составляло около 500 арифметических операций за рабо- чий день, причем этот показатель невозможно существенно изме-
Гл.4] ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ 197 5 нить при любом повышении быстродействия второго компонента системы, т. е. машины. Между тем возникновение и развитие таких новейших областей науки и техники, как атомная энергетика, ракетная техника и др., привели к необходимости практического решения задач со столь большими объемами вычислений, которые просто невозможно было выполнить в сколько-нибудь приемлемые сроки с использованием имевшихся в то время вычислительных средств. В связи с этим со всей остротой встал вопрос о создании таких реальных вычисли- тельных машин — исполнителей алгоритмов — которые бы пол- ностью автоматизировали весь процесс вычислений по заданному алгоритму, исключив из него участие человека, что давало бы прин- ципиальную возможность резкого повышения скорости вычислений. Для этого надо было прежде всего выработать принципы по- строения таких машин. Известная к тому времени алгоритмическая схема —машина Тьюринга — не могла быть непосредственно ис- пользована для этих целей, хотя бы потому, что машину Тью- ринга — из-за бесконечности ее ленты — вообще невозможно ре- ализовать. Впрочем, схема Тьюринга и не преследовала цель ее реализации. Как уже говорилось, эта схема была предложена для целей теоретического характера, где требование бесконечности лен- ты являлось принципиальным моментом, обеспечивающим ее уни- версальность. К схеме, рассчитанной на реализацию в виде действующей ма- шины, должны быть предъявлены уже другие требования. В част- ности, здесь можно пожертвовать универсальностью схемы: с точки зрения практического использования машин вполне приемлемо, если с их помощью можно решать хотя бы ограниченный класс задач — конечно, при условии, что этот класс не будет слишком уз- ким. Основное требование к алгоритмической схеме, рассчитанной на реализацию, состоит в том, чтобы эта схема действительно могла быть реализована в виде конкретных действующих машин, построе- ние и эксплуатация которых не должны обходиться слишком дорого. Чтобы построение таких машин было оправдано, они должны обла- дать достаточно высоким быстродействием — с тем, чтобы решение практически важных задач с их помощью можно было производить за приемлемые промежутки времени. При этом, естественно, класс задач, которые можно решать на таких машинах, должен быть до- статочно широким, и подготовка задач к их решению на машине должна быть сравнительно простой. Первыми вычислительными машинами, реализующими заданный алгоритм, были табуляторы. Эти электромеханические машины были созданы главным образом для решения задач типа бухгалтер- ского учета: они могли выполнять операции сложения и вычитания (при желании — с нарастающим итогом, что позволяло, например^ суммировать задаваемую последовательность чисел) и печатать на
198 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ (Гл. « рулоне бумаги те или иные результаты вычислений. Исходные дан- ные для табулятора задавались на перфокартах. Перфокарта (от слова «перфорировать», т. е. пробивать отверстия) — это изготов- ленная из плотной бумаги карта стандартного размера и формата, на которой информация представляется в виде системы отверстий (пробивок), сделанных в определенных позициях. Для подготовки перфокарт служат специальные устройства, называемые перфорато- рами. При работе табулятора аргументы для каждой операции в основном прочитывались с перфокарт, хотя иногда одним из аргу- ментов мог являться и результат одной из предыдущих операций. Программа работы табулятора задавалась с помощью специальной его съемной части — коммутационной доски, на которой вручную устанавливались нужные соединения в схеме табулятора. Таким обра- зом, как и в машине Тьюринга, задание каждой новой программы фактически приводило к изменению самой машины. Частичное уп- равление работой табулятора производилось также с помощью пробивок в определенных строках перфокарт, выделенных для целей управления. Таким образом, возможности табуляторов были весьма ограни- ченными, и эти машины не могли удовлетворить растущие потреб- ности практики. 4.1. Общая характеристика ЭВМ В 1943 г. американский математик Джон фон-Нейман предло- жил новую алгоритмическую схему, известную под названием «маши- на фон-Неймана», которая как раз и была рассчитана на ее реали- зацию. Первая машина подобного рода была разработана и построе- на в США в связи с работами по созданию атомной бомбы, когда по ходу реализации этого проекта возникла необходимость прове- сти сложные расчеты. Первые машины строились в основном на базе электромагнит- ных реле, и поэтому их быстродействие было сравнительно невы- соким. Обеспечение быстродействия в тысячи и миллионы операций в одну секунду стало возможным при использовании для построе- ния машин практически безынерционных (и потому высокобыстро- действующих) электронных элементов на базе радиоламп, диодов, транзисторов, ферритов и т. д. В связи с этим машины подобного рода получили название электронные вычислительные машины (ЭВМ), хотя это название и не совсем точно отражает суть дела, ибо с ис- пользованием электронных элементов можно построить, например, и упоминавшийся выше настольный клавишный полуавтомат. Не- удачен и термин «вычислительные» машины: хотя первые ЭВМ дей- ствительно использовались главным образом для решения задач вычислительного характера, очень скоро выяснилось, что с их по- мощью можно решать очень широкий класс задач, в том числе и за-
4.1] , ОБЩАЯ ХАРАКТЕРИСТИКА ЭВМ < 199 ' дачи невычислительного характера (управление производством, экономическое планирование, перевод текстов с одного языка на другой и т. п.). Поэтому более точным был бы термин «автоматиче- ская система обработки данных» (АСОД). Однако в дальнейшем мы будем придерживаться установившегося термина — ЭВМ (за- метим, что вместо термина ЭВМ часто используется и термин «ком- пьютер» — от английского названия computer таких машин).,И хотя в последующие десятилетия эти машины претерпели существенные изменения, тем не менее и все современные ЭВМ по существу явля- ются машинами фон-Неймана. В дальнейшем мы будем говорить о типовой машине фон-Неймана, не отвлекаясь на те довольно мно- гочисленные отклонения от этой типовой машины, которые имеют место в реальных ЭВМ. 4.1.1. Принципы фон-Неймана. Основные отличия ЭВМ от машины Тьюринга заключаются в двух принципах, предложенных фон-Нейманом. Первый принцип — принцип произвольного доступа к ячейкам памяти. В отличие от машины Тьюринга, которой на каждом такте ее работы доступна либо та же самая ячейка памяти, которая была доступна на предыдущем такте, либо соседняя с нею, ЭВМ на каждом такте своей работы может с одинаковым успехом обращаться к л ку- бы м ячейкам памяти — как для чтения, так и для записи. Для обеспечения такого произвольного доступа к памяти каждой ячейке дается индивидуальное имя — эти имена и используются для ука- зания нужных ячеек. Как правило, все ячейки раз и навсегда пере- нумеровываются (обычно от 0 до N—1, где N— общее число ячеек памяти), и в качестве имени ячейки принимается ее порядковый номер, который называют адресом ячейки, а также и адресом ее со- держимого (по аналогии с обычным почтовым адресом). Этот прин- цип обеспечивает гораздо большую свободу и простоту использова- ния информации, хранящейся в памяти, по сравнению с машиной Тьюринга. Второй принцип — принцип хранимой программы. Если каждая машина Тьюринга способна выполнять только фиксированную программу, которая и определяет данную машину, являясь по сути дела ее составной частью, то ЭВМ выполняет программу, которая представлена в цифровой форме и хранится в той же памяти, что и данные, подлежащие переработке по этой программе. Перед началом решения задачи программа вводится (записывается) в отведенные для нее ячейки памяти и хранится там во время выполнения этой программы машиной. Принцип хранимой программы прежде всего делает ЭВМ универ- сальной в том смысле, что на одной и той же реальной машине можно решать самые разные задачи, поскольку для решения каждой из
200 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ (Гл. 4 них можно составить программу, ввести ее в память и заставить ма- шину выполнить эту программу. Факт представления программы в такой же форме, что и другие данные (например, числа) и хранения программы в памяти на время ее выполнения открывает целый ряд и других возможностей. Например, программа в процессе своего выполнения также может подвергаться переработке, что позволяет задавать в самой програм- ме правила получения некоторых ее частей и тем самым получать весьма компактные программы. Более того, одна программа может являться результатом выполнения другой программы, что откры- вает принципиальную возможность автоматизировать процесс сос- тавления программ, поручив эту работу — частично или полно- стью — самой машине путем выполнения соответствующих специ- альных программ. Кроме упомянутых выше двух принципов, машина фон-Неймана имеет ряд и других особенностей по сравнению с машиной Тьюрин- га. Эти особенности обусловлены стремлением сделать машину более удобной для практического использования, избегая при этом ее чрезмерного усложнения. Мы остановимся на двух, наиболее важ- ных из этих особенностей. 4.1.2. Машинные операции. Если машина Тьюринга способна выполнять единственную, и притом простейшую операцию переработки данных — замену одного символа в ячейке памяти другим символом, то ЭВМ может выполнять гораздо более широкий набор операций. Каждая конкретная ЭВМ имеет свой набор машинных операций-, решение любой конкретной задачи в конечном счете должно быть сведено к выполнению после- довательности операций из этого набора. Очевидно, что с точки зре- ния удобства использования машины желательно, чтобы этот набор был возможно шире и чтобы он включал в себя достаточно содержа- тельные операции. С точки же зрения простоты реализации жела- тельно, чтобы этот набор был невелик и чтобы в него входили лишь простейшие операции, так как это позволяет сделать машину более простой, дешевой и надежной. Поскольку эти два требования противоречивы, то выбирается некоторый компромиссный вариант, с учетом того класса задач, на решение которых прежде всего ори- ентируется данная машина. В дальнейшем мы рассмотрим более подробно типовой набор машинных операций, а сейчас — для иллюстрации отличия от ма- шины Тьюринга — отметим лишь, что в ЭВМ в этот набор могут входить, например, четыре основные арифметические операции над вещественными числами (сложение, вычитание, умножение и деление). Для сравнения вспомним, что даже такое простейшее действие, как прибавление единицы к некоторому целому числу, на машине Тьюринга требовало выполнения целой программы»
4Л] ОБЩАЯ ХАРАКТЕРИСТИКА ЭВМ 201 Достаточно широкий набор весьма содержательных операций, выполнение которых предусмотрено в ЭВМ аппаратным путем, позволяет достичь высокой скорости вычислений, а также обес- печивает запись алгоритмов в гораздо более компактном виде, чем для машины Тьюринга, что очень важно при практическом ис- пользовании ЭВМ для решения задач. 4.1.3. Ячейки памяти. В каждой ячейке памяти ЭВМ может храниться не один символ, как в машине Тьюринга, а упорядоченная последовательность до- пустимых символов, которая называется машинным словом (словом машины, или просто словом). В соответствии с этим каждая ячейка памяти состоит из упорядоченной последовательности разрядов, каждый из которых служит для хранения соответствующего символа. Число таких разрядов в ячейке называется ее разрядностью или длиной машинного слова. В большинстве реальных ЭВМ допускается использование двух различных символов, в качестве которых обычно принимаются цифры 0 и 1. Это обстоятельство, как уже отмечалось ранее, не яв- ляется принципиальным, поскольку с помощью двух различных символов можно представить (закодировать) любую информацию.- В этом случае каждый разряд ячейки может быть реализован с помощью какого-либо простейшего физического прибора или среды, которые могут находиться в одном из двух различных устойчивых состояний: одному из них ставится в соответствие цифра 0, а дру- гому — цифра 1. Разряды, в каждом из которых может быть записан только один из двух различных символов, называются двоичными разрядами или битами. Поскольку в конечном счете вся информа- ция, используемая ЭВМ при решении той или иной конкретной за- дачи (программа, исходные данные, промежуточные и окончатель- ные результаты), представляется в машине в виде машинных слов, то длина слова выбирается так, чтобы с помощью одного машинного слова можно было представить достаточно содержательную инфор- мацию. В большинстве реальных машин длина слова лежит в преде- лах от 32 до 64 битов. Машинное слово может, например, представлять собой отдель- ное число, изображенное с достаточно высокой степенью точности, так что за одно обращение к памяти можно выбрать из нее сразу все число, являющееся одним из аргументов арифметической операции, или запомнить результат выполнения этой операции. Машинное слово может представлять собой и отдельную коман- ду программы. Команда — это предписание машине выполнить одну операцию из ее набора операций. Как уже отмечалось, каждая ЭВМ, в отличие от машины Тью- ринга, предназначена для выполнения не какого-нибудь одного* а многих разных алгоритмов. Это обстоятельство требует наличия
302 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ 1Гл. 4 специального алгоритмического языка, на котором можно было бы формулировать алгоритмы, подлежащие выполнению, и который был бы понятен машине. Такой язык называется машинным языком (или языком машины). Запись алгоритма на языке машины называ- ется машинной программой, а самостоятельная фраза этого языка, определяющая некоторый этап вычислений, является командой. Таким образом, программа представляет собой некоторую совокуп- ' ность команд, а выполнение машиной программы в целом сводится к последовательному выполнению отдельных ее команд. В общем случае команда должна содержать информацию о том, какую именно операцию надо выполнить, информацию об ее опе- рандах (аргументах и результате) и информацию о том, какую коман- ду надо выполнить вслед за данной командой. Поскольку объем этой информации сравнительно невелик, то каждая отдельная коман- да программы обычно и представляется одним словом ма- шины. Информация об операндах и следующей команде, подлежащей выполнению, задается путем указания их адресов в памяти, так что команда, представляющая собой указание о вычислении, например, разности двух чисел, звучит следующим образом: «Выполнить опера- цию вычитания; уменьшаемое взять из ячейки Л1, вычитаемое — из ячейки А2; результат запомнить в ячейке АЗ и перейти к вы- полнению команды, хранящейся в ячейке А 4», где Л1, Л2, ЛЗ и Л4— некоторые адреса (т. е. номера ячеек памяти). Таким образом, если в алголе с каждой переменной величиной связываются два ее атрибута — имя и значение, то в машине фон-Неймана к ним добавляется еще один атрибут — адрес этой величины, т. е. номер той ячейки памяти, которая отводится для хранения ее значения. В дальнейшем, в случае необходимости, адрес переменной с именем х мы будем обозначать через «адр.х». То обстоятельство, что адрес (как номер ячейки памяти) является тоже числом (целым без знака), при первоначальном изучении ЭВМ вызывает определенные трудности, связанные со смешением этих двух атрибутов — значения переменной и ее адреса. Поэтому важно помнить, что это — разные вещи: когда мы ссылаемся на нужное зна- чение путем указания его адреса, скажем 1250, то это вовсе не зна- чит, что само значение, хранящееся в ячейке с этим номером, есть 1250 — такое совпадение может быть лишь чисто случайным. На каждой машине фиксируется определенный формат команд (один или несколько): позиции (разряды) машинного слова, пред- ставляющего собой команду, объединяются в определенные поля (группы), и каждое поле служит для задания информации опреде- ленного назначения. Одно из полей, называемое полем операции, служит для указа- ния машинной операции, которая должна быть выполнена по дан- ной команде. Для этого все операции, входящие в набор машинных
у 4.2) СТРУКТУРА ЭВМ И ЕЕ РАБОТА 203 операций данной ЭВМ, нумеруются, и в поле операции команды указывается номер 0 требуемой операции, т. е. код операции. Для задания адресов операндов и следующей команды, подле* жащей выполнению, служат остальные поля, называемые соответ- ственно полем первого адреса, полем второго адреса и т. д. Естест- венно, длина каждого из этих полей должна позволять указывать адрес любой ячейки памяти. Например, команда может иметь следующий формат: 0 41 42 43 44 Поле Поле Поле Поле Поле операции первого второго третьего четвертого адреса адреса адреса адреса 4.2. Структура ЭВМ и ее работа Общую структуру ЭВМ можно изобразить в виде схемы, при- веденной на рис. 4.1. На этой схеме выделены две основные части ЭВМ: память и процессор. Процессор (от слова processing — обработка) служит для обработки данных, находящихся в памяти, так что его общее назначе- ние аналогично автомату в машине Тьюринга. Стрелка, идущая от памяти к про- цессору, отражает два факта: пер- вый — процессор в своей работе ру- ководствуется командами программы, выбираемыми из памяти, и второй — аргументы выполняемых операций то- же, как правило, выбираются из па- мяти.Стрелка,идущаяотпроцессоракпамяти, отражает тот факт, что получаемые в процессоре результаты выполнения отдельных опе- раций отсылаются на запоминание в память. Итак, процессор ЭВМ выполняет следующие функции: — выбор очередной команды, подлежащей выполнению; — выбор аргументов для заданной в команде операции; — выполнение этой операции — запоминание полученного результата. Ту часть процессора, которая предназначена для непосредствен- ного выполнения операций, предусмотренных на данной ЭВМ, принято называть арифметическим устройством (АУ). Остальные функции, выполняемые процессором, носят характер управления работой различных устройств ЭВМ, поэтому часть процессора, вы- полняющую эти функции, называют устройством управления (УУ)»
204 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ (Гл. 4 В некоторых машинах эти два устройства выделяются чисто услов- но, а в некоторых машинах они разделяются и конструктивно. Для решения на ЭВМ какой-либо задачи в ее память предва- рительно должна быть введена (записана) вся информация, необ- ходимая для работы машины, а именно: программа решения данной задачи и исходные данные, используемые в процессе ее решения. В простейшем случае вся эта информация предварительно наносит- ся на внешние носители данных с помощью специальных внешних устройств, работающих автономноох_Э]ВМ^Л_и^честве внешних носителей данных чаще всего лсибльзуются перфор карты и перфо- ленты. Для непосредственного ввода данных с внешних носителей »в память машины служит устройство ввода, которое «прочитывает» (воспринимает) данные, представленные на внешнем носителе в виде системы отверстий, и преобразует их в электрические импульсы (например, с помощью фотоэлементов), которые и передаются в память для запоминания представленной с их помощью информа- ции. На самом деле в состав ЭВМ может входить несколько уст- -ройств ввода, возможно, различных типов. Результаты решения задачи получаются в памяти машины; для выдачи из машины этих результатов или другой информации, храня- тщвйся в памяти, служит устройство вывода. Это устройство преоб- разует электрические сигналы, поступающие из памяти, в неко- действия, в результате которых производится печать на ши- роком рулоне бумаги определенных символов, нанесение пробивок за перфоленту или перфокарты, вычерчивание графиков и т. д. Устройств вывода в составе ЭВМ также может быть несколько, и также разных типов. Выполнение каждой команды процессором связано с неоднократ- ными обращениями ц памяти — для чтения самой команды и аргу- ментов заданной в ней операции, а также для записи получаемого результата, так что для достижения высокого быстродействия ма- шины память также должна быть достаточно быстродействующей. А для того, чтобы на машине можно было решать достаточно ши- рокий класс задач, ее память должна обладать способностью хра- нить одновременно достаточно большой объем информации, т. е. иметь большую емкость. Однако построение памяти большой емкости и с высоким быстродействием встречает значительные труд- ности технического характера и обходится весьма дорого. Поэтому в современных ЭВМ наряду с быстрой оперативной памятью, име- ющей сравнительно небольшую емкость (несколько десятков или сотен тысяч ячеек), имеется вспомогательная память, обладающая меньшим быстродействием, но с большей емкостью, позволяющей одновременно хранить в ней десятки и сотни миллионов машинных слов. Оперативную память часто называют внутренней, а вспомо- гательную память — внешней. Главное различие между оперативной и вспомогательной памятью состоит в том, что процессор имеет
4.2J СТРУКТУРА ЭВМ И ЕЕ РАБОТА 205 непосредственный доступ только к ячейкам оперативной памяти. Чтобы использовать информацию, хранящуюся во вспомогательной памяти, она предварительно должна быть передана в оперативную память — для обмена информацией между этими двумя видами па- мяти предусматриваются соответствующие машинные операции. По сути дела, эти два вида памяти являются различными устройст- вами машины. С учетом сказанного выше, структуру ЭВМ можно изобразить в виде более подробной схемы, приведенной на рис. 4.2. На этой схеме двойными стрелками показаны пути и направле- ние передачи данных, а простыми стрелками — пути и направ- ние передачи управляющих сигналов. Как видно из схемы, устрой- ство управления посылает управляющие сигналы всем остальным устройствам, направляя и координируя их работу. Из схемы видно, что и арифметическое устройство может посы- лать управляющие сигналы в устройство управления. Эти сигналы могут использоваться устройством управления — например, для выбора одного из нескольких возможных путей дальнейших вы- числений, предусмотренных в программе, в зависимости от свойств результата, полученного в арифметическом устройстве при выпол- нении очередной операции. Процессор ЭВМ на каждом такте работы использует довольно большой объем информации. Для ее фиксации в составе процессора имеется ряд специальных запоминающих ячеек, называемых регист- рами. Некоторые из них используются, в частности, для кратковре- менного хранения машинных слов, выбираемых из памяти и исполь- зуемых при выполнении очередной команды. Знание некоторых регистров процессора важно для понимания как функционирования
206 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 самой машины, так и приемов программирования, которые будут рассматриваться в дальнейшем. В устройстве управления наиболее важными являются следую- щие регистры. Регистр адреса команды (С). Этот регистр служит для хранения информации о местонахождении в памяти очередной команды, под- лежащей выполнению, а именно: в начале каждого очередного такта работы машины устройство управления всегда трактует содержимое этого регистра как адрес той команды, которая должна быть вы- полнена на этом такте. Напомним, что каждая команда программы представляется в виде машинного слова и все они хранятся в памяти, так что содержимое регистра С трактуется как номер ячейки, из которой и надо выбрать команду, подлежащую выполнению на дан- ном такте. Содержимое этого регистра к началу очередного такта формируется в результате выполнения команды на предыдущем так- те, а к началу выполнения программы — некоторым специальным образом (например, при нажатии определенной кнопки на пульте управления в этот регистр всегда заносится один и тот же адрес в соответствии с принятым соглашением, реализованным схемным путем). Разрядность регистра С должна быть такова, чтобы в нем можно было зафиксировать адрес любой ячейки от 0 до N—1. Регистр команды (К). В этот регистр выбирается из памяти (по адресу, зафиксированному в регистре С) очередная команда на время ее расшифровки и выполнения. Разрядность этого реги- стра такая же, как и разрядность ячеек памяти, так что в регистре К можно зафиксировать любое машинное слово. Регистр признака результата (со). Обычно это одноразрядный регистр, в котором фиксируется значение 0 или 1 в зависимости от свойств очередного результата, полученного в арифметическом устройстве (вспомним стрелку, изображенную на схеме машины, которая показывает движение управляющих сигналов от арифме- тического устройства к устройству управления). Обычно все ма- шинные операции разбиваются на группы, и каждая группа опе- раций вырабатывает значение со по одному из свойств получаемого результата. Например, одна группа операций может вырабатывать значение со в зависимости от знака полученного числа, другая — в зависимости от абсолютной величины этого числа и т. д. В част- ности, некоторая группа операций может сохранять без изменения предыдущее значение со. В некоторых машинах каждая операция может вырабатывать несколько таких признаков — в этом случае регистр признака результата состоит из нескольких разрядов, каждый из которых служит для фиксации определенного признака. Значение, зафиксированное в регистре со, в нужные моменты вре- мени может использоваться устройством управления для выбора того или иного пути дальнейших вычислений в зависимости от свойства полученного результата.
4.2] СТРУКТУРА ЭВМ И ЕЕ РАБОТА 207 Регистр особой ситуации (<р). Это одноразрядный регистр, в ко- тором фиксируется значение 1, если при выполнении очередной команды наступила необычная («аварийная») ситуация — например, попытка деления на нуль, попытка извлечь квадратный корень из отрицательного числа (при наличии машинной операции извлечения квадратного корня) и т. д. На первых ЭВМ выработка значения <р—1 влекла за собой остановку машины; на современных ЭВМ это вле- чет за собой прекращение выполнения данной программы и переход к выполнению какой-то другой программы. С некоторыми другими регистрами устройства управления мы познакомимся несколько позже. В арифметическом устройстве так- же имеется несколько регистров. Основным из них является ре- гистр сумматора (S) — этот регистр используется при выполнении почти каждой операции, и в нем получается результат каждой из них. Для приема и хранения на время выполнения операции ее ар- гументов, выбираемых из памяти, служат соответственно регистр первого аргумента (R\) и регистр второго аргумента (R2). Заметим, что отдельные устройства ЭВМ (память, АУ и другие) являются достаточно автономными и работают по принципу выпол- нения отдельных заказов, поступающих от устройства управления, для чего в их составе имеются свои, «местные» устройства управле- ния. Память, например, может выполнять заказы двух видов: про- честь (выдать) слово из данной ячейки, и записать (при- нять на хранение) слово в заданную ячейку. В местном устройстве управления памятью имеется специальный регистр, в котором фик-, сируется задаваемый номер ячейки; под воздействием управляюще- го сигнала, поступающего от процессора, местное устройство управ- ления памяти обеспечивает доступ к заданной ячейке и в зависимости от заданного управляющим сигналом режима производит либо чте- ние слова, хранящегося в этой ячейке, и его передачу в виде соот- ветствующей комбинации электрических сигналов в выходные цепи, либо запись в эту ячейку слова, поступающего по входным цепям. Слово, записанное в ту или иную ячейку, хранится в ней до тех пор, пока в эту ячейку не будет записано другое слово — в этот момент предыдущее содержимое данной ячейки автоматически унич- тожается (стирается). При чтении же слова из ячейки оно продолжает сохраняться в этой ячейке — в выходные цепи передается как бы «копия» этого слова с сохранением «оригинала» в данной ячейке. Следовательно, однажды записанное'в какую-либо ячейку слово цржно затем прочитывать многократно. Рассмотрим теперь типовую последовательность действий, вы- полняемых машиной на очередном такте своей работы, во время которого выполняется команда, хранящаяся по адресу х: х: 0 Al А2 АЗ А4 где 0 — код операции, а Л1, Л 2, ЛЗ, Л4 — некоторые адреса.
208 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 К началу этого такта в регистре С адреса команды должен быть зафиксирован (например, в результате выполнения предыдущей команды) адрес х рассматриваемой команды. Последовательность действий машины на очередном такте распадается на следующие этапы. Г. Устройство управления посылает заказ памяти на выдачу слова из ячейки, номер которой зафиксирован в регистре С (т. е. из ячейки с номером х), устанавливая связи таким образом, что вы- данно^\памятью по этому заказу слово поступает в регистр команды К устройства управления. Слово, поступившее в регистр К, устрой- ство управления всегда трактует как очередную команду, подлежа- щую выполнению. 2°. Адрес А4, указанный в поле четвертого адреса команды, пересылается в регистр С (тем самым в этом регистре фиксируется адрес следующей подлежащей выполнению команды). 3°. Устройство управления посылает памяти заказ на выдачу слова из ячейки с номером А1, указанным в поле первого адреса команды, устанавливая связи таким образом, что выданное памятью слово поступает в регистр /?1 арифметического устройств^. 4°. Этот этап аналогичен предыдущему— с той лишь разницей, что памяти посылается заказ на выдачу слова из ячейки с номером А2, указанным в поле второго адреса команды, а выданное памятью слово поступает в регистр 7?2. 5°. Устройство управления посылает заказ арифметическому устройству на выполнение той операции, код которой 6 задан в поле операции команды. Аргументы операции берутся из регистров /?1 и /?2, а результат выполнения операции получается в сумматоре <S. 6°. В зависимости от свойств полученного в сумматоре результа- та вырабатываются значения <о и <р по правилам, соответствующим выполненной операции. 7°. Устройство управления посылает памяти заказ на запоми- нание слова, передаваемого ей из сумматора S, в ячейке с номером АЗ, указанным в поле третьего адреса команды. На этом очередной такт работы машины заканчивается и машина начинает новый такт работы, который выполняется аналогичным образом. Поскольку теперь в регистре С зафиксирован адрес А4, указанный в поле четвертого адреса только что выполненной коман- ды, то в результате выполнения этапа Г в регистр К устройства управления будет выбрано слово из этой ячейки А4 — это слово и будет теперь трактоваться как очередная команда, подлежащая вы- полнению. При выполнении некоторых машинных операций указанная выше последовательность действий несколько видоизменяется: некоторые из этапов опускаются, а некоторые получают иное со- держание. В дальнейшем мы остановимся на подобного рода при- мерах.
4Л1 СТРУКТУРА ЭВМ И ЕЕ РАБОТА 209 4.2.1. Формализованное описание работы ЭВМ* Теперь дадим формализованное описание функционирования ЭВМ, используя для этой цели формальный язык, полученный не- которым расширением алгола. Как мы уже отмечали, любые данные в ЭВМ представляются в виде машинных слов, причем содержимое даже одной и той же ячейки памяти в разные моменты времени может интерпретироваться машиной по-разному в зависимости от того, где используется это слово. Так, если слово в соответствии с рассмотренным выше тактом ра- боты машины на этапе Г поступает в регистр команды К устройства управления, то это слово будет рассматриваться как очередная команда, подлежащая выполнению. Если то же самое слово будет выбрано в арифметическое устройство в качестве одного из аргу- ментов арифметической операции, то при ее выполнении данное сло- во будет трактоваться как число и т. д. Так что в отличие от алгола, где каждой переменной предписывается определенный тип прини- маемых ею значений, машинным словам нельзя предписать никакой тип — в процессе выполнения программы им могут предписываться различные типы. В связи с этим добавим к имеющимся в алголе типам данных еще один тип, который будем обозначать описателем typeless, что озна- чает «бестиповый»: если какой-либо переменной предписан тип typeless, то это значит, что этой переменной можно присваивать значения любого типа. Тогда оперативную память ЭВМ можно описать как одномерный массив typeless array ОП [0 : N—1], где N — число ячеек памяти. В большинстве машин при чтении из ячейки с номером 0 всегда выдается машинное слово, содержащее нули во всех позициях («ну- левое» слово), т. е. ОП [01=0. Регистры процессора можно формально описать как простые переменные следующим образом: integer С — индекс (адрес) в массиве 077 очередной команды, подлежащей выполнению; typeless 7? 1, 7?2, 5 — аргументы выполняемой операции, выбран- ные из памяти, и результат выполнения операции; boolean со, <р — логические переменные, используемые при уп- равлении порядком выполнения команд. Несколько сложнее обстоит дело с регистром команды К, в ко- торый выбирается очередная выполняемая команда. Как мы знаем, машинное слово, заносимое в этот регистр, т. е. команда, является некоторой структурой, состоящей из нескольких полей. В этих полях представляются независимые друг от друга значения, имею- щие различный смысл. Подобная ситуация нередко возникает и при решении практических задач, в которых приходится иметь дело со структурированными данными. В алгоритмических языках, ори-
210 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЙ МАЩИНЫ [Гл. ♦ ентированных на решение такого рода задач, предусматриваются соответствующие возможности, в частности — соответствующие типы данных. В связи с этим мы сделаем еще одно расширение алгола: для опи- сания структурированных величин введем в употребление еще один тип значений, обозначаемый описателем structured. При формаль- ном описании таких величин мы будем вслед за этим описателем, как обычно, указывать имя величины, за которым в круглых скоб- ках будем записывать через запятую имена каждого из полей с ука- занием типа значения, задаваемого в этом поле. При этих соглаше- ниях регистр команды К можно формально описать так: structured К (integer КОП, integer Al, integer А2, integer A3, integer Л4) Это описание говорит о том, что значением величины К является структура, состоящая из пяти полей, которым даны соответственно имена КОП, А1, А2, АЗ, А4, и что в каждом поле задается целочис- ленное значение (код операции, а также адреса операндов и следую- щей команды, подлежащей выполнению). Для ссылки на значение, представленное в том или ином поле, будем указывать имя этого поля, за которым в круглых скобках будем указывать имя структу- рированной величины, в которую входит это поле. Например, запись КОП(К) означает, что имеется в виду значение, заданное в поле КОП структурированной величины К, т. е. код операции, заданный в команде, находящейся в регистре К. Там, где это не вызывает неод- нозначности понимания, можно ограничиться указанием имени поля, не указывая имени величины, в которую входит это поле. С использованием этого формализма типовой такт работы ЭВМ можно описать следующим образом (имея в виду приведенные ранее описания): 1°. К'.—ОП [С] 2°. С:=Л4(К) 3°. П1:=0П [Л1(Ю1 4°. R2:=On.[A2(K)l 5°. S:=R1QR2 (где 0 — операция с кодом КОП (К)) 6°. <o:=fe(S); q>:=ge(S) (где логические функции /о и go опреде- ляются операцией 0) 7°. О77[ЛЗ(К)]:=$ Для большей наглядности мы сделали отступление от правил записи текста на алголе, использовав обозначения fe и go, а также греческие буквы со, <р и 0 с тем, чтобы не отходить от обозначений, обычно используемых при описании ЭВМ. Функции fo и go определяются для каждой машинной операции (или для некоторой группы операций). Например, если 1 — код операции сложения, то п.п. 5° и 6° такта для конкретной ЭВМ могут выглядеть следующим образом:
4.2] СТРУКТУРАМИ И ЕВ РАБОТА 211 5q. 5:=Я1+/?2 6°. ®:=S<0; <p:—abs(S)>M где М — некоторая константа, равная максимальному числу, представимому в данной ЭВМ. Для некоторых операций (управления, ввода/вывода и других) такт выполняется несколько иначе. Влияние <о на выполнение опе- раций мы увидим, когда будем рассматривать операции управления. Всю работу процессора при выполнении заданной ему в опера- тивной памяти программы и начального адреса команды также мож- но схематически описать в виде следующей процедуры (через Q обозначим код операции останова): procedure ПРОЦЕСС (ОП, НАЧС); value НАЧС; typeless array 0/7; integer НАЧС; begin integer C; typeless 7?1, R2, S; boolean co, <p; structured К (integer К0П, integer Al, integer 42, integer A3, integer 44); Ct = НАЧС; <p:=false; for К:=0П [Cl while КОП (А)=/=ЙЛ “|<P do begin C:=44 (A); Я1:=0П U1(A)1; /?2:=0/7 [42(A)]; S:=/?10/?2; <o:=fe(S); <p:=£e(S); 0П [43 (A)]:=S end end Более конкретно, с учетом имеющегося в данной ЭВМ набора операций, функционирование машины описывается процедурой следующего вида (в предположении, что операция сложения имеет код 1, операция вычитания — код 2, . . ., операция останова — код 63): procedure ПРОЦЕСС (ОП, НАЧС); value НАЧС; typeless array ОП; integer НАЧС; begin integer C; typeless Rl, R2, S; boolean co, <p; structured K. (integer К0П, integer 41, integer 42, integer 43, integer 44); Ct = НАЧС; <p:=false; for К-.=0П [Cl while КОП (A)¥=63 Д 1<p do begin C:=44; Rl.=0n [41(A)]; R2.=0n [42(A)]; if КОП (A)=1 then begin S: =7? 1 +R2; co: =S<0; <p:=4BS(S)>M; 0/7 [43(A)]: =S end; if AO/7(A)=2 then begin S:=/?l—R2; co:=S<0; <p:=4BS(S)>Af; 0П [43(A)]: =S end; end end Полный текст такой процедуры должен содержать описание всех операций из набора данной ЭВМ.
212 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 4.3. Характеристика реальных ЭВМ Весь период существования ЭВМ был периодом их бурного раз- вития и усовершенствования: практически каждые 8—10 лет одно поколение ЭВМ сменялось другим, отличаясь от предыдущего сво- ими параметрами, элементной базой и архитектурой (структурой). В результате быстродействие ЭВМ выросло от нескольких сотен до миллионов операций в секунду; емкость оперативной памяти в сов- ременных ЭВМ составляет десятки и сотни тысяч ячеек вместо не- скольких сотен или тысяч ячеек в машинах первого поколения. Все это резко повысило возможности ЭВМ и расширило область их применения. Наряду с изменениями инженерного характера постоянно менялась и методика использования ЭВМ. К настоящему времени создано много различных типов ЭВМ, которые отличаются друг от друга по целому ряду показателей. В данной главе мы остановимся на тех характеристиках ЭВМ, ко- торые наиболее существенно сказываются на программировании: способах представления чисел в машине, наборе машинных операций и формате команд ЭВМ. 4.3.1. Системы счисления. 4.3.1.1. Позиционные системы счисления. Практически во всех современных ЭВМ любая информация представляется в виде дво- ичных кодов, т. е. в виде упорядоченных последовательностей из двух различных символов, в качестве которых обычно принимаются цифры 0 и 1. В связи с этим при работе с ЭВМ часто приходится использовать системы счисления, отличные от привычной десятичной системы счисления. При этом в основном используются позицион- ные системы счисления, которые строятся по тем же принципам, что и десятичная система. Как известно, запись произвольного числа х (х^О) в десятичной системе основывается на представлении этого числа в виде полинома х=а„10«+а„.110«-1+.. .+а11О+ао+а-11О"1+б1.21О“2+. . ., где каждый из коэффициентов а( является целым неотрицательным числом, меньшим десяти. Для обозначения каждого из таких чисел введен в употребление специальный символ, называемый цифрой-. 0, 1,2, 3, 4, 5, 6, 7, 8 и 9. Запись числа к в десятичной системе пред- ставляет собой перечисление всех последовательных коэффициентов этого полинома, причем коэффициенты при неотрицательных степе- нях числа десять отделяются от коэффициентов при отрицательных степенях точкой (которая в последнее время все чаще используется вместо запятой): Xz=zCLnUn^.^. . • CL\CIq. • • • На этих же принципах основываются и другие позиционные сис- темы счисления с любым (целым) основанием Р(Р>1). В каждой из таких систем используется Р различных цифр для обозначения не-
ХАРАКТЕРИСТИКА РЕАЛЬНЫХ ЭВМ 213 4.3J которых различных целых чисел, называемых базисными (мы будем рассматривать только такие системы,, в которых в качестве базисных принимаются последовательные целые числа от 0 до Р—1). Запись произвольного числа х в системе счисления с любым ос- нованием Р также основывается на разложении этого числа х по последовательным степеням числа Р: х=6„Р»+6„_1Рв-1+. . .+M-fc-1P-1+b_,P-2+. . . где каждый коэффициент bt является одним из базисных чисел и изображается одной цифрой. Если такое разложение получено, то как и в случае десятичной системы, число х в Р-ичной системе изоб- ражается последовательностью своих коэффициентов с разделением целой и дробной частей с помощью точки: Х=(>А-(- • • b°- b-ib-t • • • Для указания в случае необходимости используемой системы счисления условимся основание системы (в ее десятичной записи) приводить в качестве нижнего индекса у записи числа, например 325,. Арифметические операции над числами в любой позиционной сис- теме счисления выполняются по тем же правилам, что и в десятичной системе, поскольку все они основываются на правилах выполнения операций над соответствующими полиномами. При этом использу- ются те таблицы сложения и умножения, которые имеют место в системе счисления с данным основанием. Остановимся кратко на некоторых системах счисления, с кото- рыми наиболее часто приходится иметь дело при работе на ЭВМ. Двоичная система. Меньше всего различных цифр — две — ис- пользуется для изображения чисел в двоичной системе счисления. Это цифры 0 и 1, которыми обозначаются числа нуль и единица. Как обычно, запись любого числа в двоичной системе основывается на его разложении по степеням числа два, например: 2310=1 • 24+0 • 28+1 • 22+1 • 2*+1 • 2°=10111 ъ 29/32=1.2-1-}-1 -2-2+1.2-»+0.2-4-|-1 -2-5=0.11101 г. Полезно запомнить запись в двоичной системе счисления первых шестнадцати целых чисел: Десятичная система Двоичная система Десятичная система Двоичная система Десятичная система Двоичная система 0 0 6 110 11 1011 * 1 1 7 111 12 1100 2 10 8 1000 13 1101 3 11 9 1001 14 1110 4 5 100 101 10 1010 15 1111
214 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. < При выполнении арифметических операций над числами в дво- ичной системе используются следующие таблиц^ сложения и ум- ножения: 0+0= 0 0X0=0 0+1= 1 0X1=0 1+0= 1 1X0=0 1 + 1 = 10 1X1 = 1 Поскольку в этой системе для изображения любых чисел ис- пользуются только две различные цифры, то для построения ЭВМ можно использовать элементы, имеющие лишь два различных устой- чивых состояния, одно из которых ставится в соответствие цифре 0, а другое — цифре 1. Это обстоятельство, а также простота выпол- нения арифметических операций в этой системе и являются главной причиной того, что двоичная система используется в большинстве ЭВМ- Восьмеричная система. В восьмеричной системе счисления цифры 0, 1, 2, 3, 4, 5, 6 и 7 используются для обозначения последователь- ных целых чисел от нуля до семи. Здесь запись числа основывается на его разложении по степеням числа восемь с указанными выше коэффициентами. Так, 215io=192+16+7=3- 8’+2 • 8х+7 • 8°=3278, 30.251o=3-81+6-8e+2-8“1=36.2g. Шестнадцатеричная система. Эта система с базисными числами от нуля до пятнадцати отличается от рассмотренных выше тем, что здесь для обозначения базисных чисел общепринятых (арабских) цифр уже недостаточно, поэтому приходится вводить в употребление дополнительные символы в качестве цифр. Обычно для обозначения первых целых чисел от нуля до девяти используются арабские цифры от 0 до 9, а для следующих чисел — от десяти до пятнадца- ти — в качестве «цифр» используютсй символы a, b, с, d, е и f. Так, десятичное число 174.5 в шестнадцатеричной системе запишется в виде ае.8. Действительно, ае.81в=10-16+14+8/16=160+14+0.5=174.510. Заметим, что в любой позиционной системе счисления ее основа- ние Р записывается в виде 10, а умножение какого-либо числа на число вида Рк, где k — целое, сводится просто к переносу точки в изображении множимого на |А| разрядов вправо или влево (в за- висимости от знака Л) — точно так же, как и в десятичной системе счисления. Смешанные системы счисления. В некоторых случаях числа, заданные в системе счисления с основанием Р, приходится изоб- ражать с помощью цифр другой системы с основанием Q(Q<P). Такая необходимость возникает, например, в случае, когда в память машины, способной воспринимать только двоичные цифры, нужно ввести десятичные числа для их последующей переработки с по-
4 3] ХАРАКТЕРИСТИКА РЕАЛЬНЫХ ЭВМ 215 мощью ЭВМ, например для предварительного перевода этих чисел в двоичную систему счисления. В таких случаях используют сме- шанные системы счисления, в которых каждый коэффициент раз- ложения числа по степеням Р записывается в Q-ичной системе. Такая смешанная система называется Q-P-ичной. При этом для за- писи каждого из упомянутых выше коэффициентов отводится одно и то же количество Q-ичных разрядов, минимально необходимое для того, чтобы с их помощью можно было изобразить любой из допустимых коэффициентов. Например, в смешанной двоично-деся- тичной системе для изображения любого коэффициента в десятич-' ном разложении числа нужно отвести четыре двоичных разряда, так как с их помощью можно изобразить любое число от нуля до пятнадцати включительно, тогда как с помощью трех разрядов, можно изобразить только числа от нуля до семи. Так, десятичное число 8903 в смешанной двоично-десятичной системе запишется сле- дующим образом (учитывая, что 81О=10002, 91в=1001» и Зхв=00112): 89031о= 1000 1001 0000 00112_„. Следует обратить внимание на то, что хотя в двоично-десятичной записи числа используются только цифры 0 и 1, эта запись отли- чается от записи данного числа в двоичной системе счисления. Дей- ствительно, 890310=100010110001112. Как видно, эта запись от- личается от записи этого же числа в смешанной двоично-десятичной системе. Особый интерес представляет случай, когда P—Q‘(l — целое). В этом случае запись любого числа в смешанной Q-P-ичной системе тождественно совпадает с записью этого числа в Q-ичной системе. Так, если Р=8, Q=2 (8=2*), то AZ=27638=010 111 110 0112-в=010 111 110 0112. ‘ Таким образом, запись числа в Р-ичной системе счисления в слу- чае P=Ql является просто сокращенной записью изображения этого же числа в Q-ичной системе: для такой сокращенной записи Q-ичные цифры в изображении числа объединяются вправо и влево от точки в группы по I разрядов (дополняя, в случае необходимости, нужное количество нулей справа и слева), и каждое число, представленное, в Q-ичной системе этой группой разрядов, записывается одной Р-ичной цифрой. Например, х=1011011011.1110118=0010 1101 1011. 1110 11102=0010 1101 1011.1110 11002_1в=2<й>.еа1в. 4.3.1.2. Перевод чисел из одной системы счисления в другую. В связи с использованием различных систем счисления нередко возникает необходимость перевода чисел из одной системы в другую. Задача перевода состоит в следующем. Пусть известна запись числа в системе счисления с одним основанием, например Р; X~(PnPn—i' • • Ро. Р-хР-2. • • Р-к)Р,
816 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. ♦ где р,— цифры Р-ичной системы. Требуется найти запись этого же числа в системе счисления с другим основанием Q: Х~(.ЧтУт-г • • Яо- Я-14-i • • • Q-i)q> где qt— цифры Q-ичной системы. При этом можно ограничиться рассмотрением положительных чисел, поскольку перевод любого числа сводится к переводу его абсолютной величины и приписыва- нию числу соответствующего знака. При переводе чисел приходится выполнять некоторые арифме- тические операции, поэтому надо решить вопрос о том, в какой сис- теме должны выполняться эти операции. Для определенности бу- дем считать, что в процессе перевода должны использоваться только Средства Р-ичной арифметики. В связи с этим необходимо рассмо- треть перевод из Q-ичной системы в Р-ичную (0~>Р) и перевод из Р-ичной системы в Q-ичную (P->-Q). Перевод Q-+P. Если известна запись числа х в Q-ичной системе (Ут9т—1‘ • ‘Яо- то его перевод в Р-ичную систему сводится к вычислению значения полинома x=qmQm+. . •+q»+q-iQ~1+- -+q~iQ~l, что следует из правил записи числа х в Q-ичной системе. Если при этом каждый из коэффициентов qh а также основание Q записать в Р-ичной системе и в этой же системе выполнять все необходимые арифметические действия, то окончательно полученное значение полинома и даст значение х в его Р-ичной записи. Пусть, например, число х=Ьа.8и надо перевести в десятичную систему средствами десятичной арифметики. Здесь Q=16, Р=10.. Для такого перевода запишем х в виде х=11 • 16+10+8-16-1 и вы- полним все необходимые действия в десятичной системе: х= 176+10+8/16= 186.510. Перевод P-+Q. Здесь удобно отдельно рассмотреть случаи целых чисел и правильных дробей, поскольку перевод любого числа можно свести к независимому переводу его целой и дробной частей. Перевод целых чисел. Пусть дана запись целого числа W в сис- теме счисления с основанием Р. Поскольку N — целое число, то его запись в Q-ичной системе будет иметь вид • +<>)<?. где qt (O^qt<.Q) — подлежащие определению цифры Q-ичной си- стемы. Эта запись является сокращенной записью полинома 2V=<7*Qft+<7*-1Q*-1+. - .+q„. Для определения qt разделим обе части последнего равенства на Q, причем в левой части деление выполним фактически (это можно сделать, так как запись числа N в Р-ичной системе известна и де- ление в этой системе по нашему предположению мы выполнять умеем), а в правой части деление выполним аналитически: w/Q=?AQft-l+. • .+?i+9«/Q.
4.3)' ХАРАКТЕРИСТИКА РЕАЛЬНЫХ ЭВМ 217 Приравнивая между собой целые и дробные части в левой и правой частях (учитывая, что 7Z<CQ), получим, что qe есть остаток от деле- ния N на Q. Частное от деления N на Q обозначим через Л\ (это значение в Р-ичной записи определяется в результате деления). Тогда Ni=-gkQk~1+ •. -+<7i. Поскольку Л\ есть целое число, то к нему можно применить тот же самый прием для определения значения qlt которое равно остатку ют деления A7i на Q. Полученное при этом частное обозначим через Л/2 и т. д. Этот процесс заканчивается, когда очередное частное окажется равным нулю. Итак, последовательные значения <77 (7=0, 1, . . .) получаются путем последовательного деления на Q исходного числа и каждого очередного частного. Поскольку все операции выполняются в Р-ич- ной системе, то в этой же системе будут получены и искомые коэф- фициенты qt-. Для окончательной записи числа N и Q-ичной системе надо в соответствующем порядке записать эти коэффициенты, изо- бражая каждый из них одной Q-ичной цифрой (напомним, что каж- дое q,- меньше, чем Q). Применим этот алгоритм для перевода десятичного числа А7=23 в двоичную систему средствами десятичной арифметики (здесь Р=10, Q=2): 231 2 22 11 |—2 1~10 51 2 —О 2> 2 1. ~2 112 0^0 О’ 1 Поскольку числа нуль и единица в каждой из этих систем обозна- чаются одинаковыми цифрами 0 и 1, то в процессе деления сразу получены искомые двоичные цифры: А7==10111а. Ситуация будет несколько иной при переводе числа М=175ю в шестнадцатеричную систему. Применение указанного выше алго- ритма в этом случае дает: —175 116 160 10|16 15~ О~0 10 Таким образом, У=10-161+15-16в. Для записи числа N в шест- надцатеричной системе надо каждый из полученных коэффициентов 10 и 15 записать одной шестнадцатеричной цифрой: N=afu. . Перевод дробных чисел. Пусть требуется перевести в Q-ичную систему правильную дробь х(0<х<1), заданную в ее Р-ичной за- дней: х=0.р_1р.1. . Запись этого числа в Q-ичной система
218 - ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ 1Гл. * будет иметь вид х=О^_,<7_4. . . .. Значит, x = ?-1Q-‘ + <7-2Q-i+ • • • +<?-mQ-a+ • • где <?_,({=1,2, . . .) — коэффициенты Q-ичного разложения, подле- жащие определению. Нетрудно убедиться, что эти коэффициенты можно получить путем последовательного умножения на Q сначала исходного числа х, а затем — дробной части очередного полученного произведения. Этот процесс продолжается до тех пор, пока дробная часть очередного произведения не окажется равной нулю, либо не будет достигнута требуемая точность изображения числа х в Q- ичной системе. Например, перевод числа х=0.11ф в двоичную систему приведет к следующей последовательности действий (вертикальной чертой отделим получающиеся целые части произведений, которые и явля- ются искомыми коэффициентами двоичного разложения числа х): 0.1 X 2 1 2 Очевидно, что дальше результаты будут повторяться, так что х=(0.0001100110011 .. .),=0.0(0011)2. Если переводимое число является приближенным, то запись х=0.р_1р_4. . .р-к означает (учитывая правило округления), что его погрешность Д = |х—х| = Р~*/2. Поэтому в записи этого числа в Q-ичной системе х=0.7_1(/_2. . . q_t следует сохранить столько цифр, чтобы его погрешность была примерно такой же, что и у исходного числа. Таким образом, значение I определяется из соот- ношения Q-U+1,^P“*<Q”Z. При этом, чтобы не внести дополнитель- ную -погрешность в результате перевода, следует получить цифру q~i-t и использовать ее для целей округления. 4.3.2. Представление чисел. В большинстве реальных ЭВМ каждое число представляется од- ним машинным словом. Далее ради определенности будем считать, что машинное слово содержит 45 двоичных позиций (битов), которые мы перенумеруем слева направо: е0 е4 е4 . . . е44 (4.1>
4.3] ХАРАКТЕРИСТИКА .РЕАЛЬНЫХ ЭВМ 219 Каждый бит может принимать одно из двух значений: 0 или 1. Ради краткости записи машинных слов условимся последовательные -дво- ичные позиции объединять в группы по три позиции и каждую та- кую тройку записывать одной восьмеричной цифрой (от 0 до 7). Другими словами, машинное слово будем трактовать как целое не- отрицательное двоичное число и записывать его в восьмеричной системе счисления. При таком соглашении слово вида (4.1) будет записываться в виде последовательности из 15 восьмеричных цифр . . .614 Позиция е« машинного слова отводится для изображения знака числа (знак + принято изображать цифрой 0, а знак — цифрой 1), а остальные позиции служат для представления абсолютной вели- чины числа в двоичной системе счисления. В ЭВМ числа могут представляться в одной из двух форм: с фик- сированной или с плавающей точкой. 4.3.2.1. Фиксированная точка. При представлении чисел в фор- ме с фиксированной точкой положение в машинном слове точки, разделяющей целую и дробную части числа, раз и навсегда фикси- руется. Чаще всего положение точки фиксируется перед старшим разрядом цифровой части числа, т. е. перед разрядом ех. В этом слу- чае машинное слово (4.1) представляет собой число х==(1—2ев)-(0.81е4. . .в44)4. Например, число х=9/16=(0.1001)2 будет представлено машинным словом (в его восьмеричной записи) 220 000 000 000 000, а число х=(—0.1)ю=(—0.0(0011))8 будет представлено (с учетом округления) машинным словом 431 463 146 314 632. Наибольшее по модулю число, которое в этом случае может быть представлено в памяти, изображается машинным словом 80111 ... 11 44 и равно (1—2е0)-(1—2”44); наименьшее по модулю представимое число, отличное от нуля, изображается словом 80 000... 01 и равно (1—2в0) 2-44. Таким образом, кроме числа нуль (ee=8i= . . . — s44=0) в ма- шине при данном способе могут быть представлены только числа из диапазона 2~44гС|х|<3—2-44. Все числа из интервала (—2~44, 2-44) приходится представлять в машине так же, как и число нуль, по- этому любое число из этого интервала называется «машинным ну- лем». Заметим, что поскольку на изображение числа отводится фикси- рованное количество разрядов, то не любое число из диапазона 2~44<|х|<1—2~44 может быть представлено точно: точно представ-
220 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 ляются лишь числа, в двоичной записи которых отличные от нуля цифры встречаются только в первых 44 разрядах после точки. Все остальные числа представляются приближенно, с одинаковой аб- солютной погрешностью Дх=|х—х|=2~4В, где х— точное, а х — приближенное значения. Если положение точки зафиксировать вслед за последним раз- рядом машинного слова (е44), то в виде машинных слов могут быть представлены только целые числа (из диапазона 0^|х|^246—1). 4.3.2.2. Плавающая точка. В форме с плавающей точкой каждое число х представляется в виде х=Л4-2^, где М удовлетворяет ус- ловию |А1|<1 и называется цифровой частью (или мантиссой) числа, ар — целое число, называемое (двоичным) порядком. Та- ким образом, число х задается на самом деле парой чисел (М, р). Поскольку |44|d, то положение точки в мантиссе фиксируется перед ее старшим разрядом, а фактическое положение точки в изо- бражении числа определяется порядком р, и при изменении этого порядка фактическое положение точки меняется, «плавает», откуда и получила название эта форма представления чисел. При представлении чисел с плавающей точкой в машинном слове выделяются отдельные группы позиций для изображения мантиссы и порядка числа, например: 0 1 2\ ... |5 0| ... |44 и \м\ где ех — знак числа, а е.р — знак порядка. Количество позиций, отводимых на изображение мантиссы, оп- ределяет точность представления чисел, а количество позиций, от- водимых на изображение порядка, определяет диапазон представи- мых чисел. Для обеспечения максимально возможной точности представления чисел, т. е. чтобы не тратить позиции машинного слова на изображение незначащих нулей в старших разрядах ман- тиссы, на мантиссу обычно накладывается требование 0.5^|Л4|<1. Если это требование выполнено, то число называется нормализо- ванным. Признаком нормализованности числа является наличие цифры 1 в старшем разряде мантиссы. Например, число Xi=(—4.75)i0=(—0.10011)2 -23 будет пред- ставлено машинным словом 403 460 000 000 000, а число х2=9/32=(0.1001)'22-х— машинным словом 201 440 000 000 000. В нашем случае в виде машинных слов могут быть представлены отличные от нуля нормализованные числа из диапазона 2-Х28^|х|^ <Z2X27(1—2~”). Что касается числа нуль, то его можно, вообще говоря, изображать в одной из нескольких форм, например: (а) М =0, р — произвольное;
4.3] ХАРАКТЕРИСТИКА РЕАЛЬНЫХ ЭВМ 221 (б) М — произвольное, р — минимальный допустимый порядок (р=—127); (в) А1=0, р — минимальный допустимый порядок; (г) Л4=0, р=0. В большинстве ЭВМ для изображения нуля принимается послед* няя из этих форм. Рассмотрим методику выполнения арифметических операций над числами с плавающей точкой х=Х-2р и #=У-2<* (х=#0, #=/=0). При сложении чисел, представленных в форме с плавающей точ- кой, в общем случае нельзя непосредственно складывать их ман* тиссы, так как если слагаемые имеют разные порядки, то фактиче* ское положение точки в изображении этих чисел будет различным, и поэтому разряды мантисс с одинаковыми номерами будут на са* мом деле изображать разные разряды чисел. Например, в десятич* нои системе счисления (где условием нормализации является соот* ношение 0.1^|Л4|<1) число х=295, представленное как нормализо- ванное число с плавающей точкой, будет иметь вид х=0.295-103, а число #=39 будет иметь вид #=0.39 • 102. Как видно, старший раз- ряд мантиссы в числе х представляет число сотен, а в числе у — чис- ло десятков. Поэтому для сложения этих чисел слагаемое, имеющее меньший порядок, т. е. число #, надо предварительно преобразовать к виду #=0.039-103, после чего можно производить сложение ман- тисс: z=x+#=0.334 • 103=334. Точно так же и в двоичной системе счисления при сложении чисел надо предварительно «выравнять порядки» — слагаемому с мень- шим порядком надо приписать порядок другого слагаемого, а чтобы величина числа не изменилась, его мантиссу надо разделить на соответствующую степень двойки, что обеспечивается сдвигом ман- тиссы вправо на число рязрядов, равное модулю разности порядков слагаемых. Таким образом, сложение и вычитание чисел с плавающей точ- кой должно производиться в соответствии с формулой: z=x±y=X-2P±Y .2«=(Х-2-^-^±У где r=max (#, #). Поскольку при таком выборе значения г одна из разностей порядков г—р или г—q обязательно равна нулю, то полу- чим следующие формулы для сложения и вычитания чисел с плава- ' ющей точкой: ( (X ±Y-2-{р~<Р)-2р при p>q, г^х±у= | (Х.2-<?-г>±Г).2« при p^q. (4,2> В этих формулах умножение мантиссы одного из аргументов (име- ющего меньший порядок) на множитель или 2-<«-*” как раз и служит для выравнивания порядков аргументов. Значение выражений, взятых в скобки, принимается за мантиссу Z результата, а величина r=max (р, q) — за его порядок. Мантисса Z, полученная по формулам (4.2), может оказаться по модулю боль-
222 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 те единицы (ио она будет заведомо меньше двух). Поэтому в сум- маторе необходимо предусмотреть один дополнительный разряд для изображения целой части числа, и если в этом разряде оказалась единица, то полученное число необходимо нормализовать «вправо» — сдвинуть мантиссу на одну позицию вправо и увеличить полученное значение г на единицу. При фактическом вычитании близких чисел в старших разрядах полученной мантиссы Z могут оказаться ну- ли — в этом случае необходимо произвести нормализацию «влево». Кроме того, полученный результат должен быть соответствующим образом округлен. Умножение выполняется по более простой формуле: г=х.1/=(Х.Г).2^+» (4.3) Значение выражения Х-У принимается за мантиссу, а значение p+q — за порядок результата. В случае необходимости произво- дится нормализация результата (если оба сомножителя были нор- мализованы, то может потребоваться нормализация «влево», но не более, чем на одну позицию), а также производится его округление. Деление реализуется по формуле z=x/y=(X/Y)-2P~i, (4.4) где значение X/Y принимается за мантиссу, а значение р—q — за порядок результата, после чего производится округление и норма- лизация результата. Каждый из рассмотренных выше способов представления чисел в машине имеет свои достоинства и недостатки. Главное неудобство представления чисел с фиксированной точ- кой — это весьма ограниченный диапазон чисел, непосредственно представимых в машине. Это обстоятельство вызывает большие трудности при решении многих задач и заставляет прибегать к спе- циальным методам подготовки задач к их решению на ЭВМ. В слу- чае же плавающей точки диапазон представимых чисел обычно вполне достаточен для решения подавляющего большинства прак- тических задач. Кроме того, в форме с фиксированной точкой все числа представляются с одинаковой абсолютной погрешностью Дх, вследствие чего малые по модулю числа представляются с малым ко- личеством значащих цифр и потому имеют большую относительную погрешность 6х=Дх/х. Между тем для получения достаточной точ- ности окончательных результатов важно, чтобы все промежуточные результаты имели одинаковую относительную погрешность, что ' обеспечивается при представлении чисел с плавающей точкой. С другой стороны, методика выполнения арифметических опе- раций над числами с плавающей точкой более сложна по сравнению с фиксированной точкой. Как видно из приведенных выше формул, при выполнении какой-либо операции приходится выполнять раз- ные действия над мантиссами и порядками аргументов. Поэтому арифметическое устройство ЭВМ в этом случае получается более сложным. Кроме усложнения АУ, плавающая точка ведет к увели-
4.3] . ХАРАКТЕРИСТИКА РЕАЛЬНЫХ ЭВМ 223 чению времени, затрачиваемого машиной на выполнение таких one-, раций, т. е. к снижению фактического быстродействия ЭВМ. В связи с этим выбор формы представления чисел определяется тем кругом задач, на решение которых прежде всего ориентируется данная ЭВМ. Следует обратить внимание на то, что из-за ограниченного ко- личества разрядов, отводимых на изображение любого числа, ве- щественные числа представляются в машине, вообще говоря, не- точно в любой из рассмотренных выше форм. По этой причине и арифметические операции над такими числами выполняются ма- шиной в общем случае неточно, поскольку получаемый результат приходится округлять, оставляя в нем лишь определенное коли- чество разрядов в цифровой части. Эта специфика ЭВМ часто по- рождает большие трудности при решении практических задач, ибо для многих, из них ошибки округления, возникающие при выполнении машиной арифметических операций, могут сущест- венно влиять на точность окончательных результатов, а оценка влияния этих ошибок представляет собой довольно сложную про- блему. И даже в весьма простых задачах недостаточно вниматель- ный учет этого фактора нередко приводит к тому, что выбранный алгоритм при его машинной реализации оказывается некорректным и может привести к неправильным результатам. Например, сум- мирование значений функции f(x), вычисляемых в узлах сетки, которая получается разбиением отрезка [а, 6] на п равных частей (включая и концы отрезка), можно задать следующим алгоритмом, записанным на алголе: 6:=(6—а)/я; з:=0; for х:=а step h until b do s:=s+f(x) Однако при машинной реализации этот алгоритм оказывается некорректным. Действительно, если а=0, 6=1 и я=10, то значение 6=0.1, которое в двоичной системе не может быть представлено в виде конечной дроби, заведомо будет представлено в машине не- точно — с избытком или с недостатком (даже в том случае, если значения а, b и п были представлены в виде машинных слов точно). А поскольку и операция сложения при выполнении действия х: = x+h также выполняется приближенно, то после я-кратного при- бавления h к значению а получится число, вообще говоря, отличное от значения Ь, и если это число окажется несколько больше, чем Ь, то значение f(b) не будет учтено в получаемой сумме. Подобного рода некорректности особенно опасны тем, что в некоторых случаях они могут и не сказываться на получаемых результатах, что за- трудняет их обнаружение. Что касается целых чисел, то их можно рассматривать как част- ный случай вещественных чисел и не предусматривать какого-то особого способа их представления в машине. Однако целочисленные
224 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 значения часто используются для управления числом повторений циклов, в качестве значений индексов и т. п., когда важно иметь точное представление значений. Как мы увидим дальше, такие числа часто используются для управления переменными адресами в ко- мандах, а для этих целей и форма представления целых чисел с плавающей точкой весьма неудобна. Поэтому на ряде машин пре- дусматривается специально целочисленная арифметика, т. е. пре- дусматривается определенный способ представления в машине целых чисел (который может отличаться от способа представления вещественных чисел), а в наборе машинных операций — наряду с арифметическими операциями над вещественными числами — пре- дусматриваются отдельные арифметические операции над целыми числами, которые обеспечивают точное представление получаемого результата в форме, принятой для целых чисел. Если на машине специальная целочисленная арифметика не предусмотрена, то ее иногда приходится вводить программным путем: выбирать тот или иной способ представления целых чисел, а для выполнения арифметических операций над такими числами использовать под- ходящие для этой цели имеющиеся машинные операции. Использование двоичной системы счисления для представления чисел в машине влечет за собой определенные неудобства и потерю эффективности использования ЭВМ. Ведь исходные данные зада- ются, как правило, в десятичной системе, поэтому их приходится предварительно переводить в двоичную систему — этот перевод обычно делается с помощью машины по соответствующей программе. Окончательные результаты, получаемые в машине также в двоичной системе, для их последующего использования человеком прихо- дится переводить в десятичную систему (что также делается, как правило, с помощью машины). Между тем имеется немало задач {например, задачи экономического характера), специфика которых состоит в больших объемах исходных данных и (или) окончательных результатов при сравнительно небольшом числе арифметических операций, которые приходится выполнять в процессе решения задачи. Ясно, что в этом случае затраты машинного времени на переводы чисел могут даже превосходить время, затрачиваемое на непосредственное решение задачи. Поэтому на некоторых ма- шинах, ориентированных главным образом на решение таких задач, предусматривается аппаратное выполнение арифметических опе- раций над числами, представленными в десятичной системе счис- ления (точнее, в смешанной двоично-десятичной системе, в которой каждая десятичная цифра изображается четырехразрядным дво- ичным числом). Конечно, в этом случае несколько усложняется аппаратура и арифметические операции выполняются, возможно, медленнее, однако эти потери вполне компенсируются устранением необходимости перевода больших количеств чисел из одной си- стемы счисления в другую.
4.3J ' . ХАРАКТЕРИСТИКА РЕАЛЬНЫХ ЭВМ 225. 4.3.3. Характеристика набора машинных операций. Различные типы ЭВМ могут существенно отличаться друг от друга своими наборами операций. Тем не менее в этот набор на каждой машине входят, как правило, следующие типы операций. Арифметические операции. Эта группа операций предназна-, чеиа для действий над числами. В нее обычно входят четыре ос* новные арифметические операции. На некоторых машинах в эту группу входят и более сложные операции, например, извлечение квадратного корня, а на некоторых машинах нет даже операции деления. В эту группу могут быть включены операции, позволя- ющие проще и быстрее выполнить ту или иную арифметическую операцию при некоторых частных значениях аргументов. Напри- ' мер, умножение числа с плавающей запятой х=Х-2р на число вида 2* (k — целое) сводится просто к добавлению к порядку р целого k, поэтому в машине могут быть предусмотрены операции, предназначенные для действий над порядком чисел. В эту группу могут входить и такие часто встречающиеся действия, как вычи- тание абсолютных величин чисел, присвоение одному числу знака другого числа и т. д., позволяющие сократить число команд в про- грамме. Поразрядные (логические) операции. При выполнении каждой из операций этой группы любое машинное слово, являющееся операндом, трактуется не как число, а как упорядоченная после- довательность битов (двоичных разрядов), представляющих с помощью цифр 1 и 0 логические значения «истина» и «ложь». При этом заданная логическая операция выполняется над всеми парами битов с одинаковыми порядковыми номерами в словах, являющихся аргументами данной операции. В эту группу обычно входят основные логические операции (опе- рации отрицания, логического умножения и логического сложе- ния), через которые можно достаточно просто выразить любую другую логическую операцию. Довольно часто вместо операции отрицания в эту группу включают операцию установления неэк- вивалентности, которая оказывается весьма полезной для различ- ных целей. Помимо основных, в эту группу могут входить и другие опе- рации логического типа, позволяющие более удобно осуществить различные преобразования машинных слов, трактуемых как ло- гические векторы. Специальные операции над кодами. Операции этой группы пред- назначены для переработки машинных слов по самым различным правилам, причем в разных операциях этой группы машинные слова могут иметь различную трактовку. Например, в операциях сдвига слово (или часть его) тракту- ется просто как упорядоченный набор двоичных цифр (двоичный код), который сдвигается вправо или влево: величина и направ- ® 3. ЛюбдмскиЯ а др.
226 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ (Гл. 4 ление сдвига задаются другим аргументом. При этом сдвиг может быть логическим, при котором двоичные цифры, выходящие в результате сдвига за пределы машинного слова, теряются, а в освободившиеся разряды с другого конца слова записываются нули; сдвиг может быть циклическим, при котором очеред- ная двоичная цифра, вышедшая за пределы слова, записывается в очередной освободившийся разряд с другого конца слова; сдвиг может быть и арифметическим, при котором разряд, представляющий знак числа, в сдвиге не участвует, а сдвигаются только разряды, в которых представляется цифровая часть числа — в случае представления чисел с фиксированной точкой такой сдвиг может использоваться для умножения или деления числа на число вида 2*, где k — целое. В некоторых операциях этой группы каждое машинное слово (или его часть) трактуется как целое неотрицательное число, и по данным операциям выполняется сложение или вычитание таких чисел. Подобного рода операции могут быть использованы, напри- мер, для реализации целочисленной арифметики. Отдельные операции этой группы специально предназначаются для преобразования команд — эти операции были особенно важны, на первых ЭВМ, в которых еще не было иных достаточно удобных средств модификации команд. Следует подчеркнуть, что любая операция из всех рассмотрен- ных выше трех групп может быть применена к л ю б ы м машинным словам независимо от того, что на самом деле представляет собой то или иное слово (число, команду, логический вектор и т. д.). В этом отношении машина фон-Неймана принципиально отличается от алгола. В алголе, например, нельзя писать выражение а+д, если, скажем, переменная b имеет тип boolean, или оператор при- сваивания х:=у, если х имеет тип real или integer, а у имеет тип boolean (или наоборот). В машине фон-Неймана данные не имеют типов — интерпрета- ция каждого машинного слова зависит от того, где и как это слово используется в процессе выполнения программы, так что даже одно и то же слово в разные моменты времени может интерпретироваться машиной по-разному. С одной стороны, это обстоятельство обеспечивает большую свободу при выборе преобразований, которым могут подвергаться машинные слова. С другой стороны, это обстоятельство вызывает и некоторые неудобства — например, из-за этого усложняется обнаружение и поиск ошибок в программе. В связи с этим на не- которых машинах данные, представляемые в виде машинных слов, снабжаются признаком типа: для указания этого типа в машинном слове отводится определенная группа разрядов (поле типа), назы- ваемая тэг, а при каждом использовании машинного слова пре- дусматривается аппаратная проверка на соответствие типа слова
4.3) ХАРАКТЕРИСТИКА РЕАЛЬНЫХ ЭВМ 227 данному его использованию. Наличие тэгов открывает и рад до- полнительных возможностей. В частности, это дает возможность возложить на аппаратуру выполнение ряда действий, которые так или иначе связаны с анализом типов данных, вместо того, чтобы предусматривать эти действия в программе. Операции управления. Операции этой группы не вырабатывают каких-либо новых машинных слов-результатов, а служат для управления порядком выполнения команд. В эту группу входят, например, операции условных переходов, которые в зависимости от свойств очередного полученного результата выбирают для ис- полнения в следующем такте одну из двух возможных команд. С помощью этих операций можно производить разветвления вы- числительного процесса по ходу вычислений, организовывать циклы в программе и т. д. В эту же группу входит операция «оста- нова», выполнение которой означает прекращение выполнения ма- шиной данной программы. Операции ввода {вывода. Эти операции служат для ввода в память машины данных (самой программы или отдельных ее частей, ис- ходных данных, подлежащих переработке по этой программе, и т. д.) с внешних носителей, а также для вывода результатов, полученных в памяти машины, на те или иные внешние носители для их дальнейшего использования. Наряду с этими типовыми группами операций в набор машин- ных операций каждой конкретной ЭВМ могут входить операции, связанные со спецификой либо самой машины, либо тех задач, на решение которых она ориентирована. Обычно набор машинных операций весьма обширен и насчитывает примерно сотню различных операций. 4.3.4. Форматы команд. Особенно сильно различные типы ЭВМ отличаются друг от друга системами команд, т. е. форматами команд и их содержательным смыслом. Эти отличия проявляются и в числе разрядов, образующих то или иное поле в команде, и в назначении информации, задава- емой в каждом из этих полей, и в количестве самих полей, и даже в числе форматов команд, имеющихся на данной ЭВМ. Специфи- ческие особенности ЭВМ, связанные с различием в системе команд, особенно сильно сказываются на непосредственном написании программы, поскольку команды для разных машин даже внешне могут существенно отличаться друг от друга, не говоря уже о том, что отличия в содержании команд влекут за собой и определенную специфику в программирование. Следует, однако, подчеркнуть, что эта специфика носит не принципиальный, а скорее технический характер. В следующих параграфах мы остановимся на этих вопросах более подробно. в*
228 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 4.4. Разнообразие систем команд ЭВМ. Примеры программ Теперь мы рассмотрим типичные системы команд ЭВМ и приве- дем примеры простейших программ для этих машин. При этом мы рассмотрим несколько различных машин, имеющих различные системы команд, чтобы, с одной стороны, проиллюстрировать раз- личие машинных языков и особенности программирования на каждом из них, а с другой стороны, показать, что эти различия не являются принципиальными и что основные правила и приемы программирования являются общими для всех машин. Чтобы более наглядно проиллюстрировать и то общее, что имеет место при про- граммировании для любой машины, и те различия, которые связаны со спецификой каждой из них, мы будем составлять программы для одних и тех же примеров, но для разных машин. Чтобы избежать рассмотрения слишком специфичных свойств, которые обычно встречаются в каждой реальной ЭВМ, мы будем рассматривать учебные машины (УМ), которые реально не существуют, но которые отражают наиболее характерные черты существующих ЭВМ раз- личных типов. В частности, весьма специфичными для каждой машины являются вопросы ввода и вывода. Поэтому мы будем исходить из того, что к началу выполнения машиной каждой из программ и сама программа, и все необходимые исходные данные уже находятся в нужных ячейках памяти. При этом программа должна быть составлена так, чтобы в результате ее выполнения иско- мые результаты были помещены в определенные ячейки памяти, после чего выполнение этой программы машиной должно быть прекращено. При рассмотрении каждой новой учебной машины мы будем давать описание тех ее характеристик, знание которых необходимо для составления программы рассматриваемых примеров. При этом - мы будем рассматривать только двоичные машины, у которых В каждой позиции машинного слова может содержаться одна из цифр 0 или 1. 4.4.1. Четырехадресная машина (УМ-4). Память состоит из 512 ячеек, перенумерованных от 0 до 511. Поскольку 512=2’, то чтобы иметь возможность в каждом поле адреса команды указывать номер любой ячейки, эти поля должны содержать по 9 двоичных позиций. Таким образом, каждый адрес — это 9-значное целое неотрицательное двоичное число. При записи программ на бумаге ради краткости и наглядности записи каждый адрес будем записывать в восьмеричной системе счисления, объ- единяя двоичные цифры (слева направо) в группы по три и записывая каждую тройку двоичных цифр одной восьмеричной цифрой. На- пример адрес, двоичная запись которого 000101111, имеет восьме- ричную запись 057. Таким образом, в восьмеричной записи адреса ячеек лежат в диапазоне от 000 до 777 включительно.
(4.4 РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 229 Машинное слово состоит из 45 двоичных позиций. При записи машинного слова на бумаге также будем использовать восьмерич- ную систему счисления, так что каждое машинное слово записы- вается в виде последовательности из 15 восьмеричных цифр, на- пример 301 052 677 005 440. Представление чисел — в виде нормализованных чисел с плава- ющей точкой — так, как это было описано в разделе 4.3. Целые числа представляются так же, как и вещественные. Примеры за- писи чисел в виде машинных слов: \ = \/2.21-. 001 400 000 000 000 3/16 = 3/4-2-а: 202 300 000 000 000 7 15 оз. ' 2 ~ 16 * 403 740 000 000 000 Система команд — четырехадресная (отсюда и название машины УМ-4). Формат команды: 0 Л1 А2 АЗ А4 9 р. 9 р. 9 р. 9 р. 9 р. где 0 — код операции, А1 — адрес первого аргумента, Л2 — адрес второго аргумента, АЗ — адрес результата, А4 — адрес следующей команды, подлежащей выполнению. Поле операции и каждое из полей адресов содержат по 9 двоичных позиций. Код операции, как и каждый из адресов, будем записывать в виде тройки вось- меричных цифр. Введем в употребление некоторые машинные операции арифме- \ тического типа над вещественными числами. При их описании через al, a2 и аЗ будем обозначать соответственно первый, второй и •тре- тий операнды операции (для хранения которых отведены ячейки с адресами Л1, Л2 и ЛЗ, указанными в команде, т. е. al=O/7L41R а2=ОП[А2] и аЗ=ОП[АЗ\). Для каждой операции будем Также указывать логическое выражение, значение которого присваива- ется переменной <о (признак <»). (См. табл. 4.1.) Операция останова имеет код 077. Адреса, указанные в команде с 0=077, не используются при выполнении этой операции и поэтому могут быть произвольными (во всех подобного рода случаях усло- вимся записывать нулевой адрес, т. е. адрес 000). Пример команды для УМ-4: 017: 001 400 001 057 012 При записи программы на бумаге слева от каждой команды обычно записывается ее адрес (в данном случае 017), который играет роль метки этой команды и служит для ссылок на нее в других Командах.
230 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 Таблица 4.1 Название операции е Содержание to Сложение 001 аЗ: —а! + д2 аЗ <0 Вычитание 002 аЗ: = а1 — а2 аЗ < 0 Умножение 005 аЗ:—а\ Ха2 |аЗ|< 1 Деление 004 аЗ: =al/a2 |аЗ|< 1 При записи отдельных команд адрес команды будем отделять от самой команды двоеточием. Чтобы эта команда была выполнена машиной на каком-либо такте ее работы, к началу этого такта в регистре С устройства управления должен быть зафиксирован адрес 017. Далее машина выполняет очередной такт своей работы в соответствии с приведен- ной ранее схемой (заметим, что данное ранее формальное описание функционирования машины было как раз ориентировано на четы- рехадресную ЭВМ); при описании отдельных этапов такта мы будем использовать здесь восьмеричную запись адресов: Г. К'.=0/710171; (в регистр команды К выбирается команда с адресом 017, который зафиксирован в регистре С). 2°. С: =012; (в регистр С пересылается адрес 012, указанный в поле четвертого адреса выполняемой команды). 3°. R1:=0/714001; (в регистр /?1 выбирается первый аргумент, адрес которого 400 указан в поле первого адреса команды). 4°. /?2:=0/71001]; (в регистр /?2 выбирается второй аргумент из ячейки 001, указанной в поле второго адреса команды). 5°. S:=/?l+/?2; 6°. <o:=S<O; <p:=a&s(S)>Af. 7°. 0/71057]:=3; (результат, полученный в сумматоре, запоми- нается в ячейке памяти с адресом 057, указанным в поле третьего адреса команды). На этом такт работы машины заканчивается и начинается новый такт, который выполняется аналогичным образом. Поскольку теперь в регистре С зафиксирован адрес 012, то на этом новом такте будет выполняться команда, выбираемая из ячейки 012. Каждая программа состоит из последовательности команд, как правило, достаточно длинной. Для облегчения составления программы и дальнейшей работы с нею, каждая команда обычно записывается на отдельной строчке листа бумаги или специального бланка. При этом отдельные команды удобно записывать в том порядке, в котором они должны выполняться машиной, если только это возможно. Последовательно выполняемые команды естественно
4.4] , РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 231 и в памяти размещать в ячейках с последовательными адресами — этого правила мы будем обычно придерживаться во всех программах. Пример 4.1. Составить программу, реализующую оператор присваивания y:=d—(a+b) f 2 Поскольку каждая команда программы формулируется в терминах адресов ее операндов, то при составлении программы необходимо предварительно произвести распределение памяти, т. е. определить, в каких ячейках памяти будут храниться как все значения, участвующие в вычислениях, так и команды программы. При размещении в памяти программы надо учитывать, что за- пуск машины в режим автоматического выполнения программы (после ввода в память машины программы и исходных данных) производится путем нажатия специальной кнопки ПУСК на пульте управления машиной. Условимся, что на машине УМ-4 при на- жатии этой кнопки в регистр С автоматически заносится фикси- рованный адрес 011, так что на первом такте своей работы машина всегда выбирает команду из ячейки ОН. Следовательно, учитывая наше соглашение о размещении команд в памяти, любая программа должна размещаться в ячейках памяти с последовательными но- мерами, начиная с 011. В дополнение к соглашению о размещении в памяти программы примем следующее распределение памяти для нашего примера: адр.а=107, адр.Ь=110, adp.d—ill, адр.у=5ОЗ, т. е. а=0/7[107], &=0/7[110], d=0/7[lllI, ^=0/7(503]. Теперь перейдем к составлению программы. Напомним, что решение на ЭВМ любой задачи должно быть сведено к выполнению некоторой последовательности машинных операций. Решение рас- сматриваемой нами задачи, очевидно, нельзя получить выполнением только одной машинной операции, так что и в этой простейшей задаче придется спланировать нужную последовательность этих операций. Учитывая старшинство арифметических операций и расставлен- ные скобки, сначала надо вычислить значение а+b, для чего можно воспользоваться машинной операцией сложения. Этот этап вычис- лений зададим первой командой, имеющей следующее содержание: 1°. rl-.=a+b, где через г1 обозначен промежуточный результат вычислений. Поскольку результат операции при выполнении каждой команды отсылается на запоминание в память, то для каждого промежуточ- ного результата тоже надо отвести определенную ячейку. Ячейки памяти, отводимые для хранения промежуточных результатов, принято называть рабочими ячейками.
232 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 Мы условимся для размещения промежуточных результатов прежде всего использовать ячейки с 001 по 010 (ячейка с номером ООО в большинстве машин является особой — при чтении из нее всегда выдается слово, содержащее нули во всех позициях). Содер- жимое этих рабочих ячеек будем обозначать соответственно через rl, г2 и т. д. Теперь полученное значение rl надо возвести в квадрат. Опе- рации возведения в степень в наборе машинных операций нет, но в данном случае можно использовать машинную операцию умноже- ния. Этот этап вычислений зададим второй (по порядку выполнения) командой следующего содержания: 2°. г2:=г1хг1. В результате выполнения этого этапа будет получено значение г2=(а+6)*, где г2 — новый промежуточный результат. Дальше для получения значения у нужно вычислить разность d—г2. Используя машинную операцию вычитания, этот этап вы- числений зададим третьей командой с содержанием: 3°. y:=d—г2. В результате выполнения этих трех команд решение постав- ленной задачи будет закончено, после чего надо предусмотреть прекращение выполнения машиной нашей программы, для чего в качестве четвертой команды предусмотрим команду останова: 4°. Стоп. Итак, мы свели решение поставленной задачи к последователь- ности четырех машинных операций, определили назначение каж- дой команды и порядок, в котором эти команды должны быть вы- полнены. Теперь остается фактически записать эти команды, обес- печив нужный порядок их выполнения машиной и приняв во вни- мание сделанное распределение памяти. В первой команде код операции сложения 0=001, адрес первого аргумента адр.а=107, адрес второго аргумента адр.Ь=110, адрес результата адр.г\ =001 и, наконец, адрес следующей команды есть 012. Таким образом, первая команда запишется так: 011: 001 107 ПО 001 012 Аналогичным образом записываются и остальные команды. В итоге мы получим следующую программу решения поставленной задачи: 011 012 013 014 001 005 002 077 107 110 001 012 001 001 002 013 111 002 503 014 000 000 000 000 rl: = a + 6 г2: = г1хг1 (г2 = (а+Ь)2) у : = d — r2 (y=d—(a + b)t) Стоп Читателю предлагается программы машиной. проследить по тактам выполнение этой
4.41 РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ ' 233, Пример 4.2. Найти объем шарового сегмента V=пН* (R—H/3), где R — радиус шара, Н — высота сегмента. На алголе эти вычисления можно задать в виде оператора при* сваивания V:=PIXH f 2Х(/?—Я/3). В данной задаче помимо переменных величин Н и /?, значения которых являются исходными данными, фигурируют и постоянные числовые значения: 3ил«3.14л:3^. Так как любые аргументы машинных операций арифметического типа при выполнении про- граммы выбираются из памяти (независимо от того, являются эти аргументы постоянными или переменными), эти числа также должны быть представлены в виде машинных слов и помещены в определен- ных ячейках памяти. Поскольку речь идет о постоянных числовых значениях, то было бы нелогично задавать их и вводить в память наряду с исходными данными R и Н. Для удобства пользования программой эти числа лучше запасти в самой программе в качестве ее констант, чтобы при вводе программы в память машины эти кон- станты вводились вместе с нею. Каждую константу можно, напри- мер, размещать в памяти вслед за той командой, в которой эта константа используется впервые. Как и в примере 4.1, сначала надо спланировать последователь- ность машинных операций, выполнение которых приведет к инте- ресующему -нас результату. Эта последовательность действий может быть следующей: 1°. Н:=ЯхЯ 2°. г2:=лХг1 (г2=л№) 3°. гЗ:=Я/3 4°. г4:=Я—гЗ (г4=Я—Я/3) 5°. У:=г2Хг4 6°. Стоп Если принять распределение памяти: адр.Я=200, <ф.Я=201, оф.У=300, то получим следующую программу (с учетом наших соглашений о размещении в памяти констант и промежуточных результатов): ОН 005 200 200 001 012 rl=H* 012 005 013 001 002 014 г2 = пН' 013 002 622 000 000 000 Константа л«3’/,4 014 004 200 015 003 016 гЗ = Н1Ъ 015 002 600 000 000 000 Константа 3 016 002 201 003 004 017 г4 = Я-Я/3 017 005 002 004 300 020 V 020 077 000 000 000 000 Стоп Теперь видно, что размещение констант вперемежку с коман- дами ухудшает наглядность программы и затрудняет ее понимание, поэтому разумнее все константы собрать в единую группу и раз-
234 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ (Гл. 4 местить ее в конце программы (после команды останова) — этого правила мы и будем придерживаться в дальнейшем. Кроме того, в приведенной программе' для каждого нового промежуточного результата отводилась своя ячейка памяти. Од- нако это вовсе не обязательно. Например, значение №, полученное в ячейке 001 по команде ОН, используется только в команде 012 при вычислении л Н2 — в последующих командах это содержимое ячейки 001 больше не используется. Поэтому ячейку 001 (после того, как из нее будет прочитано значение № для вычисления лН2) можно использовать для хранения других промежуточных резуль- татов. В связи с этим следует лишь еще раз напомнить о том, что если в какую-либо ячейку что-то записывается, то в момент записи предыдущее содержимое этой ячейки безвозвратно теряется (за- бывается). С учетом приведенных выше соображений можно составить и такой вариант программы для решения поставленной задачи: 011 005 200 200 001 012 012 005 017 001 001 013 013 004 200 020 002 014 014 002 201 002 002 015 015 005 001 002 300 016 016 077 000 000 000 000 017 002 622 000 000 000 020 002 600 000 000 000 +. = НхН г1: = лхг1 (г!=лН2) г2: — Н/3 r2: — R — r2 (r2 = R — H/3) V:=--rlxr2 Стоп Константа л « 3®/в4 Константа 3 Пример 4.3. Составить программу для вычислений, задава- емых оператором присваивания, правая часть которого содержит условие: t/:=if a<2b then (a+b) f 2else (b—a)/2 Если в каждом из предыдущих примеров решение поставленной задачи сводилось к выполнению одной и той же последовательности команд в программе при любых значениях ис- ходных данных, то в данном примере это не так, что особенно наглядно видно из блок-схемы этих вычислений (рис. 4.3). Действительно, при а<С.Ь значение у долж- но вычисляться по формуле (а+b)2, а при а^Ь — по формуле (Ь—а)/2. Однако конкретные значе- ния а и b при составлении программы еще не- известны — они будут заданы в качестве ис- ходных данных только к началу выполнения Рис. 4.3. программы машиной. В связи с этим в программе необходимо предусмотреть как случай а<.Ь, так и случай d^b. Следовательно, в программе должна содержаться как группа команд для вычислений по формуле у=(а+Ь)2, так и группа ' команд для вычислений по формуле у=(Ь—а)[2. Выполняться же при
V’ РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 235/ каждых конкретных значениях а и b должна только одна из этих групп. Поэтому в программу должны входить еще и такие команды, назначение которых состоит в том, чтобы проверить заданное ус- ловие и в зависимости от его выполнения осуществить переход либо к одной, либо к другой группе команд, предназначенных для вычисления у. Для разветвления вычислительного процесса по ходу выпол- нения программы в наборе машинных операций предусматриваются специальные операции «условного перехода», которые в зависи- мости от текущего значения признака <в обеспечивают выбор для исполнения на следующем такте либо одной, либо другой команды. Такой операцией в УМ-4 является операция 036. Поскольку ко- манда, выбираемая для исполнения на каждом очередном такте, определяется содержимым регистра С, полученным на предыдущем такте, то основное назначение команды с операцией 036 состоит в том, чтобы занести в регистр С один из двух адресов, с которых в программе начинаются соответствующие ветви вычислений, в зависимости от текущего значения <о. Правило, по которому формируется содержимое регистра С в результате выполнения команды 036 Al А2 АЗ А4 можно сформулировать на алголе следующим образом: C:=if <в then А2 else А4 т. е. в регистр С заносится адрес А2 при co=true, и адрес А4 при w=false. Адреса А1 и АЗ оказываются свободными, поэтому операцию условного перехода можно совместить с какой-либо операцией, использующей только два операнда, например с пересылкой слова из ячейки с адресом А1 в ячейку с адресом АЗ. Таким образом, команда с кодом операции 036 в УМ-4 имеет следующий смысл: аЗ:—а1‘, С:=if со then А2 else А4 Значение <в при выполнении операции 036 не изменяется. Такт работы машины при выполнении команды с операцией 0=036 несколько отличается от рассмотренного ранее типового такта, а именно — этапы 4°, 5° и 6° в данном случае имеют следующий смысл: 4°. if со then С:=А2(К) (т. е. вместо выборки в регистр R2 слова из ячейки с адресом А2, здесь на этом этапе производится коррек- тировка содержимого регистра С: если co=true, то в этот регистр заносится адрес А2, указанный в команде; если же ©«false, то это действие не производится и в регистре С сохраняется адрес А 2, занесенный в него на этапе 2°). 5°. S:=7?l (работа арифметического устройства сводится к пересылке слова из регистра 7?1 в сумматор S).
236 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ (Гл. . Этап 6° при выполнении этой операции является «пустым»,I т. е. новые значения <о и <р не вырабатываются. Остальные этапы имеют тот же смысл, что и в типовом такте. При составлении программы для рассматриваемого примера примем следующее распределение памяти: вдр.а=200, адр.Ь=201, адр.у=2О2. Как видно из блок-схемы, выполнение программы должно на- чинаться с разветвления вычислительного процесса, но прежде чем использовать команду условного перехода (с кодом операции 036), надо обеспечить выработку значения со в соответствии с за- данным условием. Для этого надо выполнить подходящую команду (или группу команд). В нашем случае такой командой может быть команда для вычисления разности г=а—й, поскольку при ее вы- полнении будет выработано значение <o=trup при а—b<zO (т. е. при a<Zb), и значение wsfalse в противном случае. Вслед за этой командой можно выполнять команду условного перехода на ту или иную ветвь, используя выработанное значение ®. Отдельные группы команд разместим в памяти в соответствии с приведенной блок-схемой, так что группу команд для вычисления z/=(a-f-b)2 разместим вслед за первыми двумя командами, реализу- ющими проверку условия — эти команды запишутся так: 011: 002 200 201 001 012; rl:=a—Ь\ <л:=а<Ь 012: 036 000 013 000 ; условный переход по со В последней команде в качестве А4 надо указать адрес команды, с которой начинается вычисление у~(Ь—а)/2, но в какой ячейке будет размещена эта команда, мы пока еще не знаем, поэтому чет- вертое поле адреса в команде 012 придется заполнить позже. С ячейки 013 разместим группу команд для вычисления у—(а+Ь)*'. .013: 001 200 201 001 014; rl:=a-H> 014: 005 001 001 202 ; у=(а+Ь)' Поскольку значение (а—Ь), полученное в ячейке 001 по команде 011, дальше нигде не используется, то эту ячейку мы использовали для хранения нового промежуточного результата (а+Ь). Согласно нашей блок-схеме дальше (начиная с ячейки 015) надо разместить группу команд для вычисления у—(Ь—а)/2, но эта группа команд не должна выполняться, если выполнялись по- следние две команды (013 и 014): вслед за командой 014 должна быть выполнена команда останова — ее адрес и должен быть ука- зан в качестве А4 в команде 014. Но поскольку место в памяти команды останова еще неизвестно, то четвертое поле адреса в ко- манде 014 тоже приходится временно оставить пустым. С ячейки 015 разместим группу команд для вычисления у=> =(Ь—а)/2 — именно на эту группу команд и надо перейти по ко- манде 012, если к моменту ее выполнения признаку в» было при-
РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ ' 237. своено значение false, так что теперь можно заполнить четвертое поле адреса в команде 012: 012: 036 000 013 000 015; С:=if со then 013 else 015 Теперь напишем команды для вычисления у=(Ь—а)/2: 015: 002 201 200 001 016; гк=Ь—а 016: 004 001 202 ; у=(Ь—а)/2 В последней команде в качестве Л2 надо указать адрес константы, представляющей число 2, а в качестве А4 — адрес команды оста- нова; эти адреса тоже пока неизвестны, поэтому их придется за- писать в этой команде позже. В следующей по порядку ячейке поместим команду останова: 017: 077 000 000 000 000; Стоп К выполнению этой команды и надо перейти после вычисления у по каждой-из формул, так что адрес 017 надо записать в качестве А4 в каждой из команд 014 и 016: 014: 005 001 001 202 017; у=(а+Ь)* 016: 004 001 202 017; у=(Ь—а)/2 Вслед за командой останова разместим константу, представляющую число 2: 020: 002 400 000 000 000; константа (число 2) Адрес этой константы (020) надо указать в качестве А2 в команде 016: 016: 004 001 020 202 017; у=(Ь—а)/2 Окончательно наша программа примет вид: 011 002 200 012 036 000 013 014 015 016 017 020 001 005 002 004 077 002 200 001 201 001 000 400 201 013 201 001 200 020 000 000 001 000 001 202 001 012 015 014 017 016 rl: = a—b; со: — а<Ь Переход к 013 при со true и к 015 при со false г1: = а-(-6 у = (а +1>)*; переход к стоп г\: = Ь—а 202 017 у = (Ь—а)/2; переход к стоп 000 000 Стоп 000 000 Константа (число 2) Следует подчеркнуть, что если в команде 014 не предусмотреть обход следующей за ней группы команд, предназначенных для вычисления у—(Ь—а)/2, т. е. записать эту команду в виде 014: 005 001 001 202 015 то по такой программе окончательное значение у всегда . будет получаться по формуле у=(Ь-—а){2, независимо от значений а и Ь,
238 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл ) I поскольку выполнение команд 015 и 016 будет аннулировать ре» зультат, полученный в ячейке 202 по командам 013 и 014. Читателю предлагается проследить — команда за командой — выполнение составленной программы для каждого из трех вариан- тов значений исходных данных: (а=1, Ь=2), (а=0, Ь=—3), (а=1, Ь=1). 4.4.2. Трехадресная машина (УМ-3). В четырехадресной машине отдельные команды программы могут размещаться в памяти самым произвольным образом, по- скольку в каждой команде указывается адрес следующей подлежа- щей выполнению команды. Однако уже из рассмотренных примеров программ видно, что при составлении программы удобно записывать команды по возможности в том порядке, в котором они должны выполняться машиной, и в этом же порядке размещать их в ячейках памяти. Если следовать этому правилу при составлении каждой программы, то в процессе своей работы последовательно выполня- емые команды машина будет обычно выбирать из ячеек памяти с последовательными номерами. Необходимость нарушать этот ес- тественный порядок выполнения команд возникает лишь в тех местах программы, где по специфике алгоритма надо «перескочить» через определенную группу команд. Однако такая необходимость возникает сравнительно редко. Следовательно, адрес А4 в команде, как правило, является избыточным и малоинформативным: если выполняемая команда выбрана из ячейки х, то в этой команде, как правило, А4=х+1. А это значит, что и написание этого адреса в каждой команде является по сути дела лишней работой, и 20% памяти, отводимой для хранения программы, используется не эф- фективно. Для устранения этих недостатков в машине УМ-3 зафиксиро- вано соглашение о естественном порядке выполнения команд. Реа- лизация этого соглашения заключается в том, что регистр С сделан счетчиком команд: при выполнении каждой команды к содержи- мому этого счетчика автоматически прибавляется единица (на этапе 2° каждого такта вместо действия С:=А4 выполняется дей- ствие С:=С+1). Исключение составляют лишь команды перехода, назначение которых как раз и состоит в том, чтобы прервать этот естественный порядок выполнения команд, и которые формируют содержимое счетчика команд по другим правилам. При таком соглашении четвертый адрес в команде оказывается излишним, и мы приходим ктрехадресной системе команд с форматом команды 0 41 42 43
1.4] РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 239 •де 0, А1, А2 и АЗ имеют тот же смысл, что и в УМ-4, а в качестве 44 принимается значение х+1, где х — адрес данной команды. Поскольку четвертое поле адреса в команде теперь отсутствует, то можно либо уменьшить за счет этого длину машинного слова (если это приемлемо с точки зрения представления чисел), либо увеличить число позиций в каждом поле адреса. На УМ-3 избран второй путь: длина машинного слова сохранена такой же, что и в УМ-4 (45 разрядов), а на каждое поле адреса в команде отведено по 12 позиций. Память УМ-3 содержит 4096 ячеек с восьмеричными адресами от 0000 до 7777 включительно. Представление чисел — такое же, как и в УМ-4. УМ-3 имеет тот же самый набор операций арифметического типа, а также опе- рацию останова, с теми же кодами операций. В связи с этим про- грамма примера 4.2 для УМ-3 будет отличаться от программы для УМ-4 только отсутствием адреса А4 в командах и записью каждого адреса в виде четырехзначного восьмеричного числа: ООП 0012 0013 0014 0015 0016 0017 0020 005 005 004 002 005 077 002 002 0200 0017 0200 0201 0001 0000 6220 6000 0200 0001 0020 0002 0002 0000 0000 0000 0001 0001 0002 0002 0300 0000 0000 0000 г1 = № г1=л№ г2 = Я/3 г2 = 7?-Я/3 V = rl хг2 Стоп Константа л « 3’/,4 Константа 3 При составлении этой программы отпала необходимость записи четвертого адреса в каждой команде, и программа стала более наглядной. Очень существенным является и то обстоятельство, что при той же длине машинного слова появилась возможность увели- чить объем адресуемой памяти в 8 раз по сравнению с УМ-4. Что касается операций перехода, то теперь их роль становится более важной, чем на УМ-4. Прежде всего, несколько меняется смысл операций условного перехода: поскольку на УМ-3 принят естественный порядок выпол- нения команд, то логично при одном из значений со сохранять этот естественный порядок выполнения команд, а изменять его, т. е. действительно осуществлять переход к какой-то другой команде, отличной от следующей по порядку, при другом значении со. В связи с этим команда с операцией 0=036 036 Л1 А2 АЗ теперь имеет следующий смысл: o3:=al; C:=if w then А2 else С+1 и называется «условный переход по costrue». Как видно, операция 036 перестала быть симметричной: с ее помощью нельзя нарушить естественный порядок выполнения
240 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. команд при fi>=false. Для обеспечения такой возможности на УМ-3 есть симметричная операция «условный переход по co=false» с кодом 076; команда 076 Л1 А2 АЗ 1 имеет следующий смысл: a3:=al; C:=if~iw tnenX2elseC4-l Иногда возникает необходимость нарушать естественный по- рядок выполнения команд независимо от каких-либо условий, как это имело место в примере 4.3 после вычисления у по первой из формул. Для достижения этой цели на УМ-3 есть еще одна допол- нительная операция «безусловный переход» с кодом 056 — по этой операции в счетчик команд С всегда заносится адрес А2, не- зависимо от значения о, так что команда 056 Al А2 АЗ имеет следующий смысл: оЗ:=а1; С:—А2 При выполнении любой из операций перехода значение со не из- меняется. Программа примера 4.3 для УМ-3 примет следующий вид: ООП 1002 0200 0201 0001 II ft 1 5г 8 ft A о* 0012 076 0000 0016 0000 При со =false переход к 0016 0013 001 0200 0201 0001 rl: = а-\-Ь 0014 005 0001 0001 0202 у = (а + Ь)* 0015 056 0000 0020 0000 Безусловный переход к 0020 0016 002 0201 0200 0001 rl =Ь — а 0017 004 0001 0021 0202 у — (Ь—а)/2 0020 077 0000 0000 0000 Стоп 0021 002 4000 0000 0000 Константа 2 По сравнению с программой для УМ-4 здесь появилась одна допол- нительная команда (безусловного перехода), но поскольку число таких команд в программах обычно весьма невелико, то можно считать, что количество команд в программах, предназначенных для решения одной и той же задачи, для четырехадресной и трех- адресной машин будет почти одинаковым. 4.4.3. Двухадресная машина (УМ-2). Анализ даже приведенных выше простейших программ для трехадресной машины показывает, что в них далеко не все команды являются по существу трехадресными, т. е. такими, в которых все три адреса различны. И это не случайно: статистика, набранная на большом числе реальных программ, показала, что только 25% Команд содержат три различных адреса; 50% команд содержат два различных адреса, а у 25% команд используется только один адрес (если не учитывать нулевой адрес, который часто фигурирует в
4.4] РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ- 241 :лучае, когда тот или иной адрес в команде вообще не используется, при выполнении данной операции, например в командах останова). Это, в частности, связано с тем, что при программировании формул довольно часто возникает ситуация, когда результат вы- полнения операции можно запомнить на месте одного из ее аргу- ментов. Кроме того, все операции переходов (если их не совмещать с другими операциями) используют только один из адресов, зада- ваемых в команде. Приведенные цифры свидетельствуют о том, что по крайней мере один из адресов в трехадресной команде является малоин- - формативным, и поэтому желательно было бы в команде указывать только два адреса, а третий определять по некоторому соглашению (аналогично тому, как определяется адрес следующей подлежащей выполнению команды на УМ-3). Тогда мы придем к двухад- ресной системе команд, при которой команда имеет вид: б Л1 А2 Соглашения, по которым определяется один из трех адресов,, могут быть различными. На двухадресной машине УМ-2 принято- следующее соглашение: в команде указываются адреса аргументов операции (Л1 и Л 2), а полученный результат запоминается на месте первого аргумента (по адресу Л1). Таким образом, команда УМ-2 6 Л1 Л2 обычно имеет следующий смысл: al:=al*a2, где * — операция с заданным кодом 0. Разумеется, на двухадресной машине принимается и соглашение* об естественном порядке выполнения команд, которое реализуется так же, как и на УМ-3. Двухадресная система команд дает такие же преимущества по- сравнению с трехадресной, что и последняя — по сравнению с. четырехадресной: в программе не надо записывать малоинформа- тивные адреса, рациональнее используется память, отводимая для хранения программы, упрощается устройство управления машины. Поскольку теперь третье поле адреса в командах отсутствует, то опять можно либо уменьшать длину слова, либо увеличивать- число разрядов в каждом поле. На УМ-2 избран первый путь —• ячейка имеет 33 разряда; машинное слово на бумаге записывается последовательностью из 11 восьмеричных цифр.
242 Электронно-вычислительные машины 1гл. Команда имеет формат 0 Л1 Л2 9 р. 12 р. 12 р. Память состоит из 4096 ячеек с адресами от 0000, до 7777в. Числа представляются так же, как в УМ-3, с той разницей, что на изображение модуля мантиссы отводится 24 разряда — это обеспечивает представление мантиссы с точностью до 2~2*, т. е. с 7—8 верными десятичными цифрами. Все арифметические операции УМ-3 имеются и в УМ-2. Заметим, однако, что теперь операции вычитания и деления стали «несим- метричными»: если при сложении и умножении можно оставить без изменения любой из аргументов (для чего достаточно сослаться на него во втором поле адреса в команде), то при вычитании и де- лении можно оставить без изменения только вычитаемое и делитель. Такая несимметричность может иногда вызывать определенные трудности при программировании. Для их устранения в УМ-2 предусмотрены модификации этих операций: обратное вычитание (0=102) и обратное деление (0=104); в командах с этими кодами операций уменьшаемое (делимое) выбирается по адресу Д2, а вы- читаемое (делитель) — по адресу Д1. Например, команда вида 102 А\ А2 имеет следующий смысл: а1:=а2—al. Операции управления в УМ-2 те же, что и в УМ-3, только в УМ-2 операции переходов уже не совмещаются с операцией пере- сылки, и в командах с этими операциями адрес Д1 не используется. В связи с этим в УМ-2 имеется специальная операция пересылки слова из ячейки с адресом А2 в ячейку с адресом Д1 (0=000). При выполнении этой операции значение со не изменяется (как и при выполнении операций перехода). Рассмотрим программу примера 4.2 для УМ-2. Вычисление значения V мы начинали с вычисления Н2, однако сейчас мы не можем в качестве первой команды программы написать команду умножения: 005 0200 0200; Я:=ЯхД, поскольку в результате ее выполнения значение Н было бы уте- ряно — в ячейку 0200 было бы записано значение Н2. Между тем значение Н еще потребуется дальше, при вычислении (7?—Я/3). И вообще при программировании следует неуклонно придержи- ваться правила — не изменять значений исходных данных, если ото специально не оговорено в постановке задачи. Эта рекомендация связана с тем обстоятельством, что задача, выданная кому-либо для программирования, как правило, является частью другой,
4J РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 243 более общей задачи. Поэтому вполне возможно, что значения, заданные в качестве исходных данных для этой части задачи, будут затем использоваться и в других частях общей задачи, и поэтому должны остаться без изменения. Из этих же соображений во время выполнения программы не должно изменяться содержимое ячеек, в которых были запасены константы, иначе при повторном об- ращении к этой программе (как части другой, более сложной программы) она не будет правильно выполнять свои функции. В связи с этим значение Н сначала перешлем в рабочую ячейку, а потом выполним операции, необходимые для вычисления ООП: ООО 0001 0200; г1:=Я 0012: 005 0001 0001; rl=№ 0013: 005 0001 ; г1=л№ В качестве А2 в последней команде надо указать адрес константы, представляющей значение л, но поскольку мы еще не знаем, в какой ячейке будет, размещена эта константа, второе поле адреса команды 0013 пришлось временно оставить пустым. В результате написанных трех команд в ячейке 0001 будет получен промежуточный результат rl —лН2. Далее надо вычислить значение (/?—Я/3). Последовательность необходимых для этого операций очевидна, но опять же первой нельзя писать команду для деления Н на три, так как в результате ее выполнения будет «испорчено» либо значение Н, либо константа, представляющая число 3. Нельзя здесь воспользоваться и тем обстоятельством, что раньше значение Н пересылалось в ячейку 0001: поскольку далее вычислялось И2, то значение Н в этой ячейке сохранить было не- возможно. Поэтому очередная группа команд тоже должна начи- наться с пересылки одного из аргументов операции деления, на- пример Н, в рабочую ячейку (отличную от 0001, поскольку в ней хранится еще неиспользованный промежуточный результат л№): 0014: 000 0002 0200; г2:=Н 0015: 004 0002 ; г2=Н1$ 0016: 102 0002 0201; r2=R—HI3 Второе поле адреса в команде 0015 временно оставлено пустым — здесь надо будет указать адрес константы, представляющей число 3. Теперь надо перемножить значения rl и г2, полученный резуль- тат переслать в ячейку, отведенную для V, и прекратить процесс вычислений: 0017: 005 0001 0002; Н:=г1Хг2 0020: 000 0300 0001; V:=rl 0021: 077 0000 0000; Стоп Вслед за командой останова разместим последовательно константы, представляющие числа л и 3 — их адреса надо указать соответ- ственно в команадх 0013 и 0015.
244 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл? z' Окончательно программа примет вид: ООП 000 0001 0200 г1: = Я 0012 005 0001 0001 г1=№ 0013 005 0001 0022 И=Я2л 0014 000 0002 0200 г2: = Я 0015 004 0002 0023 r2 = H/3 0016 102 0002 0201 r2 = R — H/3 0017 005 0001 0002 rl: = rl Хг2 0020 000 0300 0001 V:=rl 0021 077 0000 0000 Стоп 9 0022 002 6220 0000 Число л « 3ft 0023 002 6000 0000 Число 3 Программа примера 4.3 для УМ-2 будет иметь аналогичные особенности: при программировании каждой из формул а—b (для выработки значения ©), (а-Н)2 и (Ь—а)/2 (для вычисления значения у) первой будем писать команду, пересылающую один из аргументов первой арифметической операции в одну из рабочих ячеек. Чтобы избежать лишних пересылок, значение, которое нужно присвоить у, на каждой из ветвей вычислений получим в одной и той же ра- бочей ячейке, например в 0001, а пересылку этого значения в ячейку, отведенную для у, сделаем в одном месте программы. Остальные моменты в программе будут мало отличаться от программы для УМ-3: ООП 000 0001 0200 г\: — а 0012 002 0001 0201 V 3 1 0013 076 0000 0020 Переход к 0020 при со = false 0014 000 0001 0200 rl: = а 0015 001 0001 0201 г! —а + Ь 0016 005 0001 0001 rl =(a+by 0017 056 0000 0023 Безусловный переход к 0023 0020 000 0001 0200 rl : = а 0021 102 0001 0201 rl =Ь — а 0022 004 0001 0025 rl = (Ь — а)/2 0023 000 0202 0001 уг = П 0024 077 0000 0000 Стоп 0025 002 4000 0000 Число 2 Примечание. Может показаться, что команда 0020 (пере- сылка значения а в ячейку 0001) является лишней, поскольку выше >же была написана команда 0014 такого же содержания. Однако это не так — указанные команды находятся на разных ветвях вычислений, так что если выполняется команда 0020, то не выпол- нялась команда 0014 и наоборот. На самом деле одну из этих ко- манд пересылки в ячейку 0001 все же можно «сэкономить»: поскольку с этой пересылки начинается каждая из ветвей, то ее можно выпол-
4.)] РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 245 нить до разветвления вычислительного процесса. Читателю пред- лагается составить такой вариант программы. По сравнению с программами для УМ-3 в программах для УМ-2 появились дополнительные команды пересылки, связанные с тем обстоятельством, что на двухадресной машине результат выпол- нения операции нельзя отослать на запоминание в любую жела- емую ячейку. Таких дополнительных команд возникает не так уж много. А если учесть, что каждая команда на УМ-2 на 12 разряде» короче, чем на УМ-3, то общее число разрядов в памяти, требуемое для хранения программы, для обоих машин будет примерно оди- наковым. В типичных программах двухадресная система команд более экономична, чем трехадресная. К тому же сама двухадресная машина несколько проще, чем трехадресная — хотя бы потому, что команда содержит в себе меньше полей, подлежащих хранению и интерпретации в устройстве управления. 4.4.4. Одноадресная машина (УМ-1). Из примеров программ, приведенных для УМ-2, видно, что во многих командах фигурирует один и тот же адрес'Л1. Это свя- зано, в частности, с тем обстоятельством, что для хранения многих промежуточных результатов можно использовать одну и ту же рабочую ячейку. Так, во всей программе примера 4.3 для УМ-2 использована только одна рабочая ячейка (0001), и если не считать команд пере-~ хода и останова, в которых адрес А1 вообще не используется, то 8 из 9 оставшихся команд имеют один и тот же первый адрес (А1 = =0001). Очень похожая ситуация имеет место и в программе примера 4.2 для УМ-2: если вслед за командой 0013, получающей в ячейке 0001 промежуточный результат я№, записать команду пересылки со- держимого этой ячейки в другую ячейку (например в 0002), то и в трех последующих командах в качестве А1 можно принять адрес 0001, так что и в этой программе подавляющее число команд имело бы один и тот же адрес Л1. Эта ситуация достаточно типична. Она объясняется тем, что промежуточные результаты, как правило, используются немед- ленно вслед за их вычислением. В связи с этим для двухадресной машины можно принять дополнительное соглашение: Al=const, т. е. во всех командах в поле первого адреса подразумевать адрес некоторой фиксированной ячейки, специально предназначенной для кратковременного хранения очередного промежуточного ре- зультата. Но поскольку этот адрес всегда один и тот же, то отпадает необходимость указывать его в командах в явном виде, и в итоге мы приходим кодноадресной команде 0 А
246 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ 1Гл., 4 которая обычно имеет смысл: г:=г*а, где г — операнд, храня- щийся в фиксированной ячейке, а а — операнд с адресом А. Поскольку команды не содержат явной ссылки на эту фикси- рованную ячейку, а она определяется принятым на машине согла- шением, то эту «особую» ячейку можно выполнить в виде быстрого регистра, расположенного в самом арифметическом устройстве — это дает возможность исключить из такта работы машины два об- ращения к памяти и тем самым повысить быстродействие ЭВМ. Роль такого быстрого регистра обычно выполняет регистр сумма- тора S арифметического устройства. Как видно, одноадресная команда очень короткая и поэтому требует мало места в памяти для ее хранения, а также имеет очень простую структуру, что упрощает ее интерпретацию, а следова- тельно — и устройство управления. Однако малая длина команды порождает следующую проблему: если длину машинного слова принять равной длине команды, то такая длина слова может ока- заться недостаточной для представления чисел. Здесь наиболее естественны следующие две возможности: либо одним длинным словом представлять две команды, либо каждое число представлять двумя короткими словами. Рассмотрим конкретную одноадресную машину УМ-1. Машинное слово состоит из 42 двоичных разрядов. Представ- ление чисел — как в УМ-3, с той разницей, что на изображение модуля мантиссы отводится 33 разряда вместо 36 на УМ-3. Формат команды: ф А 9 р. 12 р. Одним машинным словом представляются две очередные коман- ды: в левой половине слова представляется одна команда (левая), а в правой половине слова — другая команда (правая); при этом считается, что левая команда предшествует правой. Набор операций УМ-1 аналогичен УМ-2. Заметим, однако, что операция пересылки 0=000, которая теперь означает пересылку слова из ячейки А в сумматор S, перестала быть симметричной — с ее помощью нельзя переслать содержимое сумматора в память. Поэтому на УМ-1 есть операция обратной пересылки (0=100), по которой содержимое сумматора пересылается в ячейку А — при этом содержимое сумматора и значение со сохраняются. При программировании на УМ-1 необходимо учитывать, что в машине адресуются только слова. А поскольку в слове представ- ляется пара команд, то с помощью адреса можно сослаться только на левую команду этой пары. Отсюда, в частности, следует, что все команды, на которые возможны переходы, обязательно должны
4 4] ' РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 247 быть левыми. При этом возможна такая ситуация, что правая половина предыдущего слова не может быть использована для пред- ставления очередной команды. Но если левая команда, представ- ленная в этом слове, не является командой перехода, то машина все равно будет интерпретировать правую половину этого слова как очередную команду, подлежащую выполнению. В связи с этим в правую половину такого слова придется поместить лишнюю команду, единственное назначение которой — обеспечить переход к следующей по порядку команде (таковой может быть, например, команда безусловного перехода к следующей по порядку команде). Программирование для УМ-1 в целом мало отличается от про- граммирования для УМ-2 — необходимо лишь иметь в виду, что регистр S, содержимое которого на одноадресной машине всегда является одним из аргументов, используется почти в каждой опе- рации. Составим для УМ-1 программу примера 4.2, взяв за основу программу этого примера для УМ-2. Сначала спланируем последовательность машинных операций. Поскольку одним из аргументов каждой арифметической операции является содержимое сумматора, то для вычисления одно из значений л или Н надо предварительно переслать в сумматор: 1°. $:=Я 2°. S:=SxH 3°. S:=SXn Вычисление (R—Н/3) тоже придется начать с пересылки значе- ния Н в сумматор, но чтобы не потерять значение, полученное в сумматоре ранее, его содержимое предварительно надо запомнить в рабочей ячейке (так приходится поступать всегда, когда полу- ченный промежуточный результат не может быть использован не- медленно): 4°. rl:=S 8°. S:=SXrl 5°. S:=tf 9°. V:=S 6°. S:=S/3 10°. Стоп 7°. S:=/?—S Дополнительные операции пересылок, выполняемые на этапах Г и 9°, на одноадресной машине являются неизбежными: первая из них обязательно должна предварять первую арифметическую операцию, а вторая обеспечивает запоминание результата в нужной ячейке памяти. Что касается пересылок, выполняемых на этапах 4° и 5°, то их можно устранить, если поменять порядок вычислений по данной формуле, начав их с вычисления значения (/?—Я/3): 1°. 5:=Я 5°. S:=Sxtf 2°. S:=S/3 6°. S:=SX« 3°. S:=R—S T. V:=S 4°. S:=SXH 8°. Стоп
248 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ (Гл. 4 Как видно, выбор порядка выполнения арифметических операций при программировании формул для одноадресной машины имеет существенное значение. В итоге программа для УМ-1 примет вид: ООП 000 0200' 004 0015 0012 102 0201 005 0200 0013 005 0200 005 0016 0014 100 0300 077 0000 0015 002 6000 000 0000 0016 002 6220 000 0000 = S: = S/3; (S = tf/3) S:=R—S; S: = Sxff; (S= = (Я-Я/3)Я) S: = Sxff; S: = Sx«; (S = = (/?-Я/3)Я»л) V'. = S; стоп Константа (число 3) Константа (число л « З^Л Отличия этой программы от соответствующей программы для УМ-2 состоят главным образом в представлении двух команд в виде одного машинного слова. В программе примера 4.3 для УМ-1 проявятся и другие специ- фические особенности, которые можно выявить уже на этапе пла- нирования подходящей последовательности машинных операций: S:=a S:—S—b; &'.=^.<Zb Переход на метку М при ct>=false S:=a S:=S+& rl:=S (запоминание S в памяти) S:=Sxrl Безусловный переход на метку Р { Af: { Р- Стоп S:=a S:=b—S S:=S/2 y:=S Здесь фигурными скобками объединены пары команд, которые будут размещаться в одной ячейке памяти. Отсюда видно, что безусловный переход производится на вторую команду пары, что на УМ-1 недопустимо. Поэтому команда, помеченная меткой Р, должна быть помещена в левой половине следующего слова, а после команды с действием S:—S/2 придется вставить дополнительную команду, так что окончание нашего плана вычислений придется сделать, например, следующим образом: f S:=S/2 ( Безусловный переход на метку Р
4.4] РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 249 P:y:—S Стоп В итоге программа этого примера для УМ-1 будет иметь вид: ООН 0012 0013 0014 0015 0016 0017 0020 000 076 001 005 000 004 100 002 0200’002 0015:000 0201|100 00011056 02005102 0020!056 02021077 4000I000 0201 0200 0001 0017 0201 0017 0000 0000 S: = a; <S: = S—!>(«>: = а<5) Переход при <о = false; S: = a S: = S+&; rl: = S S: = S x г Г; Безусловный переход S: = a; S: = b—S S: = S/2; Безусловный переход y: = S‘, Стоп Число 2 4.4.5. Дробно-адресная машина (УМ-Д). Если проанализировать «активность» использования отдельных ячеек памяти в процессе выполнения машиной какой-либо про- граммы, то окажется, что они используются далеко не равномерно: к одним ячейкам обращение производится очень часто, к другим — гораздо реже. Действительно, возьмем программу примера 4.2 для УМ-2. В этой программе используется 16 различных ячеек — для хранения команд, констант, исходных данных, промежуточных и окончательных результатов. Нетрудно подсчитать, что в процессе выполнения этой программы в общей сложности будет произведено 30 обращений к памяти, из них к ячейке 0001—9 раз, к ячейке 0002— 6 раз, к ячейке 0200—2 раза и к остальным 15 ячейкам — всего по одному разу. Таким образом, 50% всех обращений приходится иа долю двух ячеек (0001 и 0002) из 16, а на одну ячейку (0001)—30% всех обращений. И это обстоятельство не является случайным — статистический анализ реальных программ показал, что при их выполнении, как правило, на одну из ячеек приходится в среднем около 25%, на четыре ячейки — примерно 50%, а на 16 ячеек — больше 90% всех обращений. А теперь проведем несложные расчеты. Пусть на машине надо выполнить программу, требующую в общей сложности М обращений к памяти. На современных ЭВМ непосредственное выполнение опе- раций в арифметическом устройстве производится очень быстро — затрачиваемое на это время сравнимо со временем одного обращения к памяти. Кроме того, непосредственное выполнение операций уда- ется частично (а иногда и полностью) совместить по времени с об- ращениями к памяти. Поэтому приближенно можно считать, что время, затрачиваемое машиной на выполнение программы, равно суммарному времени всех необходимых обращений к памяти. Если обозначить через т число обращений к памяти в единицу времени (что является характеристикой ее быстродействия), то на выпол- нение программы будет затрачено время Т=М[т.
250 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 Если принять во внимание нашу статистику и обозначить через JWi число обращений к упомянутым 16 ячейкам памяти, а через М2 — число обращений к остальным ячейкам (Afi+Af2=Af), то Ме=0.$М, М2—0.1А4. Теперь допустим, что быстродействие этих 16 ячеек увеличилось в k раз. Тогда на выполнение программы будет затрачено время т, ОЗМ . о.Ш ~ km ' m Степень повышения быстродействия машины определяется как отношение Его значение равно 0.55 при й=2 и 0.19 при 6=10. А это значит, что если увеличить в два раза быстродействие только этих 16 ячеек, то и общее быстродействие машины почти удвоится, а повышение их быстродействия в 10 раз увеличивает общее быстродействие машины в 5 раз. Разумеется, более быстрая память обходится до- роже, поэтому было бы экономически невыгодно увеличивать в несколько раз быстродействие всей памяти, состоящей из нескольких тысяч или десятков тысяч ячеек. Существенное же повышение быстродействия сравнительно небольшого числа ячеек не обойдется слишком дорого, а эффект получится весьма значительным и оп- равдает эти дополнительные затраты. Такими быстрыми можно сделать, например, первые 64 ячейки с номерами от 0 до 63, и если в каком-либо поле адреса команды разрешить ссылки только на ячейки этой группы, то для этой цели можно использовать «короткие» адреса (в восьмеричной за- писи от 00 до 77), для представления которых в этом поле адреса достаточно иметь всего 6 двоичных разрядов. Тогда на базе двухадресной команды можно получить «полуто- раадресную» команду 0 al А2 (буква а используется для обозначения короткого адреса), на базе трехадресной команды — команду с адресностью 0.5+1+ 1 0 al А2 АЗ или с адресностью 0.5+1+0.5 0 al А2 аЗ и т. д., которые имеют тот же смысл, что и на УМ-2 или УМ-3. Помимо возможности существенно повысить быстродействие машины за счет сравнительно небольших дополнительных затрат, дробно-адресные машины имеют меньшую длину команд по срав- нению с полноадресными, и поэтому для хранения программы в памяти требуется меньшее число битов. Программы для таких машин — за исключением использования коротких адресов — прак- тически не отличаются от программ для соответствующих полно- адресных машин.
4.4] РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 251 . Рассмотрим, например, машину УМ-1.25, полученную на базе УМ-2. Адреса ячеек памяти — от 0000 до 7777; ячейки от 0 до 7 яв- ляются специальными (быстрыми). Длина слова — 24 двоичных разряда. Команда представляется одним словом и имеет формат 0 al А2 9 р. 3 р. 12 р. Представление чисел — как в УМ-3, но на изображение модуля мантиссы отводится 39 двоичных разрядов. Каждое число представ- ляется парой машинных слов с последовательными адресами, причем второе слово считается непосредственным продолжением первого, а адрес первого слова является и адресом числа. Набор операций — как в УМ-2; дополнительно имеется опера- ция обратной пересылки (0=100) для достижения симметричности этой операции. Приведем программу примера 4.2 для УМ-1.25 при распреде- лении памяти ад/?.//=0200, оф./?=0202, аф.У=0300, адр.г\ =0001, адр.г2-=» 0003: ООН 000 1 0200 0012 005 1 0001 rl ; = г] xrl 0013 005 1 0022 rl: = rl х л 0014 000 2 0200 г2: = // 0015 004 2 0024 г2: = г 2/3 0016 102 2 0202 г2: = R — г2 0017 005 1 0002 г1: = г!хг2 0020 100 1 0300 У: = г1 0021 077 0 0000 Стоп 0022 002 6 2200 0023 000 0 0000 Число л 0024 002 6 0000 0025 000 0 0000 | Число 3 4.4.6. Машина с переменным форматом команд (УМ-П). По мере роста сложности задач, решаемых на ЭВМ, все более острой становится проблема экономии памяти, необходимой для хранения соответствующих программ. Между тем любая из рас- смотренных выше систем команд имеет свои недостатки в этом отношении. Так, чем больше адресность команды, тем чаще встречаются случаи, когда тот или иной адрес в команде либо совсем не исполь- зуется (например, все адреса в командах останова, адреса А1 и АЗ в трехадресной команде перехода и т. д.), либо несет слишком малую
252 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. ♦ информационную нагрузку (например, адрес А 4 в четырехадресной: команде). С другой стороны, уменьшение адресности приводит к появ- лению вспомогательных команд, и хотя малоадресные команды более короткие, тем не менее затраты памяти для хранения отдель- ных частей программы могут даже увеличиваться за счет хранения как лишних кодов операций, так и лишних адресов. Например, для реализации оператора присваивания Z:=X+Y на трехадресной машине требуется одна команда 001 адр.Х adp.Y adp.Z состоящая из четырех слогов. На одноадресной машине этот опе- ратор реализуется тремя командами: ООО адр.Х 001 adp.Y 100 adp.Z содержащими в общей сложности 6 слогов (появились два дополни- тельных кода операции 000 и 100). На двухадресной машине эти вычисления могут быть реализованы командами 000 adp.Z адр.Х 001 adp.Z adp.Y содержащими также 6 слогов (появились дополнительный код операции 000 и один дополнительный адрес adp.Z, поскольку он встречается дважды). Для устранения этих недостатков на некоторых машинах ис- пользуется переменный формат команд, при котором команды могут иметь различную адресность и различные размеры полей адресов, из чего следует, что и длины команд могут быть различными. При этом формат команды иногда связывается с кодом заданной в ней операции, а иногда он указывается явно в специально отведенном для этой цели поле команды. Рассмотрим конкретную машину такого типа — УМ-П. Для экономного расходования памяти слово взято короткое: оно состоит из 12 двоичных разрядов. Каждое машинное слово рассчитано на представление одного слога команды (кода операции или адреса). Команда представляется одним или несколькими слогами, расположенными в ячейках памяти с последовательными адресами. В первом слоге (каждый слог записывается четверкой восьмеричных цифр) первая цифра указывает адресность данной команды, а сле- дующие три цифры являются кодом операции. Каждый следующий слог является очередным полем адреса в команде. Числа представляются аналогичным образом: первая восьме- ричная цифра первого слога означает число слогов, отведенных для изображения модуля мантиссы, а следующие три восьмеричные
4.4) РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ - 25$ цифры — как и в УМ-3 — служат для изображения знака числа; и его порядка. Модуль мантиссы представляется следующими по- порядку слогами. Таким образом, числа тоже могут иметь разную длину. При выполнении арифметических операций длина результата принимается равной наибольшей из длин аргументов. С числами можно было бы поступить и иначе — например, определять длину аргумента по коду операции (в этом случае одна и та же операция может иметь разные модификации, например сложение целых чисел, сложение обычных вещественных чисел, сложение вещест- венных чисел повышенной точности, причем каждый тип чисел: имеет свою длину). Вместо задания длины аргумента можно ис- пользовать также какой-то фиксированный признак его конца,, помещаемый в последнем слоге мантиссы. В набор операций УМ-П входят все рассмотренные ранее опе- рации с теми же их кодами. Составим теперь программу примера 4.2 для УМ-П. Будем считать, что каждое из исходных значений Н и R представлено' тремя последовательными слогами, причем все двоичные цифры мантиссы верные. Значит, и приближенные числа, запасаемые в качестве констант, должны иметь такую же точность. Искомое- значение V также будет представлено тремя последовательными слогами. В связи с этим примем следующее распределение памяти: Н-. 0200 0201 0202 (2, Р) М R: 0203 0204 0205 (2, Р) | М V: 0206 0207 0210 (2, Р) М (заметим, что при этих условиях и каждое промежуточное значение будет представляться тремя последовательными слогами). Чтобы максимально использовать преимущества одноадресной системы команд, вычисление значения V начнем с получения в. сумматоре значения (/?—Я/3): это можно сделать либо по одной трехадресной и одной одноадресной командам (пользуясь тем, что вырабатываемый по трехадресной команде результат сохра- няется в сумматоре), либо по трем одноадресным командам. По- скольку по числу слогов в программе эти варианты эквивалентны^ мы остановимся на первом из них. Последующие действия (и ко- манды) достаточно очевидны, поэтому приведем программу в окон* чательном виде: ООН 0012 0013 0014 3 о о о 004 200 030 001 Трехадресная; деление Адрес Н Адрес числа 3 Результат Я/3 в ячейках 0001—0003 (и в S) 00151 1 1021 Одноадресная; обратное вычитание 001610 2031 Адрес R (S = R—Я/3) 001711 0051 Одноадресная; умножение 002010 2001 Адрес Я (S = Я (Я —Я/3))
254 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл 4 0021 0022 0 005 200 I Одноадресная; умножение | Адрес Н = 0023 1 005 Одноадресна я; ум ножение | Адрес л (3 = л//2(/?—/7/3)) 00241 0 0321 00251 1» 100 I Одноадресная; обратная пересылка 0026 1 |о 206 1 Адрес V (V:=S) 0027|0 077 | Нульадресная; Стоп 00301 00311 11 |6 0021 000 1 Число 3 0032 2 002 0033 6 221 Число л 0034 7 733 Программа аг. 0200 0201 0202 0203 примера (3, Р) м 4.3 для УМ-П при распределении памяти Ь: 0204 0205 0206 0207 (3, Р) М у. 0210 0211 0212 0213 (3, Р) м может быть следующей: ООП 3 002 Трехадресная; вычитание 0012 0 200 Адрес а 0013 0 204 Адрес b 0014 0 001 Адрес г (г\—а—Ь\ со:= а < Ь) 001511 0761 Одноадресная; переход при ш »false 001610 0271 Адрес перехода 0017 3 001 Трехадресная; сложение 0020 0 200 Адрес а 0021 0 204 Адрес b 0022 0 001 Адрес г (г:= а-[-Ь) 00231 |1 0051 Одноадресная; умножение Адрес г — 00241 |о 0011 00251 11 056! Одноадресная; безусловный переход 00261 |0 0351 Адрес перехода 0027 3 002 Трехадресная; вычитание 0030 0 204 Адрес Ъ 0031 0 200 а 0032 0 001 Адрес г (S:= г:= Ь — а) 0033 11 0041 I Одноадресная; деление 0034 |о 0401 | Адрес числа 2 (S:=S/2) 0035 |1 1001 I Одноадресная; обратная пересылка 0036 |о 210 | Адрес у (y.— S) 0037 10 077 | Нульадресная; стоп 0040 0041 11 14 002 000 I Число 2 Если объем (длину) программ для каждой из машин измерять в числе слогов, то получим следующие характеристики этих про- грамм:
4.4) РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 255 УМ-4 УМ-3 УМ-2 УМ-1 УМ-П Пример 4.2 40 32 33 24 20 Пример 4.3 40 36 39 32 25 Как видно, переменный формат команд обеспечивает наиболее эко- номное расходование памяти для хранения программ. Заметим, что некоторое увеличение общего числа слогов в про- граммах для УМ-2 по сравнению с УМ-3 в рассмотренных примерах объясняется тем, что в этих примерах расчетные формулы были очень короткими, так что на этих примерах общая закономерность не проявляется в полной мере. Если же такую статистику набирать на различных (в том числе и достаточно сложных) программах, то отклонения от монотонного убывания чисел в строках будут встречаться редко. 4.4.7. Безадресная (стековая) машина (УМ-С). Вернемся еще раз к одноадресной машине и остановимся по- подробнее на некоторых ее особенностях. Самое существенное от- личие одноадресной машины от всех остальных рассмотренных нами машин заключается в принятии соглашения о том, что один из аргументов каждой операции выбирается из фиксированной ячейки и что получаемый результат всегда запоминается в этой же фиксированной ячейке. Эту специально выделенную ячейку можно трактовать как специальную память (назовем ее S-памятью), из которой автома- тически всегда производится выборка аргумента, и в которую всегда производится запись результата. Поскольку данная S-память способна хранить только одно слово, то по операции чтения из нее всегда выдается то слово, которое было в нее записано в по- следний раз. Таким образом, если результат выполнения каждой операции является одним из аргументов следующей операции, то отпадает необходимость запоминать промежуточные результаты в основной памяти и тем самым использовать адреса для ссылок на эти про- межуточные результаты. Так, вычисления по формуле x=aXb+c—d при распределении памяти аЗр.а=0100, ддр.Ь=0101, пЗр.с=0102, adp.d=0103, адр. х=0200 реализуются следующей программой:
256 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ (Гл. 4 ООН 000 0100 S: = a 0012 005 0101 S=ax6 0013 001 0102 S=axb+c 0014 002 0103 S =ax&-f-c—d 0015 100 0200 x:=S 0016 077 0000 Стоп В этой программе нет ни одной дополнительной пересылки проме- жуточных результатов из сумматора в память и нет ни одной явной ссылки на промежуточные результаты. Ситуация, однако, становится менее благоприятной, если по- лученный промежуточный результат оказывается невозможно ис- пользовать немедленно: поскольку S-память способна хранить только одно слово, то в этих случаях возникает необходимость в дополнительных командах пересылки промежуточных результатов в основную память и использования адресов для последующих -ссылок на них. Например, вычисления по формуле x=a'Xb+cXd яри прежнем распределении памяти реализуются по программе: ООП 000 0100 S:=a 0012 005 0101 S = axb 0013 100 0001 rl : = S 0014 000 0102 S: = c 0015 005 0103 S = cxd 0016 001 0001 S=axb+cx.d 0017 100 0200 x:=S 0020 077 0000 Стоп Здесь появилась дополнительная команда пересылки промежуточ- ного результата ахb в память и возникла необходимость явной ссылки на этот результат, что фактически привело к необходимости еще одной дополнительной команды. Если же в наборе машинных операций имеются одноместные -операции (т. е. операции с одним аргументом), например, вычисле- ние И, К*, sin(x) и т. д., то в случае, когда аргумент такой опе- рации является промежуточным результатом, в соответствующей команде вообще нет необходимости указывать какой-либо адрес, т. е. она будет по существу безадресной командой. Например, при наличии на одноадресной машине указанных .выше одноместных операций, имеющих соответственно коды 064, 4)65 и 066, вычисления по формуле /, = sin (УТ*Т) яри распределении памяти: adp.x=0100, adp.y=Q200 реализова- лись бы по следующей программе (как обычно, в качестве неисполь- зуемого адреса будем записывать адрес 0000):
4.4) РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 257 ООН ООО 0012 0013 064 065 0014 0015 0016 066 100 077 0100 0000 0000 0000 0200 0000 S = |x]__ S = sin(j/i7D </:=S Стоп Как видно, в этой программе все команды, задающие выполнение операций арифметического типа, являются безадресными, так что адресное поле в таких командах является излишним и эти команды можно было бы задавать в более компактном виде. В рассмотренной ранее машине с переменным форматом (УМ-П) для задания каждой из таких команд достаточно было бы одного слога, в котором за- дается только код нужной операции. Однако и в этой машине .не- обходимо явно указывать адреса промежуточных результатов, которые не могут быть использованы немедленно. В связи с этим возникает естественное желание — обобщить идеи, использованные в одноадресной машине, таким образом, чтобы, с одной стороны, ликвидировать в программе дополнитель- ные команды пересылок промежуточных результатов и, с другой стороны, сделать безадресными любые команды, задающие операции переработки данных, независимо от числа аргументов, использу- емых в этих операциях. Для достижения этих целей достаточно обобщить соглашение, принятое на одноадресной машине, а именно, считать, что все аргументы каждой операции выбираются не из общей, адресуемой памяти, а из специальных запоминающих ячеек, т. е. из S-памяти, и в этой же памяти запоминается каждый получаемый результат. Для этого S-память, естественно, должна быть более емкой, чтобы в ней можно было хранить одновременно несколько слов. Поскольку при обращениях к этой памяти не должен использоваться принцип адресации, то необходимо принять некоторое дополнительное со- глашение'о том, какое именно слово должно выдаваться из S-памяти при очередном обращении к ней для чтения. Мы уже отмечали, чтб, как правило, промежуточный результат* выработанный по какой-либо команде, используется в команде, непосредственно следующей за данной. Причем чаще всего этот промежуточный результат в дальнейшем уже не нужен. А это значит, что S-память должна быть устроена так, чтобы из нее пер- вым выдавалось то слово, которое в нее поступило последним, и чтобы при этом оно удалялось из S-памяти. Следовательно, S- память должна иметь уже знакомую нам по главе 3 структуру стека. Каждый очередной аргумент выполняемой команды вы- бирается из вершины стека и исключается из него, а вырабатыва- емый результат снова записывается в вершину стека. Стек может быть реализован на обычной адресуемой памяти; однако л * Э. 3. Любимский и др.
258 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. ♦ желательно, чтобы память, на которой реализуется стек, была выполнена на возможно более быстрых элементах, так как она используется значительно интенсивнее, чем основная опера- тивная память. В процессоре стековой машины помимо дополни- ' тельной памяти, на которой реализуется стек, имеется специальный регистр R—указатель вершины стека: в нем хранится и автомати- чески пересчитывается адрес первой свободной ячейки стека. Про- цедуры работы со стеком, реализуемые процессором, несколько отличаются от тех, которые были приведены в главе 3. Во-первых, они рассчитаны на работу всегда с одним и тем же массивом typeless array S[l: N], где N — число ячеек в стеке. Во-вторых, в них должен быть предусмотрен контроль за переполнением стека и за попыткой выбрать слово из пустого стека. Эти процедуры можно описать так: procedure.В СТЕК(х); value х; typeless х; If /?>ATthen <p:=true else begin S[/?]:=x; /?:=/?+! end procedure ИЗ CTEKA(x)-, typeless x; If /?==! then <p:=true else begin R:=R—i; x: =$[/?] end Тогда выполнение, например, операции сложения запишется так: ИЗ CTEKA(Rl); ИЗ CTEKA{R2}-, S:=/?l+/?2; ю:=$<0; <p:=abs(S)>A4; В CTEK(S) Для обмена данными между стеком и основной памятью вводятся специальные операции, выполнение которых описывается следу- ющими операторами: ИЗ СТЕКА(0П1А]) и В СТЕК(0П1А]), где А — адрес ячейки основной памяти, с которой должен быть произведен обмен. При дальнейшем изложении условимся состояние стека изоб- ражать в виде последовательности позиций, отделенных друг от друга запятой, которые упорядочены слева направо по увеличению «возраста» их содержимого, так что самая левая позиция соответ- ствует вершине стека, а самая правая — его «дну». Вслед за самой правой позицией будем записывать значок , означающий конец стека. При наличии стека на одноадресной машине программа для вычислений по формуле x=a'Xb+cXd при прежнем распределении памяти могла бы иметь следующий вид (в предположении, что первоначально стек был пустым): Адрес Команда Состояние стека ООП 000 0100 а |— 0012 000 0101 Ь, а |— 0013 005 0000 (ахЬ)Н 0014 000 0102 с, (axb)\—
4.4 J РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 259 0015 000 0103 d, с, (ахЬ)[— 0016 005 0000 (clxc), (axb))— 0017 001 0000 (dxc+axb)[— 0020 100 0200 1— 0021 077 0000 Стоп В этой программе нет ни одной явной ссылки на промежуточные результаты и все команды с операциями арифметического типа являются безадресными — в том смысле, что в' адресной части таких команд не задается какая-либо информация об операндах выполняемой операции. Поэтому адресная часть в таких командах оказывается излишней, и в итоге мы приходим к безадресной (стековой) машине. Прежде чем переходить к описанию конкретной стековой ма- шины, заметим, что при вычислениях иногда все же возникает ситуация, когда один и тот же промежуточный результат исполь- зуется несколько раз в качестве аргументов операций, например при вычислении (a+b)/(a+b+V). В таких случаях при использовании стека возникают определенные трудности: после вычисления (а+Ь) этот промежуточный результат будет записан в стек, но для выпол- нения следующих операций сложения, и деления это значение должно находиться в стеке в двух экземплярах, так. как при вы- полнении второй операции сложения (после занесения в стек еди- ницы) значение (а+Ь) будет выбрано из стека и тем самым исклю- чено из него, так что выбрать это значение второй раз при выпол- нении операции деления будет уже невозможно. В связи с этим в программе придется предусмотреть дополнительные команды пересылок (одна пересылка из стека в память и затем две пересылки, этого значения из памяти в стек). Для устранения этих неудобств полезно иметь специальную машинную операцию дублирования в стеке последнего из записанных в него слов (содержание этой опе- рации эквивалентно, например, таким трем операторам: ИЗ. СТЕ- КА(Р)-, В СТЕК(Р)\ В СТЕК(Р)). ‘ Теперь рассмотрим конкретную стековую машину УМ-С. Стек на этой машине выполнен в виде самостоятельной памяти, неза- висимо от основной. Длина слова в стеке — 36 двоичных разрядов. Представление чисел — как в УМ-3, с выделением соответствую- щего количества разрядов на изображение модуля мантиссы. Основная память состоит из 512 ячеек с восьмеричными адре- сами от ООО до 777. Длина слова в основной памяти — 12 двоичных разрядов. Каждое число представляется последовательной тройкой слов; адрес первого из них является адресом числа. Команда представляется одним словом в котором восьмеричная цифра 6® определяет тип команды и трак- товку следующей тройки восьмеричных цифр A =6i6263 по следу- ющему правилу: 9»
260 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 6,=0 означает запись в стек числа, выбираемого из памяти по адресу А (В СТЕК (ОП [Л]); 60=1 означает чтение числа из стека и его запоминание в па- мяти по адресу А (ИЗ СТЕКА (ОП [Л1); 6о=2 означает выполнение в арифметическом устройстве опе- рации с кодом 0=Л, аргументы которой последовательно читаются из стека и результат также записывается в стек (ИЗ СТЕКА (/?1); ИЗ СТЕКА (R2)-, S:=7?10/?2; В СТЕК (S)); 6О=3 означает переход по адресу Л при co^true (if со then С: =Л); 6О=4 означает переход при <»=false (if-ко then С:=Л); 6О=5 означает безусловный переход по адресу Л (С:=Л). Набор машинных операций такой же, что и в УМ-1, с теми же кодами операций. Кроме того, в УМ-С имеется упоминавшаяся ранее операция дублирования в стеке слова, находящегося в его вершине — эта операция имеет код 047. Для иллюстрации особенностей программирования для УМ-С рассмотрим сначала пример 4.1 (вычисление y=d—(a+b)2). Как обычно, начнем с планирования подходящей последова- тельности машинных операций, причем будем считать, что к началу этих вычислений стек пуст. Из заданной формулы видно, что сначала надо вычислить сумму (а+Ь), но прежде чем выполнять операцию сложения, надо позабо- титься о том, чтобы аргументы этой операции оказались в последо- вательных ячейках стека, начиная с его вершины: Г. Запись в стек Ь-, Ь[— 2°. Запись в стек а; а, Ь\— Теперь можно выполнять операцию сложения; при ее выполнении значения а и Ь будут последовательно выбраны из стека (и пере- даны в регистры /?1 и R2 арифметического устройства), в резуль- тате чего стек снова окажется пустым. Результат выполнения опе- рации сложения (значение (а+Ь)) будет записан в стек: 3°. Сложение; (а+Ь)|— Полученное значение (а+Ь) затем надо возвести в квадрат, для чего воспользуемся операцией умножения. Однако эта операция требует двух аргументов, и оба они должны находиться в стеке, а у нас в стеке пока находится лишь один из них. Поэтому перед операцией умножения выполним операцию дублирования: 4°. Дублирование; (а+Ь), (а+Ь)|— Теперь можно выполнять операцию умножения: 5°. Умножение; (а+Ь)2)— Прежде чем будет выполнена операция вычитания для получения d—(a+b)2, значение d должно быть записано в стек:
4.4} РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 261 6°. Запись в стек d; d, (а+b)2)— 7°. Вычитание; (d—(а+6)2)|— Теперь полученный в стеке результат надо запомнить в ячейке памяти, отведенной для у, и остановиться: 8°. Запоминание у, |— 9°. Стоп. Если принять распределение памяти: адр.а=200, adp.b=203, adp.d—206, adp.z/=300, то программа для УМ-С будет иметь вид: Адрес Команда Примечания Состояние стека ОН 0203 bz>S Ы~ 012 0200 az+S a, bj— 013 2001 Сложение (а-|-Ь)|— 014 2047 Дублирование (а+b), (а+Ь)Н 015 2005 Умножение 016 0206 • d=>S d, (а+Ь)8Н 017 2002 Вычитание (d-(a-H)8)H 020 1300 S => у Н 021 2077 Стоп Можно было бы поступить и иначе — сначала заслать в стек все исходные значения (с учетом порядка их использования в вычис- лениях), а затем выполнять требуемые операции: Адрес Команда П римечания Состояние стека ОН 0 206 d=>S d[— 012 0 203 b=>S b, d|- 013 0 200 a=>S а, b, d\— (а+b), dH 014 2 001 Служение 015 2 047 Дублирование (а+b), (а+b), dH 016 2 005 Умножение (a + b)2, d|— 017 2 102 Обратное Вычитание (d-(a4-fe)*)H 020 1 300 S^y H 021 2 077 Стоп Заметим, что в данном случае мы программировали некоторый законченный этап вычислений — оператор присваивания; в резуль- тате его выполнения мы пришли к тому же состоянию стека, ко- торое было к началу выполнения данной программы — эта весьма важная особенность использования стека очень удобна для про- граммирования. Программа примера 4.2 для УМ-С будет весьма похожа на про- грамму примера 4.1, поэтому приведем ее без дополнительных по- яснений, приняв следующее распределение памяти: адр.Н=200, adp.R =203, adp.V=206:
262 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ 1ГЛ. ♦ Адрес Команда Примечания Состояние стека 011 0 026 я => S я|- 012 0 200 Я=>3 Н, л|— 013 2 047 Дублирование Н, Н, я |— 014 2 005 Умножение Я8, л |— 015 2 005 Умножение (лЯ2) Н 016 0 031 3=>S 3, (лЯ8)Ь 017 0 200 Я=>5 Н, 3, (лЯ8) |— 020 2 004 Деление (Я/3), (лЯ8)Н 021 0 203 R^S R, (Н/3), (лН8)|— 022 2 002 Вычитание (R-H/3), (лЯ8)Н 023 2 005 Умножение (лЯ8(/?-Я/3))Н 024 1 206 s=> V I— 025 2 077 Стоп 026 0 026 1 9 027 2 200 > ЧИСЛО Я « 3 -дГ 030 0 000 1 04 031 0 026 032 0 000 | ЧИСЛО 3 033 0 000 Программа примера 4.3 для УМ-С будет иметь некоторые осо- бенности. При распределении памяти адр.а=200, адр.6=203, адр.</=206 эта программа может быть следующей: Адрес Команда Примечания Состояние стека 011 0 203 &=>S ьн 012 0 200 a-^S а, Ь\— 013 2 002 Вычитание; со:=а<Ь (а~Ь)\— 014 4 023 Переход при о>=false («—Ь)Н 015 0 200 a=>S а, (а—Ь) |— 016 0 203 Ь, а, (а~ Ь)|— 017 2 001 Сложение (а+Ь), (а—Ь)[— 020 2 047 Дублирование (а+Ь), (а+Ь), (а—Ь)\— 021 2 005 Умножение (а+Ь)2, (а—Ь)}— 022 5 030 Безусл. переход (а+Ы, (а—Ь)\— 023 0 032 2=>S 2, (а—Ь)|— 024 0 200 a=>S а, 2, (а—6)|— 025 0 203 6=>S Ь, а, 2, (a—1>)Н 026 2 002 Вычитание (Ь—а), 2, (а—&)}— 027 2 004 Деление (Ь—а)/2, (а—Ь)[— 030 1 206 (а-Ь)\- 031 2 077 Стоп (а-Ь)Н 032 0 024 033 0 000 j число 2 034 0 000
4.4} РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ . 263 Здесь — в отличие от предыдущих программ — стек в итоге не вернулся в исходное состояние, хотя программа задает закончен- ный этап вычислений: в нем остался «мусор», т. е. значение (а—Ь), которое дальше нигде не будет использовано, поскольку это про- межуточный результат данной программы (если ее рассматривать как часть более общей программы). В данном случае это произощло из-за того, что значение (а—Ь) было вычислено только для выра- ботки значения о, и в дальнейшем это значение нигде не было ис- пользовано в качестве аргумента какой-либо арифметической опе- рации. Поэтому на стековых машинах возникает специфичная проблема «чистки» стека от подобного «мусора». В данной программе для этих целей можно было бы после команды 030, реализующей окончательное присваивание S=>y, вставить команду (1000), «за- писывающую» этот «мусор» в ячейку 000, и тем самым обеспечить возвращение стека в исходное состояние. Если вернуться к таблице сравнительных длин программ из раздела 4.4.6, то мы увидим, что — как и следовало ожидать — показатели машины УМ-С на обоих примерах будут даже лучше, чем показатели УМ-П. 4.4.8. Машины с индексными регистрами (УМИР). Во всех рассмотренных ранее программах использовались только простые переменные и отдельные константы (последние можно рассматривать как частный случай простых переменных, значения которых известны заранее и не меняются в процессе вычислений). При распределении памяти для каждой простой переменной от- водится определенная ячейка памяти, и при составлении машинной программы для ссылки на значение этой переменной указывается ее адрес, т. е. номер отведенной для нее ячейки. Поскольку мы предполагаем, что в процессе выполнения программы распределение памяти не изменяется, то адрес каждой простой переменной явля- ется постоянной величиной. Однако мы знаем, что основой почти любой программы является цикл, причем цикл над простыми переменными — это только част- ный (и достаточно редкий) случай цикла. Чаще же всего встречаются циклы над структурами данных, когда какая-либо фиксированная группа команд последовательно применяется к разным элементам некоторой структуры. Но поскольку разные элементы структуры размещаются в разных ячейках памяти, то адрес обрабатываемого элемента становится переменной величиной. В языках программирования элементы структур данных пред- ставляются переменными с индексами. Изменение значения индекс- ного выражения означает, что данная переменная теперь пред- ставляет уже другой элемент структуры. В ЭВМ для этой цели служат индексные регистры. Это, как правило, быстрые регистры, входящие в состав процессора (хотя в принципе в качестве индекс-
264 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ 11л 4 ных регистров можно использовать и обычные ячейки оперативной памяти). Разрядность каждого из этих регистров такова, что в нем можно зафиксировать адрес любой ячейки оперативной памяти. Из регистра с номером 0 обычно всегда выдается нулевой код (в связи с чем регистр с этим номером физически может и отсутствовать). Использование индексных регистров основывается на том, что адрес переменной с индексами распадается на две составляющие: постоянную (которая определяется только произведенным распре- делением памяти, но не зависит от индексов) и переменную (которая зависит от индексов и меняется вместе с изменением их значений). Так, если вектор лс[0:201 размещен в группе ячеек памяти с после- довательными адресами, начиная с адреса X, то адр.х0=Х, adp.Xi=X+l, ..., адр.хг<)=Х+20, и очевидно, что adp.x[v]=X+v, где v — индексное выражение. Здесь X — постоянная, a v — переменная — составляющие пере- менной величины адр.хЫ. Если в машине имеется не один, a k индексных регистров (обо- значим их как массив HP[0:k], ИР[0]=0), то в формате команды для каждого поля адреса предусматривается соответствующее ему поле модификации, в котором указывается номер а одного из ин- дексных регистров. Выполнение любой команды (за исключением, может быть, команд с некоторыми специальными кодами операций) на такой машине происходит так, что фактические обращения к памяти производятся, вообще говоря, не по тем адресам, которые непосредственно указаны в команде, а по исполнительным (или модифицированным) адресам, получаемым по правилу: АИСП—А+ИР[а\ где А — адрес, указанный в команде, а — номер индексного ре- гистра, указанный в соответствующем поле модификации этого адреса, а ЛИСП — исполнительный (модифицированный) адрес, причем получение исполнительных адресов производится аппаратно в устройстве управления. Конечно, значение АИСП должно при- надлежать диапазону адресов на данной машине, а если значение А+ИР1а] выходит за этот диапазон, то обычно от этого значения берется соответствующее количество младших разрядов в качестве исполнительного адреса. Поскольку ИР[0\=0, то в случае указания в поле модификации регистра с номером 0 исполнительный адрес совпадает с адресом, указанным в команде (АИСП=А), т. е. в этом случае модификация адреса фактически не производится. Таким образом, если в какой-либо команде программы должен использоваться переменный адрес, то в поле адреса команды за- писывается его постоянная составляющая (которая известна при произведенном распределении памяти), а в соответствующем поле модификации указывается номер индексного регистра, по кото-
4.4] РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 265 рому должен модифицироваться этот адрес. При этом в программе должно быть предусмотрено, чтобы каждый раз к моменту выпол- нения данной команды в указанном регистре находилась перемен- ная составляющая используемого переменного адреса. Для этих целей в наборе машинных операций предусматриваются специаль- ные операции для работы с индексными регистрами. Рассмотрим теперь конкретную машину с индексными регистрами УМИР-1, за основу которой возьмем одноадресную машину УМ-1. Оперативная память УМИР-1 состоит из 4096ю ячеек с вось- меричными адресами от 0000 до 7777 (как обычно, 0/7[0]=0). Ма- шинное слово содержит 24 разряда. Представление чисел — в форме с фиксированной точкой, рас- положенной вслед за последним разрядом машинного слова, та$. что в УМИР-1 непосредственно представимы только целые числа.’ (каждое число изображается одним машинным словом). В УМИР-1 имеется восемь 12-разрядных индексных регистров, перенумерованных от 0 до 7, причем ЯР[0]^0. Формат команды: 9 р. 3 р. 12 р. где 6 — код операции, а — номер индексного регистра (поле,' в котором указан этот номер, и является полем модификации), А — адрес в команде. Набор операций УМИР-1 содержит в себе все рассмотренные ранее операции УМ-1 (причем в качестве результата операции деления целых чисел пит принимается ближайшее целое к п/т). Дополнительно в этот набор входят следующие операции над ин- дексными регистрами (при выполнении команд с этими операциями модификация адреса в команде не производится): 0=400 — занесение адреса А в регистр а; 0=401 — занесение в регистр а двенадцати младших разрядов слова, выбираемого по адресу А; 0=402 — запоминание содержимого регистра а в двенадцати младших разрядах ячейки с адресом А (в остальные разряды этой ячейки записываются нули); 0=403 — прибавление к содержимому регистра а адреса Л; 0=404 — прибавление к содержимому регистра а числа, изоб- ражаемого двенадцатью младшими разрядами слова, хранящегося по адресу А. (При выполнении всех этих операций значение <в не изменяется.) 0=405 — вычитание из содержимого регистра а адреса А (т. е. ИР[а]:—ИР[а]—А) с выработкой значения <» в зависимости от получаемой при этом разности: <л-.—ИР[а]—Л<0 (если эта раз-
266 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. 4 ность отрицательна, то она запоминается в индексном регистре в виде дополнения до 212). ‘ Теперь регистр команды К можно формально описать как сле- дующую структуру: structured К (integer КОП, integer a, integer А), а стандартный такт работы УМИР-1 (при выполнении операций арифметического типа) можно описать следующим образом: 1°. К:=ОП[С] 2°. С:=С+1 3°' Й\:=ОП1А (К)+ИР\а (/QU 4°. S:=S0R1, где 6 — операция с кодом КОП (К) 5°. ®:=f0(S); <p:=ge(S). Однако в связи с более широким разнообразием имеющихся машин- ных операций в УМИР-1 по сравнению с УМ-1 отклонения от этого типового такта будут более частыми. Например, такт работы ма- шины при выполнении операции с кодом 0=400 (занесение адреса А в индексный регистр), выполняется следующим образом: 1°. К:—ОП1С] 2°. С‘=С+1 4°. ИР[а (К)]:=А (К). Этапы 3° и 5° в данном случае отсутствуют. На примере этой операции мы впервые сталкиваемся с отступ- лением от обычного правила задания в команде какого-либо аргу- мента х путем указания его адреса: по операции 0=400 в индекс- ный регистр с номером а заносится адресЛ, а не содержи- мое ячейки с этим адресом. Таким образом, сам указанный в ко- манде адрес А и является аргументом х данной операции, т. е. А—х. Такое явное задание аргумента (аргументов) для некоторых машинных операций встречается в большинстве реальных ЭВМ. Адрес в команде, который непосредственно, в явном виде пред- ставляет собой аргумент операции, называется непосредственным адресом (или адресом нулевого ранга). Если же адрес А в команде является номером ячейки, отведен- ной для хранения аргумента х, т. _е. А —адр.х, то он называется прямым адресом (или адресом первого ранга). Прямые адреса яв- ляются наиболее типичными. На самом деле возможна ситуация, когда ячейка, предназначен- ная для хранения аргумента (или результата) х, отводится для этой цели уже в процессе выполнения программы, и потому адрес этой ячейки неизвестен при составлении программы. Вспомним, например, такую структуру данных, как очередь, рассмотренную в главе 3, а также способ ее отображения на одно- мерный массив алгола (а тем самым и на массив ячеек памяти ЭВМ): поскольку очередь в процессе выполнения программы может изме- няться, то меняется и номер позиции в массиве (адрес ячейки па-
4.4] РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 267 мяти),.в которой размещается последний элемент очереди—этот элемент часто и является аргументом для той или иной операции. В таких случаях для ссылки на аргумент х при составлении программы отводят определенную ячейку с адресом А, в которой по ходу выполнения программы и должен фиксироваться (в опре- деленных разрядах этой ячейки) истинный адрес этого аргумента х, так что теперь А=адр.(адр.х). Именно так мы поступали при отображении очереди на одномерный массив: номер позиции, в которой хранится последний элемент очереди (или номер следу- ющей по порядку позиции), все время фиксировался в определенной позиции этого массива. Этот адрес А и фигурирует в программе в явном виде, но он используется только для того, чтобы в нужный момент из ячейки с этим номером извлечь прямой адрес аргумента х, т. е. адр.х, который уже и используется для непосредственного обращения к аргументу х. Такой адрес А называется косвенным адресом (в данном случае — адресом 2-го ранга). Так, если адрес какого-либо аргумента х при составлении про- граммы неизвестен, а для фиксации этого адреса (в младших 15 раз- рядах машинного слова) в процессе выполнения программы мы заранее отведем ячейку А =0005, то занесение аргумента х в сумма- тор на У МИРА реализуется парой команд: 401 1 0005; ИР(1].=адр.х 000 1 0000; 8:=0П[ИР[1]] (т. е. 3:=0П[адр.х]) Адрес 0005 здесь является косвенным адресом 2-го ранга. Аналогичным образом можно определить косвенные адреса 3-го ранга, 4-го ранга и т. д. Косвенные адреса высоких рангов возникают, например, при отображении на массив ячеек памяти структур данных, организованных в виде списка, когда отдельные звенья списка связываются в цепочку с помощью соответствующих ссылок. Если данные не имеют признака типа, то при программировании должен быть известен ранг каждого из используемых адресов — для того, чтобы знать, в какой момент при переходе от адреса ранга k к адресу ранга k—1 мы придем к самому аргументу. По этой же причине в программе приходится предусматривать (путем напи- сания соответствующих команд) и каждый из таких переходов. Так, если А =0005 является адресом третьего ранга аргумента к (т. е. А=адр.(адр.(адр.х))), то для пересылки значения х в сумма- тор в программе для машины УМИР-1 придется написать более длинную последовательность команд, например: 401 1 0005; ИР[1]:~адр.(адр. х) 000 1 0000; I . , 100 0 0001; frl ' = adp-x 401 1 0001; ИР[1]:= адр.х 000 1 0000; S: = x
268 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. ♦ Если же данные имеют признак типа (тэг), то задачу выбора значения аргумента по адресу любого ранга можно возложить на аппаратуру, которая будет последовательно понижать ранг адреса , до тех пор, пока очередное выбираемое из памяти машинное слово в этом процессе не будет иметь признак типа, требуемый выполня- емой операцией. В качестве дополнительной иллюстрации использования ин- дексных регистров при программировании рассмотрим простейшие примеры программ для УМИР-1. Пример 4.4. Даны целочисленные векторы х[0:20], (/[0:20]. Требуется получить вектор г[0:20], где z^xi+yt (i=0, 1.....20). Следующая запись на алголе алгоритма решения этой задачи наи- более близка к его машинной реализации (с учетом того, что компо- ненты вектора г можно получать в порядке убывания их номеров): i:=20; L:z[t]:=x[i]+i/[i]; i:=t—1; if t>0 then go to L Примем следующее распределение памяти: adp.xo=010O, adp.Xi=0101......ad/>.x2e=0124, адр.уо=О125, адр.у!=0126, ..., адр.ум=015\, adp.zo=O152, adp.Z!=0153, ..., adp.z2o=O176. Как видно, adp.Xi=0100-H, adp.pj=0125+i, adp.Zi=0l52+i, так что все эти переменные адреса имеют одну и ту же переменную составляющую, равную значению i — это значение будем получать в первом индексном регистре. Для организации циклических вы- числений воспользуемся операцией условного перехода, а для вы- работки значения <о удобно воспользоваться операцией с кодом 6 =405, с помощью которой из содержимого первого индексного регистра будем вычитать единицу, задаваемую непосредственно в виде адреса в команде. Пока текущее содержимое этого регистра будет больше нуля, по этой операции будет вырабатываться значение w=false; как только содержимое этого регистра к моменту вычи- тания из него единицы окажется равным нулю, будет выработано d)z=true. В результате программа будет выглядеть так: ООП 400 1 0024 ИР[1]:= 20 (i:=20) L: 0012 000 1 0100 S : = x[i] 0013 001 1 0125 S : = S-|-£/[i] 0014 100 1 0152 z[/]:=S 0015 405 1 0001 <&:= ИР [)]<!’, ЯР[1]:= ЯР[1]-1 0016 076 0 0012 При os false переход на L 0017 077 0 0000 Стоп В результате выполнения команды ООН этой программы будет получено ЯР[1]=20 (i=20), так что команды 0012, 0013 и 0014 при
4.4] РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 269 первом своем выполнении будут иметь исполнительные адреса 0124, 0151 и 0176 соответственно, и в результате первого выполнения этих команд будет получено Далее по команде 0015 содержимое первого регистра уменьшается на единицу, в резуль- тате чего в нем будет получено значение i=19. Поскольку это значение неотрицательно, то по команде 0015 будет выработано зна- чение (osfalse, и потому по команде 0016 будет осуществлен пере- ход опять на команду 0012. Теперь ЯР[1]=^9, поэтому исполни- тельные адреса у команд 0012, 0013 и 0014 б^ут равны 0123, 0150 и 0175, так что при втором выполнении этих команд будет получено г10=х1в+г/19. Затем по команде 0015 содержимое первого регистра будет опять уменьшено на единицу и по команде 0016 будет осу- ществлен переход на команду 0012 и т. д. Последний раз команды 0012, 0013 и 0014 выполнятся при ЯР[1]=0, так что их исполни- тельные адреса будут равны 0100, 0125 и 0152, в результате чего будет получено zo=xo+i/o. После этого по команде 0015 будет по- лучено ДР(1]—1<0 и впервые будет выработано значение оstrue, при котором команда условного перехода осуществит переход к следующей по порядку команде — команде останова. В этой программе все переменные адреса имели одну и ту же переменную составляющую, поэтому все команды с переменными адресами можно было модифицировать по одному и тому же ин- дексному регистру. В других задачах разные переменные адреса могут иметь различные переменные составляющие, и тогда для управления переменными адресами удобно использовать несколько индексных регистров, как это имеет место в следующем примере. Пример 4.5. Даны целое п (0<п<64) и целочисленные векторы х[0:п], г/[0:п]. Получить z=x0p„+xipn_14-.. .+хпу9. Алгоритм решения этой задачи можно сформулировать на алголе следующим образом: z:=0; i:=0; /:=п; L: z:^=z+x[i]Xy[j]-, if/^0 then go to L Примем такое распределение памяти: адр.г=0376, adp.n=Q377, адр.хо=О4ОО, адр.Xi=0401, ..., адр.хл=0400+п, adp.t/o=O5OO, адр.t/i=0501...adp.t/„=05004-n. Тогда adp.Xi=0400+i, одр. «//=0500+/. Как видно, переменные составляющие этих адресов различны, поскольку значения i и / отличаются друг от друга. Для управления переменными адре- сами текущее значение i будем получать в первом регистре, а те- кущее значение / — во втором регистре. Как видно из алгоритма,
270 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ 1Гл. 4 при каждом очередном выполнении цикла значение / (т. е. содер- жимое ИР12]) уменьшается на единицу. Если для этого использо- вать операцию 0=405, то вырабатываемый по этой операции при- знак <о можно использовать и для управления числом повторений цикла. Тогда программа для УМИР-1 может быть следующей: ООН 000 0 0000 1 ~ (\ 0012 100 0 0376 j- г :=0 0013 400 1 оооа / ЯР[1]: = 0 (i :=0) 0014 401 2 0377 ЯР[2]:=п (/:=п) L: 0015 000 1 0400 S := х [t] 0016 005 2 0500 S: = Sxi/[j] 0017 001 0 0376 S:=S-|-2 0020 100 0 0376 z: = S 0021 403 1 0001 ИРГ1] := ЯРГ11 +1 0 :=i + l) 0022 405 2 0001 <о:=ИР[2]<1; ИР[2]:= ИР[2]— 1 (/:=/-1) 0023 076 0 0015 При ® = false переход на L 0024 077 0 0000 Стоп Модификация адресов с помощью индексных регистров исполь- зуется не только для работы с компонентами массивов, но и для других целей. Например, длина адресного поля в командах УМИР-1, равная двенадцати двоичным разрядам, позволяет непосредственно адресовать только 4096 ячеек. Такая емкость оперативной памяти для решения многих практических задач явно недостаточна: на современных ЭВМ емкость памяти составляет от 32К до 256К и даже более (в качестве единицы емкости памяти часто использу- ется не машинное слово, а более крупная единица, равная 1024 словам — эта единица обозначается через К). Однако при прямой адресации увеличение емкости памяти требует увеличения длины адресных полей в командах. Например, для емкости памяти в 256К адресные поля должны содержать по крайней мере по 18 двоичных разрядов, что приводит к значительным затратам памяти для хра- нения программы. Для сокращения этих затрат на некоторых машинах (в том числе и на широко распространенных в нашей стране машинах серии ЕС ЭВМ) применяется способ адресации, также основанный на модификации адресов с помощью индексных регистров. Идея этого способа состоит в следующем. Пусть память разбита на отдельные сегменты (части) В», Bi, ..., Bm, каждый из которых содержит не более 4096 ячеек. Если ячейка X принадлежит сег- менту В*, то ее адрес можно представить в виде суммы: адр.Х= =адр.Вк+1, где адр.Вк — адрес начала сегмента Вк, а I — отно- сительный номер данной ячейки в этом сегменте. Адрес начала сегмента называют базой, а относительный номер ячейки в данном сегменте — сдвигом (или смещением) относительно этой базы.
4.41 РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 271 Пусть разрядность индексных регистров такова, что в каждом из них можно зафиксировать любую базу (при емкости памяти в 256 К каждый регистр должен содержать по крайней мере 18 дво- ичных разрядов). Если база зафиксирована в одном из индексных регистров, то для ссылки на какую-либо ячейку этого сегмента в поле адреса команды достаточно указать сдвиг этой ячейки отно- сительно базы (для чего требуется меньшее число разрядов), и промодифицировать данный адрес по тому индексному регистру,' в котором зафиксирована база. Если, например, емкость оперативной памяти УМИР-1 сделать равной 256К, индексные регистры — 18-разрядными, а память разбить на сегменты по 4096 ячеек, то передача в сумматор слова из ячейки 232636 может быть осуществлена по команде ООО 1 2636 если в регистре с номером 1 будет зафиксирован адрес 230000, т. е. база того сегмента, которому принадлежит ячейка 232636. Как видно, этот механизм позволяет при большой емкости памяти обой- тись сравнительно короткими адресными полями в командах. Чтобы наряду с базированием обеспечить использование индекс- ных регистров и для эффективного управления переменными ад- ресами при работе с компонентами массивов, обычно предусмат- ривается возможность модификации адресов в команде одновременно по двум индексным регистрам. В этом случае при работе с мас- сивами в одном регистре фиксируется база того сегмента памяти, в котором размещен массив, в поле адреса команды указывается постоянная составляющая переменного адреса, равная сдвигу на- чала массива относительно базы, а в другом регистре помещается переменная составляющая этого адреса. В качестве иллюстрации рассмотрим учебную машину УМИР2-1, которая является модификацией УМИР-1 и в которой модификация адреса производится по двум индексным регистрам. Оперативная память УМИР2-1 содержит 256К ячеек той же раз-* рядности, что и в УМИР-1. Она разбита на 512 сегментов Bj по 512 ячеек с такими адресами: аЭр.В0=000000, адр.В1=001000 и т. д. В соответствии с этим поле адреса команды в УМИР2-1 содержит 9 разрядов. В машине УМИР2-1 имеется восемь 18-разрядных индексных регистров. Формат команды: 0 al а2 А 9 р. 3 р. 3 р. 9 р. Первое поле модификации служит для указания номера al реги- стра, по которому производится базирование, а второе поле моди- фикации служит для индексации с помощью регистра а2.
272 ЭЛЕКТРОННО-ВЫЧИСЛИТЕЛЬНЫЕ МАШИНЫ [Гл. ♦ Регистр команды К в УМИР2-1 формально можно описать как структуру: structured К (integer КОП, integer al, integer а2, integer А), а в стандартном такте работы одноадресной машины этап 3° для УМИР2-1 имеет следующий смысл: 3°. Р\:—0П[А+ИР[а1 (К)]+ИР[а2 (К)]]. Наличие в команде двух полей модификации открывает богатые возможности для создания набора операций над индексными реги- страми. Однако ради простоты мы сохраним в УМИР2-1 те же операции с кодами 400—405, что и в УМИР-1, причем в качестве а примем регистр с&. Естественно, что в операциях 401; 402 и 404 теперь используются не 12, а 18 младших разрядов слова О/71Л). При использовании операций над индексными регистрами сле- дует учитывать, что в УМИР2-1 базирование (т. е. модификация адреса А по регистру al) производится всегда, независимо от операции, используемой в команде; индексация же (т. е. модифи- кация адреса А по регистру а2) в случае операций над индексными регистрами — как и в УМИР-1 — не производится. Для иллюстрации базирования и индексации рассмотрим ту же задачу, что и в примере 4.5, но с другим распределением памяти: adp.Xt=143100+1, адр.^=610125+1. Программу, как обычно, разместим начиная с ячейки 000011. Таким образом, здесь будут использоваться три сегмента памяти с начальными адресами 000000, 143000 и 610000. Для базирования первого из них будем использовать индексный регистр с номером 0, в котором, как обычно, всегда хранится нуль. После занесения двух других баз в регистры 3 и 4 наша программа практически не будет отличаться от программы из примера 4.5, поэтому приведем ее без дальнейших пояснений: ОН 012 013 014 015 016 L: 017 020 021 022 023 024 025 026 027 030 401 401 000 100 400 401 000 005 001 100 403 405 076 077 001 006 0 0 0 0 0 0 3 4 0 0 0 0 0 0 4 1 3 4 0 0 1 2 1 2 0 0 1 2 0 0 3 0 027 030 000 376 000 377 100 125 376 376 001 001 017 000 000 000 ДР[3]: = 143000 (база 1) ДР[4]: = 610000 (база 2) | z : = 0 ДР[1]: = 0 (!: = 0) ЯР12]:=л (/: = «) S := х[х] S:=Sxi/[/] S: = <S + z z-.= S ЯР[1]:=ЯР[1] + 1 <о:=ЯР[2]<1; 0Р[2]:= ДР[2]-1 При и = false переход на L Стоп База 1 (= 143000) База 2 (=610000)
4.41 РАЗНООБРАЗИЕ СИСТЕМ КОМАНД ЭВМ 273 В этой программе — из-за различия в разрядности индексных регистров и длине поля адреса в командах — в некоторых случаях вместо операции занесения в индексный регистр адреса пришлось использовать операцию занесения в регистр содержимого ячейки памяти. Вообще говоря, двойная индексация может использоваться не только для целей базирования, но и для других, самых разнообраз- ных способов управления переменными адресами. Так, если пере- менная составляющая адреса состоит из двух слагаемых, которые изменяются независимо друг от друга, то удобно значение каждого из них иметь в отдельном регистре и адрес в команде, который представляет постоянную составляющую переменного адреса, мо- дифицировать по каждому из этих регистров. Следует заметить, что индексные регистры’при программиро- вании находят весьма широкое применение, тогда как число таких регистров в машине обычно весьма ограничено. Поэтому довольно часто один и тот же регистр используется в программе для различ- ных целей, например для модификации с его помощью различных переменных адресов, имеющих разные переменные составляющие. В таких случаях значения, которые должны использоваться с по- мощью индексных регистров, обычно получаются в ячейках памяти, а в соответствующий момент нужное значение передается в индекс- ный регистр для использования. В частности, нет никакой необхо- димости все используемые в программе базы сегментов одновре- менно хранить в инд^сных регистрах — в принципе для целей базирования можно обойтись одним индексным регистром, если в программе предусмотреть своевременное изменение его содержи- мого, для чего потребуется выполнение соответствующих вспомо- гательных команд. Однако при разумной организаций программы и соответствующем распределении памяти изменение базы прихо- дится делать сравнительно редко.
Глава 5 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ ДЛЯ ЭВМ В главе 4 мы дали общую характеристику набора машинных операций, рассмотрели некоторые конкретные операции из этого набора, а также простейшие приемы программирования в наиболее типичных случаях — вычисления по формулам и разветвление вычислительного процесса — для разных типов ЭВМ. Цель настоящей главы — более полное рассмотрение набора машинных операций и приемов составления программ для ЭВМ. При этом нам придется иметь дело с таким понятием, как «ка- чество программы». Это понятие трудно определить формально, поскольку в него включается несколько характеристик программы, из которых можно выделить три наиболее существенных показателя: а) объем памяти машины, необходимый для выполнения про- граммы; б) «быстродействие» программы, т. е. время, затрачиваемое дан- ной ЭВМ на выполнение программы (которое условимся измерять в числе тактов работы машины); в) простота структуры программы, от которой зависит удобство дальнейшей работы с нею (проверка программы, внесение в нее тех или иных изменений и т. д.). Как уже отмечалось, для решения подавляющего большинства задач можно предложить много различных вариантов алгоритмов (а следовательно, и программ), отличающихся друг от друга по перечисленным выше показателям. Вообще говоря, было бы ес- тественно считать наилучшим такой вариант программы, который требует наименьших затрат памяти и машинного времени, а также имеет самую простую структуру программы по сравнению с другими возможными вариантами. Однако указанные выше показатели часто противоречат друг другу. Например, быстродействие про- граммы во многих случаях можно повысить за счет увеличения числа команд, т. е. за счет увеличения требуемого объема памяти, а число команд в программе часто можно уменьшить за счет услож- нения ее структуры и т. д. Поэтому вопрос о том, какому варианту программы следует отдать предпочтение (а следовательно, и во- прос о качестве программы), обычно решается, исходя из конкретных условий — из характера решаемой задачи и особенностей исполь- зуемой ЭВМ. Если, например, память используемой ЭВМ сравнительно мала, а алгоритм решения данной задачи по существу является сложным
6.1] СИМВОЛИЧЕСКОЕ ПРОГРАММИРОВАНИЕ 275 и громоздким, и (или) при его выполнении используется большое количество данных, то основное требование к программе будет за- ключаться в сведении к минймуму объема требуемой памяти; если же специфика задачи состоит в большом объеме вычислений при сравнительно небольшом объеме требуемой памяти, то основное требование к программе будет заключаться в максимальном повы- шении ее быстродействия; если речь идет о задаче, которая еще не совсем точно поставлена или методика решения которой еще не достаточно отработана, то решающее значение может иметь мак- симальная простота структуры программы — с тем, чтобы облег- чить в дальнейшем внесение в нее различных изменений, и т. д. В дальнейшем мы, как правило, не будем говорить об алгорит- мах в целом, а ограничимся рассмотрением их наиболее типичных фрагментов. 5.1. Символическое программирование Прежде чем переходить к изучению основных правил и приемов программирования, познакомимся с одним из методов составления программ, который называется программированием в символиче- ских обозначениях (или, короче, символическим программированием). Вернемся еще раз к составленным нами в главе 4 программам. В них каждая команда (и константа) записывалась непосредственно в виде машинного слова, в цифровой форме: требуемая машинная операция в команде задавалась в виде кода этой операции (ее по- рядкового номера в упорядоченном перечне машинных операций), а операнды задавались путем указания их истинных адресов, т. е. номеров ячеек памяти, в которых эти операнды будут храниться в процессе выполнения программы машиной. Программы, состав- ленные в таком виде, называются программами в машинном коде (или абсолютными программами) — только такие программы ма- шина и способна выполнять непосредственно. Составляя программу в машинном коде, мы на самом деле ре- шаем одновременно две довольно различные задачи: планиро- вание нужной последовательности машинных команд, а также их цифровое кодирование. Это обстоятельство вызывает целый ряд трудностей как при непосредственном составлении про- граммы, так и при дальнейшей работе с нею. В чем состоят эти трудности? Во-первых, в определении истинных адресов. С одной стороны, к началу составления программы уже должно быть произведенб распределение памяти; с другой стороны, для выполнения этой работы еще нет достаточной информации, так как еще неизвестно, сколько команд и констант будет содержать программа, сколько ячеек понадобится для хранения промежуточных результатов, в какой именно ячейке будет размещена та или иная команда или
276 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 константа. Поэтому к началу составления программы приходится делать лишь ориентировочное распределение памяти, уточняя его по ходу составления программы. При таком ориентировочном рас- пределении памяти весьма вероятны просчеты, что может привести в дальнейшем к необходимости переписывать программу заново с учетом сделанных изменений в распределении памяти. По этой же причине — как это имело место даже в рассмотренных ранее простейших программах — целый ряд команд невозможно сразу записать в окончательном виде: некоторые поля адресов прихо- дится временно оставлять пустыми, заполняя их по мере уточнения места в памяти тех или иных машинных слов (констант и команд, которые будут написаны позже, но на которые имеются ссылки в уже написанных командах), что вынуждает многократно возвращать- ся к написанным ранее командам. Во-вторых, значительные неудобства возникают при внесении в программу изменений и исправлений, особенно в том случае, если они связаны с изменением распределения памяти. Действительно, рассмотрим программу примера 4.3 для УМ-3, реализующую оператор присваивания y:=ita<Zb then (а+b) f 2 else (b—a)/2 и допустим, что на самом деле надо было запрограммировать опе- ратор присваивания y:=na<b then (a+t>) t 3 else (b—a)/2 Это значит, что при составлении программы была допущена ошибка. Для ее устранения можно, например, после команды 0014, получа- ющей в ячейке 0202 значение (а+b)2, вставить команду 005 0202 0001 0202; y:=yXrl Поскольку эта команда должна выполняться вслед за командой 0014, то ее нужно поместить в ячейку 0015, однако в этой ячейке была размещена команда безусловного перехода. Чтобы освободить ячейку 0015 для вставляемой команды, надо все строки программы, начиная с команды 0015, сдвинуть на одну ячейку «вперед», а в освободившуюся ячейку 0015 вставить пропущенную команду. Но при этом у всех сдвигаемых строк изменяются адреса, а на них имеются ссылки как в командах, сохранивших свое место в памяти (команда 0012), так и в командах, изменивших свое расположение в памяти (прежние команды 0015 и 0017). Следовательно, эти ко- манды тоже придется корректировать в связи с. изменившимся распределением памяти. Как видно, даже такое простое изменение й программе, как вставку одной команды, нельзя сделать «локаль- но» — на самом деле придется просмотреть все команды программы, выявить в них и соответствующим образом скорректировать все адреса, с помощью которых были сделаны ссылки на слова, изме- нившие свое место в памяти в результате внесенного изменения в программу. Очевидно, что это весьма трудоемкая работа, требую-
S. и СИМВОЛИЧЕСКОЕ ПРОГРАММИРОВАНИЕ . 277 щая большого внимания и аккуратности, при выполнении которой можно легко ошибиться и тем самым внести в программу дополни- тельнее ошибки. В-третьих, как для составления программы, так и для после- дующей работы с нею (проверка, внесение исправлений и т. д.) важное значение имеют наглядность и простота понимания про- граммы. Запись же программы в цифровой форме этому требова- нию совершенно не удовлетворяет. И, наконец, довольно трудно запомнить цифровые коды всех операций, поскольку набор машинных операций обычно достаточно велик. Для устранения указанных выше трудностей и используется метод программирования в символических обозначениях. Этот метод заключается в том, что при написании каждой команды в ее полях соответствующая информация задается не в виде машинного кода операции и истинных адресов операндов, а в виде их условных, символических обозначений. Эти обозначения выбираются таким образом, чтобы, с одной стороны, точно определить содержание каждой команды и обеспечить наглядность и простоту ее пони- мания, и с другой стороны — сделать запись программы незави- симой от конкретного распределения памяти. Так, для задания машинной операции, которая должна быть выполнена по данной команде, вместо цифрового кода операции указывается ее символические обозначение, которое проще запо-' минается и обеспечивает достаточную наглядность, например Д (дет ление), И (логическое умножение), ПБ (переход безусловный) и т. п. Для указания в командах требуемых операндов при символи- ческом программировании вместо адресов операндов употребля- ются имена этих операндов — это позволяет не только сделать программу независимой от конкретного распределения памяти, но и существенно улучшить наглядность программы. Сказанное выше относится не только к операндам, участвующим в выполне- нии какой-либо операции в арифметическом устройстве, но также и к самим командам: команды символической программы просто выписываются подряд без указания номеров ячеек памяти, в ко- торых они будут храниться. При этом отдельные команды снаб- жаются метками, которые используются для ссылок на эти команды (например, в командах перехода). С этой целью метками снабжа- ются и константы, запасаемые в программе. В символических программах могут использоваться и допол- нительные выразительные средства. Если, например, в программе приходится (или удобно) иметь дело с «массивом памяти», т. е. с группой ячеек с последовательными адресами, то этому массиву можно дать свое имя, например А, и условиться считать, что это имя является и именем начальной ячейки данного массива, а сле- дующие по порядку ячейки обозначать через А 4-1, А 4-2, А 4-3
278 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. S ит. д., используя для записи чисел привычную десятичную систему счисления. Для задания информации о том, какие адреса в команде должны модифицироваться по индексному регистру, также при- нимается определенное соглашение, упрощающее задание этой информации и улучшающее наглядность программы. Можно, на- пример, условиться после каждого символического адреса, подле- жащего модификации, указывать взятый в круглые скобки номер индексного регистра, по которому должна производиться модифи- кация; если же для модификации всегда используется один и тот же индексный регистр (как, например, в УМИР-1), то можно ус- ловиться опускать в этой записи номер регистра, например: X (), Д+1 () и т. д. Для удобства составления символических программ можно принять и еще некоторые соглашения. Например, довольно часто возникает необходимость зарезервировать в программе одну или несколько групп ячеек памяти для их использования в качестве рабочих ячеек. При программировании в машинном коде в подоб- ных случаях приходится каждый раз записывать нужное количе- ство каких-либо машинных слов. Вместо этого можно условиться задавать лишь информацию о необходимости резервирования па- мяти, например в виде символической «команды», в поле операции которой записывается определенное символическое обозначение данной «операции» резервирования (например, ПАМ), а в адрес- ном поле указывается число резервируемых ячеек. Например, символическая команда R-.ПАМ 5 означает, что в данном месте программы резервируется группа из 5 ячеек памяти с последовательными адресами и первая из этих ячеек снабжается меткой /?. Можно также условиться ввести в употребление символическую запись запасаемых в программе кон- стант: каждую константу будем записывать по формату команды, в поле операции которой указывается специальный мнемонический код операции КОНСТ (который и означает, что в данном случае речь идет о константе), а в адресном поле такой «команды» задается сама константа в привычном для человека виде. Для констант, представляющих собой вещественное число, можно условиться само число записывать, например, так же, как в алголе: ПИ.КОНСТЪЛЬ В качестве иллюстрации составим символическую программу для УМ-3, реализующую оператор присваивания z/:=if a<b then (a+b) t 2 else (b—a)/2 Если для операций управления принять следующие символические обозначения (в скобках указаны цифровые коды операций): ПБ (56) — переход безусловный, ПЕ (36) — переход по <о=1 (true),
5.1] СИМВОЛИЧЕСКОЕ ПРОГРАММИРОВАНИЕ 279 ПУ (76) — переход по g>=0 (false), СТОП (77) — останов, то символическая программа будет выглядеть следующим образом: НАЧ: В А В 9 <»:== а < b ПУ — ELSE » переход к ELSE при а^Ь с А В R; У R R У; }^ = (а+Ь)* ПБ — кон 9 Безусловный переход к КОН ELSE: В в А R; Д R ДВА У; ^у = (Ь — а)12 КОН: стоп — • ' » Стоп ДВА: конст 2.0 / R: ПАМ 1 Как видно, эта программа обладает гораздо большей наглядностью, чем аналогичная программа в машинном коде. Несмотря на исполь- зование символических обозначений, каждая символическая ко- манда однозначно определяет и машинную операцию, которая должна быть выполнена, и все операнды. Кроме того, эта программа никак не связана с конкретным распределением памяти. Поскольку программа фактически уже составлена, то теперь имеется информация, на основании которой можно произвести распределение памяти. Пусть для нашей символической программы принято следующее распределение памяти: 7=0011, адр. А =0023, адр. В=0024, adp.Y=0023. Тогда для получения абсолютной программы достаточно Закоди- ровать символическую программу в цифровой форме: каждое сим- волическое обозначение операции заменйть на соответствующий цифровой код операции (для чего удобно иметь однажды состав- ленную таблицу такого соответствия), а каждый символический адрес заменить на соответствующий ему истинный адрес. Если учесть, что для нашей программы ELSE=HA4+3, K0H=HA4+T, ДВА=НАЧ+8 и /?=ЯЛ7+9, то при кодировке надо воспользо- ваться следующей таблицей соответствия между символическими и истинными адресами: Символический адрес Истинный адрес НАЧ ELSE КОН ДВА R А В Y Константу, представляющую шинного слова 002 4000 0000 0000, ООН 0016 0020 0021 0022 0023 0024 0025 два, надо записать в виде ма-
280 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 а поскольку исходное содержимое рабочей ячейки /? может быть произвольным, то можно в соответствующей строке машинной программы записать любое слово, либо вообще не записывать эту строку, если это последняя строка программы. Чтобы использовать символическую программу при другом распределении памяти, достаточно заново произвести ее кодиров- ку — сама же символическая программа остается без изменения. Поскольку символическая программа не зависит от распреде- ления памяти, то в нее легко вносить изменения. Например, для внесения в нашу программу упомянутого ранее исправления, чтобы программа реализовала оператор присваивания z/:=if a<.b then (a+b) f 3else (b—a)/2 достаточно вслед за символической командой У R R Y вставить символическую команду У Y RY причем эта вставка не потребует корректировки каких-либо других команд символической программы, т. е. это исправление имеет действительно локальный характер. Поскольку в результате вне- сенного исправления будет получена другая символическая про- грамма, то, естественно, ее кодировка должна быть сделана заново. Следует обратить внимание на то, что кодировка символиче- ской программы представляет собой чисто техническую работу, которую можно выполнять по совершенно формальным правилам, если строго определить синтаксис и семантику языка символиче- ских обозначений. Другими словами, кодировку можно произво- дить по определенному алгоритму, а следовательно, выполнение этой работы можно передать самой машине с помощью однажды составленной специальной программы. В дальнейшем мы рассмот- рим эту задачу более подробно. Итак, теперь составление программы на языке машины можно разделить на два самостоятельных этапа: символическое програм- мирование и кодирование, а весь процесс программирования можно изобразить в виде следующей схемы: Получение абсолютной программы
5,2] ХАРАКТЕРИСТИКА УМИР-3 28] В дальнейшем — при рассмотрении основных приемов програмг мирования — мы будем ограничиваться составлением символи- ческих программ, поскольку их кодирование представляет из себя формальный и очевидный этап работы, который к программиро- ванию по существу дела уже не относится. 5.2. Характеристика УМИР-3 Из примеров программ, приведенных ранее для ЭВМ разных' типов, видно, что программирование на каждую конкретную ма- шину имеет свои специфические особенности. Однако эти особен- ности носят не принципиальный, а скорее технический характер — на самом деле основные правила и приемы программирования являются общими для всех ЭВМ. Поэтому при обучении програм- мированию достаточно овладеть этими правилами и приемами применительно к какой-либо одной конкретной машине: последую- щий переход к программированию для другой машины обычно не вызывает особых затруднений. В связи с этим при дальнейшем изложении все примеры программ, иллюстрирующих те или иные моменты программирования, мы будем приводить только для одной из конкретных машин. Вопрос о том, какую именно конкретную ЭВМ выбрать для целей обучения, не является принципиальным. Мы в дальнейшем будем в основном ориентироваться на учебную трехадресную ма- шину с одним индексным регистром — УМИР-3, поскольку трех- адресная система команд наиболее естественна и потому удобна для первоначального обучения программированию. УМИР-3 близ- ка к реальным отечественным машинам типа М-20 (М-20, М-220, БЭСМ-4 и др.). В связи с тем, что по этим машинам издано довольно много различных пособий, доступных для широкого круга читателей, мы в основном сохраним в УМИР-3 характеристики этих реальных машин, включая и коды операций (хотя некоторые из достаточно специфичных операций упомянутых выше машин мы рассматривать не будем). Кроме того, мы примем более простой способ представ- ления чисел в УМИР-3. Эти обстоятельства читателю следует иметь в виду при использовании литературы по машинам типа М-20. Заметим, что с машиной УМИР-3 мы частично уже знакомы, по- скольку рассмотренная ранее учебная машина УМ-3 весьма близка к ней. 5.2.1. Общая характеристика УМИР-3. Память содержит 4096 ячеек с восьмеричными адресами от 0000 до ПП. Ячейка с адресом 0000, как и во многих реальных машинах, является особой: при чтении из нее всегда выдается машинное слово, содержащее нули во всех разрядах («нулевое» слово) — это позволяет не запасать такое слово, используемое почти в каждой
282 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. S реальной программе, в качестве константы. Заметим, что эта ячейка на самом деле может и не существовать — в этом случае факти- ческие обращения к этой ячейке не производятся, а при заказе на чтение из нее аппаратно выдается нулевое слово. Машинное слово состоит из 45 двоичных разрядов, которые считаются перенумерованными справа налево от I до 45: e4S8448W • -вг8!. При составлении программ машинное слово условимся записывать в виде последовательности из 15 восьмеричных цифр. Представление чисел — в форме с плавающей запятой. Каждое число х=М-2? (х#=0) изображается .одним машинным словом, разряды которого распределяются следующим образом: 45 44 1 1 I 43 1 42 •. • | 37 36 ... 1 * |Д| |М| где — знак числа, ер — знак порядка (знак + изображается цифрой 0, а знак — изображается цифрой 1), р — порядок числа <0sCI,o|<63), а М —его мантисса. Левый < 15-й) разряд слова при изображении чисел не исполь- и его значение не влияет на величину числа (условимся ч изображении чисел в этом разряде записывать цифру 0). Число пуль изображается нулевым машинным словом (в45=.• • = — н,—0). Машина в основном оперирует с нормализованными чис- лами (0.5^1Л11<1), но может оперировать и с ненормализованными числами. Примеры изображения нормализованных чисел в виде машинных слов УМИР-3: 12-|- = (.110011)2-2<: 004 6300 0000 0000 — 1 = (—.!),.21 : 201 у1000 0000 0000 3/16 = (.11)а-2-» : 102 6000 0000 0000 Система команд — трехадресная. В УМИР-3 имеется два 12-раз- рядных индексных регистра, которым присвоены номера 0 и 1, причем /УР(О]=Э, так что физически имеется только один индексный регистр с номером 1, который будем обозначать через F. Каждая команда изображается одним машинным словом, раз- ряды которого распределяются следующим образом: 45 44 43 42 | ... 37 36 • •. 25 24 13 12 1 *1 л2 л3 е 41 42 АЗ
6.21 ХАРАКТЕРИСТИКА УМИР-3 283 где 0, А], А2 и АЗ имеют прежний смысл, а л<, nt и я» (каждое из которых может иметь значение 0 или 1) указывает номер индексного регистра, по которому модифицируются адреса Л1, А2 и АЗ соот- ветственно. Регистр команды К машины УМИР-3 можно описать как сле- дующую структуру structured К (integer л1, integer л2, integer лЗ, integer КОП, integer Л1, integer А2, integer ЛЗ), а стандартны”! такт работы УМИР-3 можно описать так: Г. К:—€л1\С\\ I . С:=С+1; Л1ЯС77:=Л1+7/Р[л11; Л2ЯСЛ:=Л2+ИР[л21; ЛЗЛ<н:=ЛЗ+ЯР1л31 3°. П\.^0П1А\ИСП]-, 4°. К2:=ОП[А2ИСЛ]; Г. S'.=R13R2 (/. И:=/0(5); <p:=g0(S); 7°. ОП[АЗИСП]:=8 Поскольку //Р[0)==0, то фактическая модификация каждого из адресов в команде может производиться только по индексному регистру F. Поэтому каждое из Ль л2 и л8 можно трактовать как признак модификации соответствующего адреса в команде по ре- гистру F: если л{ (t=l, 2, 3) равно 1, то соответствующий адрес в команде должен модифицироваться по регистру F; в противном случае такая модификация не производится. Таким образом, можно считать, что получение исполнительных адресов в УМИР-3 производится по правилу: А\ИСП~ (ЛЦ-ягЛоммй**, А2ИСП= (Л 2+n2-F)mod212, АЪИСП = (ЛЗ+л8-Г)тоб2‘2. При записи команд тройку двоичных разрядов Л1Л2л3 условимся записывать одной восьмеричной цифрой л. Рассмотрим, например, команду 501 0103 0001 0200 у которой л=5#=101г (т. е. Л1 = 1, л2=0, л8=1), а 0=01 —код операции сложения. Если к моменту выполнения этой команды в индексном регистре F зафиксировано число 00028, то первое слагае- мое будет выбрано из ячейки с адресом А\ИСП= (0103+1 «0002) mod212=0105, второе слагаемое — из ячейки с адресом А2ИСП=* ~ (0001+0*0002)mod212=0001, а полученная сумма будет записана по адресу АЗИСП= (0200+1 -0002)mod212=0202. Если же к моменту выполнения этой команды в регистре F на- ходится число 7776g, то А1ИСП= (0103+7776)mod212= (10101) mod2l2=010l, Л 27/07=0001, АЗИСП= (0200+7776)mod212=> = (10176)mod212=0176. Так что на самом деле в индексном реги- стре можно фиксировать целые числа любого знака, только неот-
284 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5' рицательные числа представляются в нем непосредственно, а от- рицательные — в виде дополнения до 212, т. е. отрицательное чис- ло — |Л| представляется в виде 21S — |£|. Набор машинных операций УМИР-3 включает в себя, в частно- сти, все операции, которые были введены на УМ-3. Код каждой из этих операций в УМИР-3, записываемый двумя восьмеричными цифрами, совпадает с двумя младшими восьмеричными цифрами кода операции для УМ-3 (старшая восьмеричная цифра в кодах всех рассмотренных операций на УМ-3 была равна нулю). Машин- ные операции УМИР-3 мы будем вводить в употребление по мере необходимости, а сейчас рассмотрим операции арифметического типа и основные операции над индексным регистром. Арифмети- ческие операции УМЙР-3 можно разбить на две группы: основные арифметические операции и операции над порядками. 5.2.2. Основные арифметические операции. К основным арифметическим операциям УМИР-3 относятся сложение, вычитание, вычитание абсолютных величин, деление и умножение, смысл которых очевиден. Сведем эти операции в таблицу, приняв следующие обозначения: х=Х-2Р— число, выбираемое по адресу А\ИСП, y=Y'2ч — число, выбираемое по адресу А2ИСП, z—Z-2r — результат, записываемый по адресу АЗИСП. В таблице для каждой операции приведены ее символическое обо- значение (этими обозначениями мы будем пользоваться в дальней- шем) и ее цифровой код; кроме того, указаны логические выраже- ния, значения которых присваиваются признакам и <р: Название операции е Результат со <р Сложение С(01) z:=x+# z<0r^64 * Вычитание 5(02) z:=x—у Вычитание абсолютных величин ВА(03) г:=|х|—|у| г<0 — Деление Д(04) г:= xly г 64 V$/=0 V |Х|^2|У| Умножение У (05) г:= хХу 1 г^64 Аргументами этих операций являются вещественные числа, представленные в форме с плавающей запятой. Если получаемый результат не может быть изображен точно, т. е. для изображения его мантиссы требуется больше 36 разрядов, то при выполнении перечисленных выше операций результат округляется, а затем нормализуется. Большинство из этих операций имеют модификации (каждая со своим кодом операции), при которых блокируется (не выполняется) либо округление, либо нормализация, либо одно- временно и то, и другое. Коды этих операций получаются из кода основной операции по определенному правилу: этот код увели- чивается на 20в, если блокируется округление, на 40, — если бло- кируется нормализация, и на 60, — если блокируются и округ-
5.2] ХАРАКТЕРИСТИКА УМИР-3 285 ление, и нормализация, так что соответствующие модификации операции сложения, например, имеют коды 21, 41 и 61. При сим* волическом обозначении таких операций условимся к обозначению основной операции дописывать справа нужную комбинацию букв, указывающих, что именно блокируется: О (округление), Н (нор- мализация), ОН (округление и нормализация), например С, СО, СН, СОН. Что касается целых чисел, то они считаются частным случаем вещественных чисел. Однако при программировании целые числа иногда удобнее представлять иначе, чем вещественные числа. В таких случаях принимается некоторое соглашение о том, как целые числа должны изображаться в виде машинных слов. Эти соглашения могут быть различными — здесь следует учитывать как наиболее типичные случаи использования целых чисел, так и возможность достаточно простого преобразования вещественных значений в целочисленные и обратно. Чаще всего целые числа используются в качестве значений индексных выражений. Учитывая специфику использования ин- дексного регистра, целые числа в’УМИР-3 удобно представлять в виде ненормализованных чисел с плавающей запятой — так, чтобы целое число изображалось в виде количества единиц в поле второго адреса команды, для чего целое число должно иметь порядок р=24 (в предположении, что целое число Л удовлетворяет условию 0^7г<212). Например, целое число 19 при таком способе будет представлено машинным словом 030 0000 0023 0000 Упомянутые выше модификации арифметических операций могут использоваться, в частности, для преобразования вещественных чисел в целые и обратно. Если при составлении программы не возникает необходимости подобного рода преобразований, то можно условиться при представлении целых чисел во всех разрядах ма- шинного слова, кроме поля второго адреса, записывать нули — в этом случае число 19 будет представлено машинным словом 000 0000 0023 0000 Этот способ представления целых чисел мы и будем использовать в дальнейшем, если не будет оговорено противное. , 5.2.3. Операции над порядками. К этой группе относятся четыре операции над порядками чисел, которые удобно использовать для умножения (деления) некоторого числа х на число вида 2т (т — целое), что сводится к прибавлению (вычитанию) значения т к порядку (из порядка) числа х. В операциях «сложение порядка с адресом» и «вычитание из порядка адреса» значение т задается в команде в явном виде первым исполнительным адресом, причем отрицательное число т задается в виде дополнения до 212, т. е. в этом случае А1ИСП—2Ы—|/и|.
286 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ (Гл. "5 Таким образом, в командах с этими операциями первый адрес в Команде является непосредственным адресом (адресом нулевого ранга). В операциях «сложение порядка с порядком» и «вычитание из порядка порядка» в качестве т принимается порядок числа, выби- раемого по адресу А1ИСП. Во всех четырех операциях основной аргумент, значение кото- рого после соответствующего изменения его порядка принимается в качестве результата, выбирается по адресу А2ИСП. Сведем эти операции в таблицу, обозначив через т целое число, задаваемое непосредственно в виде А\ИСП. Название операции е Результат co Сложение порядка с адресом СПА(М>) Z\=Y\ r.— q+m I Сложение порядка с порядком СПП(2С,) Z-.=Y*t r:=q-[-p I Вычитание из порядка -адреса В/7Л(46) Z:—Y; r:=q—m I Вычитание из порядка поряда z//7/7(66) Z:=Y; r.= q—p 1 Если, например, значение и имеет порядок рв=3, а значение v — порядок pv=—3, то в результате выполнения любой из команд СПА 3 v w ВПА 7775 v w СПП и v w ВПП v v w будет получено w—8v. При символическом программировании значения аргументов, которые задаются в виде непосредственных адресов, удобно запи- сывать в соответствующем поле адреса команды в привычной для человека десятичной системе счисления, допуская при этом и за- пись знака у числа. Например, вычисления по формуле у=х/\6 можно задать символической командой СПА —4 х у При записи символических команд мы будем допускать использо- вание и восьмеричных адресов, заключая их в строчные кавычки, ' например ‘7775’. 5.2.4. Операции над индексным регистром. В эту группу входят две операции, по каждой из которых те- кущее содержимое регистра F можно запомнить в какой-либо ячейке памяти и занести в него новое содержимое. В одной из этих опе- раций новое значение F задается непосредственным адресом в ко- манде, а в другой — выбирается из памяти. Засылка в регистр F адреса. РА (52). При выполнении команды вида п 52 Л1 А2 ЛЗ
6.31 ПРОГРАММИРОВАНИЕ ВЫЧИСЛЕНИИ ПО ФОРМУЛАМ 287 в ячейку памяти с адресом АЪИСП записыватся слово вида АЪИСП: 052 0000 А1ИСП 0000 а в регистр F заносится адрес А2ИСП. Таким образом, если в команде с операцией 6=52 задано nv=f и А 1=0, то А\ИСП=Р, и по адресу АЗИСП запоминается (в поле второго адреса) текущее значение F. Запоминание А1ИСП вместо непосредственного значения F позволяет в общем случае запомнить значение F+1 (при nt=l и Л1=/). Запоминаемое по адресу АЗИСП машинное слово на самом деле является командой с тем же кодом операции (52), второй адрес в которой и представляет собой запоминаемое значение. Таким образом, последующее выполнение этой команды влечет за собой восстановление в регистре F запомненного значения (F или F+1). Засылка в регистр F слова. PC (72). Эта операция отличается от предыдущей только тем, что в регистр F заносится слово, выби- раемое по адресу А2ИСП, а точнее — второй адрес, указанный в этом слове, если его трактовать как команду. Значение ® при выполнении этих операций не изменяется. ' Например, символическая крманда РА 0 0 О задает действие F:=0; команда РА 0( ) 1( ) R задает действия /?:= (РА 0 F 0) и а команда РА 0 *7777’( ) 0 задает действие F:=F—1. Операция РА обычно используется в том случае, когда зано- симое в регистр F значение (или значение, на которое надо увели- чить или уменьшить текущее значение F) известно при составлении программы, а операция PC — когда это значение вырабатывается в процессе выполнения программы. 5.3. Программирование вычислений по формулам При решении на ЭВМ задач вычислительного характера чаще всего приходится программировать вычисления по формулам. Возникающие при этом вопросы мы рассмотрим на примере простых арифметических выражений алгола, которые содержат только четыре основные арифметические операции и круглые скобки (трак- туя операцию возведения в целую неотрицательную степень как сокращенную запись умножения) — в этом случае для каждой операции, содержащейся в выражении, имеется соответствующая машинная операция УМИР-3. При программировании формул возникают следующие задачи. Г. Выбор порядка выполнения операций. Эта задача решается так же, как и при вычислениях вручную — с учетом старшинства входящих в формулу операций и расставленных скобок.
288 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл 5 2°. Программирование каждой очередной операции. В рассмот- ренном нами случае для этого в программе надо написать, как правило, одну очередную команду. .3°. Размещение в памяти промежуточных результатов и пра- вильное их использование. При составлении программы необхо- димо следить за тем, чтобы каждый промежуточный результат, выбираемый из рабочей ячейки в качестве одного из аргументов очередной команды, был получен в этой рабочей ячейке по одной из команд, выполняющихся раньше данной команды. Мы условимся для размещения промежуточных результатов прежде всего ис- пользовать группу «стандартных» рабочих ячеек 0001—0010, кото- рые будем обозначать через Z?l, R2, ..., /?8. 4°. Запасание констант. В программируемой формуле наряду с переменными могут фигурировать и конкретные числовые зна- чения. Если команда, в которой это число будет использоваться в качестве одного из аргументов, не допускает его явного задания {в виде непосредственного адреса), то такое число должно быть представлено в виде соответствующего машинного слова и поме- щено на хранение в определенную ячейку памяти в качестве «кон- станты программы». Константы естественно считать неотъемлемой частью программы — обычно все они собираются в единую группу, которую удобно размещать в конце программы. К началу решения задачи они вводятся в отведенные для них ячейки памяти вместе с программой. Как мы увидим дальше, однажды изготовленная программа часто используется в качестве составной части другой, более общей программы; Поэтому при составлении каждой программы необхо- димо следить за выполнением следующих условий: 1) программа не должна изменять значения исходных для нее данных, если это явно не сформулировано в качестве одной из ее задач; 2) программа должна правильно решать поставленную перед ней задачу всякий раз, когда осуществляется переход на ее начало (отсюда, в частности, следует, что программа в процессе своей работы не должна изменять содержимое ячеек, отведенных для хранения констант). Пример 5.1. Запрограммировать оператор присваивания z:= (х|2—у] 2)/ (0.5+хХу) При планировании последовательности действий условимся сна- чала вычислять числитель, а потом знаменатель. Дальнейший порядок действий достаточно очевиден: Г. г!:=хХх 4°. г4:=хХу 2°. .г2-.=уХу 5°. г5:=0.5+г4 (г5=0.5-ЬхХу) 3°. гЗ:=г1—г2 (гЗ=ха—уа) 6°. г:=гЗ/г5
5.3] ПРОГРАММИРОВАНИЕ ВЫЧИСЛЕНИЙ ПО ФОРМУЛАМ 289 При программировании вычисления г5:=0.5+г4 будет использо- ваться машинная операция сложения, при выполнении которой оба аргумента выбираются из памяти, так что и заранее известное значение 0.5 надо представить в виде машинного слова в определен- ной ячейке памяти, которую обозначим через С1: С1: 100 4000 0000 0000; число 0.5 Тогда символическая программа, реализующая заданный оператор присваивания, будет иметь вид: У X X /?1; Н=х2 У У У /?2; r2=z/2 В /?1 R2 R3-, гЗ=х*—у2 УХУ R4-, г4=хХу С 61 R4 R5; г5=0.5+хХу Д R3 /?5 Z; г=гЗ/г5 В этой программе команды выполняются в том порядке, в котором они записаны, каждая по одному разу. Программа, реализующая вычисление по любой из формул рас- сматриваемого нами вида, будет иметь структуру, аналогичную приведенной выше программе. Как видно, быстродействие такой программы определяется числом содержащихся в ней команд и характером используемых операций, а требуемый объем памяти равен суммарному числу ячеек, необходимых для размещения команд, констант и промежуточных результатов (исходные данные н окончательные результаты учитывать не будем, поскольку они от нас не зависят). В связи с этим возможны следующие пути оп- тимизации таких программ. 5. 3.1. Экономия команд. Возможность такой экономии возникает, например, в случае, когда в арифметическом выражении исходного оператора присваи- вания несколько раз встречается одно и то же подвыражение. В этом случае экономия достигается за счет того, что вычисление значения этого подвыражения производится только в одном месте. Это значение запоминается в одной из рабочих ячеек и исполь- зуется в дальнейшем по мере необходимости — тем самым произ- водится экономия вычисления этого подвыражения в других местах программы. В терминах алгола для этого нужно всюду в исходном выражении заменить это подвыражение одной и той же промежу- точной переменной, а перед исходным оператором присваивания записать дополнительный оператор, присваивающий значение этой промежуточной переменной. Если в получившихся в итоге опера- торах опять несколько раз встречается какое-либо подвыражение, то с ним следует поступить аналогичным образом. Проиллюстри- руем это на следующем примере. Пример 5.2. Пусть дан оператор присваивания /:= (х+у+г) / (х+у+и)— (х+у+и) f 2/ (аХ (х+у+г)) Э. 3. Любинский и др.
290 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 Здесь дважды встречаются подвыражения x+z/+z и х+y+u; чтобы в программе не писать два раза команды для вычисления каждого из них, заменим в исходном операторе первое подвыражение на г2, второе — на гЗ, и перед этим оператором запишем два дополнитель- ных оператора присваивания: г2:=х+у+гг, r3:=x+y+w, t:=f2/r3—r3f2/(aXr2) В получившейся последовательности операторов два раза встре- чается подвыражение х+у, поэтому для его экономии сформируем еще один дополнительный оператор: г\: =х+у\ г2: =г1 +z; r3:=rl +и; t:=r2/r3—r3 f 2/ (а Xг2) Нетрудно подсчитать, что при непосредственном программировании исходного оператора присваивания программа содержала бы 13 ко- манд, а с учетом экономии — только 8 команд: С X Y Rl; rl=x+y С Rl Z R2-, г2=х+у+г С Rl U R3; гЗ=х+у+и У R3 R3 R4; г4= (х+у+ы)2 У A R2 R5; г$=а (х+у+г) Д R2 R3 R6; г6= (х+у+г)/ (х+у+и) Д R4 R5 R7; г7= (х+у+ы)2/ (оХ (х+у+г)) В R6 R7 Т ; t =г6—г7 Имеются также два дополнительных источника экономии ко- манд. Первый из них заключается в предварительном преобразо- вании исходного выражения по обычным алгебраическим правилам (приведение подобных членов, вынесение за скобки и т. д.) с целью сокращения числа операций, входящих в выражение, что позволяет сократить и число команд в программе. Второй источник состоит в предварительном выполнении действий над числами, содержа- щимися в заданном выражении. С этой точки зрения, например, оператор присваивания у:= (а 12+а/2)Х (6+3/4) следует заме- нить оператором у:=аХ (а+0.5)Х (6+0.75), программа для ко- торого будет содержать на две команды меньше, чем для исходного оператора. 5.3.2. Выбор машинных операций. При программировании формул следует учитывать, что не- которые действия могут быть выполнены с использованием разных машинных операций, а также, что одни операции выполняются машиной быстрее, а другие — медленнее. Из рассмотренных нами операций арифметического типа наиболее медленными являются умножение и деление (поскольку в машине, как и при вычислениях вручную, они сводятся к последовательности сложений и вычита- ний); быстрее выполняется сложение и вычитание, а самыми быст- рыми являются операции над порядками. Поэтому для повышения быстродействия программы следует каждое действие, заданное в
6.31 ПРОГРАММИРОВАНИЕ ВЫЧИСЛЕНИЙ ПО ФОРМУЛАМ 291 программируемом выражении, сводить к наиболее быстрым машин- ным операциям. Например, оператор г/:=2Хх можно реализовать командой У X ДВА Y где через ДВА обозначена константа, представляющая число 2. Однако тот же результат можно получить по команде с более быст- рой операцией сложения С X X Y или с еще более быстрой операцией сложения порядка с адресом СПА 1 X Y 5.3.3. Экономия рабочих ячеек и констант. В приведенных выше программах при размещении в памяти промежуточных результатов мы пользовались очень простым пра- вилом: каждый новый результат помещали в новую рабочую ячейку. Но поскольку число используемых рабочих ячеек сказывается на общем объеме требуемой памяти, то возникает задача об их экономии. Возможность экономии рабочих ячеек основывается на том, что после того, как какой-либо промежуточный результат был исполь- зован в последний раз, рабочую ячейку, отведенную для его хра- нения, можно использовать для хранения других промежуточных результатов. Так, в программе примера 5.1 после вычисления разности х2—у2 промежуточные значения х2 и у2 дальше нигде не используются, и поэтому ячейки, отведенные для их хранения, можно использо- вать для хранения новых промежуточных результатов. В связи с этим, оператор присваивания примера 5.1 может быть реализован следующей программой, в которой используются только две рабочие ячейки вместо пяти: У X X /?1; Н-х2 У Y Y /?2; г2=у2 В Rl R2 Я1; г1=х2—у2 У X Y R2\ г2=хХу С Cl R2 R2\ г2=0.5—хХу Д R\ R2 Z \ г =г\/г2 Более полная экономия рабочих ячеек может быть достигнута за счет выбора более рационального порядка выполнения операций. Возможность дополнительной экономии основывается на том факте, что чем меньше по времени нужно будет хранить какой-либо про- межуточный результат в рабочей ячейке, тем больше вероятность того, что эту ячейку можно будет использовать для хранения дру- гих промежуточных результатов. Поэтому возникает задача выбора такого из допустимых порядков выполнения операций, чтобы мо- мент последнего использования каждого вычисленного значения был возможно ближе расположен к моменту его вычисления. Проиллюстрируем сказанное на следующем примере. Ю*
292, ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 Пример 5.3. Запрограммировать оператор присваивания . г:=а/ (х+у) + (х—у) X (a+x)+b X (х+у) Для экономии дважды встречающегося подвыражения х+у заменим исходный оператор присваивания двумя: r\:—x+y, z:=±a/rl + (х—у)Х (a+x)+bXr\ Если следовать принятому в алголе порядку выполнения операций, то — с учетом естественной экономии рабочих ячеек — получим следующую программу: С X Y Я1; г1=х+«/ Д А 7?2; г2=а! (х+у) В X Y R3; гЗ=х—у С А X Я4; г4=а+х У R3 R4 R3; гЗ= (х—у)Х (а+х) С R2 R3 R2-, г2=а/ (х+у)+ (х—у)У, (а+х) У В Rl RV, rl=bx (х+у) С R2 R\ Z Теперь заметим, что значение х+у, вычисленное по первой команде, в последний раз используется только в седьмой команде, так что до этого момента ячейку нельзя использовать для хра- нения других промежуточных результатов. Для достижения до- полнительной экономии рабочих ячеек, при реализации второго оператора присваивания целесообразно сначала вычислить a/rl + +bx.rl, а затем уже вычислять и использовать слагаемое (х—у) X X (а+х): С X Y 7?1; Л=х+у Д A RX R2-, г2=а/(х+у) У В Rl 7?1; r\=bx(x+y) С Rl R2 /?1; г\=а/(x+y)+bX(x+y) В X Y R2-, г2=х—у С А X R3-, гЗ=а+х У R2 R3 R2-, г2— (х—у)Х(а+х) С Rl R2 Z ; В этой программе по сравнению с предыдущим вариантом удалось сэкономить еще одну рабочую ячейку. Конечно, более глубокие тождественные преобразования формул могут дать более существенную экономию и рабочих ячеек, однако такие преобразования без достаточного учета существа и специ- фики решаемой задачи могут привести к неприятным последствиям, и поэтому здесь следует проявлять осторожность. Действительно, допустим, что надо было запрограммировать оператор присваивания, записанный в виде /:= (х/н)Х (у/о), а мы решили предварительно переписать его в виде t:= (хХу)/ («Хи). С теоретической точки зрения мы сделали тождественное преобра- зование формулы, но при машинной реализации оно может привести к совершенно иным результатам. В самом деле, пусть наименьшее
6.4) ПРОГРАММИРОВАНИЕ ПРОСТЫХ ЛОГИЧЕСКИХ ВЫРАЖЕНИЙ 293 по модулю число, представимое в машине, равно 10“*, а фигури- рующие в вычислениях переменные имеют следующие текущие значения: х=10-5, «/=10“’, и=10-12, и=10-1?. При выполнении программы, реализующей первый оператор присваивания, было бы получено/=1013, а при выполнении программы, реализующий вто- рой оператор присваивания, при делении произошел бы «аварий- ный останов», поскольку uXv=10“” является машинным нулем. При экономии рабочих ячеек также следует проявлять большую осторожность: одной из наиболее типичных ошибок при ручном программировании является «забивание» рабочих ячеек, т. е. за- пись в рабочую ячейку нового промежуточного результата, тогда • как в последующих командах должно было еще использоваться предыдущее содержимое этой ячейки. К тому же слишком «жест- кая» экономия рабочих ячеек может усложнить последующую проверку полученной программы. Заметим, что ячейку памяти, отведенную для хранения окон- чательного результата вычислений по заданной формуле, можно, вообще говоря, использовать и для хранения промежуточных результатов, однако такой экономии рабочих ячеек следует избе- гать, так как она затрудняет понимание и проверку программы: с этой точки зрения удобно считать, что появление имени (адреса) окончательного результата в поле третьего адреса команды озна- чает окончание вычисления искомого значения. Что касается экономии констант, то эта оптимизация сводится к тривиальному требованию: чтобы одинаковые константы, исполь- зуемые в разных местах программы, не запасались в ней несколько раз (т. е. не помещались для хранения в нескольких ячейках па- мяти) — достаточно это сделать один раз. Экономия констант может быть также достигнута за счет более целесообразного использования имеющихся машинных операций. Так, в рассмотренном ранее примере реализации оператора у:=2Хх использование операции сложения порядка с адресом вместо опе- рации умножения позволяет не только получить более быструю программу, но и сэкономить константу, представляющую число 2 (если эта константа не потребуется по существу в других местах программы). Экономия констант также может быть достигнута и за счет предварительного выполнения заданных в исходной формуле дей- ствий над числами, о чем говорилось выше. 5.4. Программирование простых логических выражений 5.4.1. Представление логических значений. Логические значения true и false — в отличие от чисел — не имеют в машине стандартного представления, поэтому надо принять определенное соглашение об их представлении. Поскольку логи-
294 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ 1Гл. 5 ческих значений всего два, то их можно закодировать с помощью цифр 0 и 1, так что для хранения значения логической переменной достаточно одного двоичного разряда. В связи с этим в одной ячейке памяти можно хранить значения до 45 логических переменных, но если число таких переменных, используемых в программе, сравнительно невелико, то удобно для каждой из них отвести от- дельную ячейку. Это соглашение мы и примем: в дальнейшем ус- ловимся считать, что значение true представляется машинным словом ООО 0000 0000 0001, a false — словом 000 0000 0000 0000. Для кратковременного хранения логических значений в машине можно использовать также регистр признака результата <о. 5.4.2. Операции УМИР-3 логического типа. Как и в большинстве реальных машин, в УМИР-3 нет операций над отдельными логическими значениями: операндами каждой операции логического типа являются 45-разрядные машинные слова. В этих операциях каждое машинное слово интерпретируется как логический вектор, состоящий из 45 компонент, и все эти опе- рации являются поразрядными: каждая из них выполняется над всеми парами компонент с одинаковыми номерами упомянутых векторов, являющихся аргументами данной операции, независимо друг от друга. К данной группе относятся следующие операции УМИР-3. Сравнение (или неэквивалентность). И (15). Содержание опе- рации: z(i]:='] (x[i]ssj!/[t]) (i=l, 2.45). Это значит, что z[t]=l при х[Л^=у[Л и ги]=0 при x[i]=i/[i]. Если в результате получается нулевое машинное слово, то признаку а> присваивается значение true, в противном случае — значение false. Операция сравнения удобна для проверки тождественного сов- падения двух машинных слов: если сравниваемые слова тождест- венно совпадают, то результатом является нулевое машинное слово й вырабатывается значение mstrue; если слова-аргументы не сов- падают, то в слове-результате цифра 1 содержится в тех разрядах ik, для которых (т. е. цифрой 1 будут как бы «поме- чены» несовпадающие разряды в словах-аргументах), и вырабаты- вается значение cosfalse. Операцию сравнения можно также трак- товать и как поразрядное сложение по модулю 2. В частности, операция сравнения удобна для проверки выпол- нения отношения в случае операций = и =#. Логическое умножение {логическое «И»). И (55). Содержание опе- рации: z[il:=x[t]At/[i) (t=l, 2, ..., 45). В этой операции z[i)=l только в случае x[i] —y[i\=1. Значение в> вырабатывается по та- кому же правилу, что и в операции сравнения. Заметим, что если z/[i]=l, то z[i]=x[t], а если i/[i]=0, то и z[i]=0 независимо от значения х[Л. Благодаря этому обстоятельству дан- ная операция часто используется для выделения определенных
5.4] ПРОГРАММИРОВАНИЕ-ПРОСТЫХ ЛОГИЧЕСКИХ ВЫРАЖЕНИЙ 295 разрядов одного слова-аргумента и «гашения» остальных его раз- рядов, что регулируется соответствующим подбором другого слова (которое можно назвать «константой выделения» или «маской»). Логическое сложение (логическое «ИЛИ»). ИЛИ (75). Содержание операции: z[i]:=x[ilV//b’l (*=1, 2, ..., 45). Следовательно, г[Л=0 только в том случае, когда х[i]=z/Ul =0; в остальных случаях z[Z]=l. Правило выработки значения со такое же, как и в предыдущих операциях. Очевидно, что при x[i]=0 получается ?[/]=#[/], а при у[Л=О получается z[f]=x[ij. В связи с этим операцию логического сложе- ния удобно использовать для формирования некоторого слова из отдельных его составных частей. Пересылка. П (00). Содержание операции: г:=х, т. е. ОП{АЪИСП\ :=0П[А1ИСП]. При этом второй адрес в команде не используется и значение со не изменяется. Эту операцию можно трактовать как одноместную поразрядную операцию: г[Л:=х[Л (i=l, 2, ..., 45). 5.4.3. Реализация логических операций. Итак, в УМИР-3 имеется только три логические операции. По- этому прежде всего возникает вопрос о реализации на машине других логических операций (используемых, например, в алголе). Здесь можно воспользоваться тем фактом, что логические операции достаточно просто выражаются друг через друга. Так, вычисление в силу истинности высказывания = “] (a=true) реализуется применением операции сравнения (0=15) к двум словам, одно из которых представляет значение логической переменной а, а другое — логическое значение true: если последнее слово обозначить через Т, то оператор присваивания ft:=“|a реа- лизуется одной командой: И А Т В Импликация в силу истинности высказывания (а:эЬ)= (~|а\/Ь) может быть реализована последовательным применением операций сравнения и логического сложения: по первой из них рассмотрен- ным выше способом можно получить значение ~|а, а по второй, примененной к значениям ~]а и ft, получается значение а:эЬ. Например, оператор присваивания c:=az>ft может быть реали- зован двумя командами, в которых буквой Т обозначена константа, представляющая значение true: И АТ /?1; rl:=la ИЛИ /?1 В С\ с-.=г\:=)Ь Операция эквивалентности может быть реализована двукрат- ным использованием операции сравнения в силу истинности вы- сказывания “| (“| (а^=Ь))= (а^Ь). Первое применение этой опе- рации к значениям а и ft дает значение “] (а=Ь), а отрицание этого значения дает значение а=Ь.
296 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 5.4.4. Программирование простых логических выражений. Выше мы уже познакомились с программированием некоторых простейших логических выражений, содержащих только одну логическую операцию. В более общем случае необходимо запро- граммировать все заданные в выражении логические операции с учетом их старшинства. Программирование простых логических выражений мало от- личается от программирования простых арифметических выраже- ний — разница заключается лишь в томк что вместо машинных операций арифметического типа в основном используются опе- рации логического типа, а также в том, что некоторые логические операции реализуются не одной, а несколькими машинными опе- рациями. При этом все рассмотренные ранее правила экономии команд и рабочих ячеек остаются в силе. Пример 5.4. Запрограммировать оператор присваивания Я'—1аД (&^c)=>dV (b=c) Для экономии команд дважды встречающееся подвыражение (Ь=с) вычислим в одном месте: rl:—b=c; g:— 1 аДг1 z>d\/r\ Если через Т и F обозначить константы, представляющие логиче- ские значения true и false соответственно, то можно составить сле- дующую программу: И В С RI; И Rl Т Rl; rl:b=c И AT R2; г2: *]а И Rl R2 R2; r2- ~]a/\rl ИЛИ D Rl Rl; rl: dV (b=c) H R2 T R2; r2-.= ~\r2 ИЛИ R2 Rl G; g: =r2\/rl Отдельными компонентами простого логического выражения наряду с переменными и постоянными логическими значениями — могут также быть иотношения, значения истинности которых используются при вычислении значения выражения. Значение отношения, с целью его последующего использования, обычно запоминается в какой-либо ячейке памяти, так что програм- мирование отношения вида А1*А2, где А1 и А2 — простые ариф- метические выражения, а * — знак одной из операций отношения, обычно сводится к программированию оператора вида if А1*А2 then г:=true else r:=false Если логические значения true и false запасти в качестве констант, то программирование такого оператора сводится к программи- рованию пересылки одной из этих констант в ячейку, отведенную для переменной г, причем выбор пересылаемой константы произ- водится в зависимости от выполнения условия if А1«А2 then.
5.4] ПРОГРАММИРОВАНИЕ ПРОСТЫХ ЛОГИЧЕСКИХ ВЫРАЖЕНИЙ 397 Таким образом, программа, предназначенная для вычисления значения отношения, имеет уже знакомую нам по главе 4 блок* схему, изображенную на рис. 5.1. Как видно, эта программа должна начинаться с группы команд, предназначенной для разветвления вычислительного процесса. Последней командой этой группы должна быть одна из команд условного перехода, а ей должны пред- шествовать команды, предназначенные для выработ- ки значения ® в соответствии с заданным отношени- ем (с программированием разветвлений мы уже зна- комы по главе 4). Операции перехода в УМИР-3 точно такие же, как и в УМ-3, с теми же кодами операций (0=36 (ПЕ) — условный переход по co=true, 0=76 (ПУ) — условный переход по <o=false и 0=56 (ПБ) — безусловный переход). Как и в УМ-3, опера- ции перехода совмещены с операцией пересылки. Следует лишь иметь в виду, что в УМИР-3 при Рис. 5.1. выполнении команд перехода используются, как обычно, исполнительные ад* - реса, так что, например, команда вида л ПЕ А\ А2 АЗ имеет следующий смысл: 0П[АЗИСП}:=0Л[А1ИСЛ]; C:=ifwthen А2ИСП else С+1 При выполнении операций перехода значение со не изменяется. Что касается выработки значения со при программировании отношений, то для этих целей надо использовать подходящие ма- шинные операции. Например, в случае операций отношения С, > и можно использовать операцию вычитания, поскольку от- ношение А1*А2 эквивалентно отношениям А1—А2*0 и 0*А2—А1, причем (учитывая правило выработки значения со при операции вычитания) в случае операций отношения < и следует вычислять значение А1—А2, а в случае операций > и — значение А2—А1. Пусть, например, требуется реализовать разветвление вычис- лительного процесса в зависимости от условия ifx<ythen, гдё х и у — вещественные переменные. Если считать, что метками ДА и НЕТ помечены команды, к которым надо осуществить переход соответственно при выполнении и невыполнении заданного ус- ловия, то такое разветвление реализуется командами В X Y /?1; rl:=x—у, <л:=х<.у ПУ — НЕТ —; ДА: ........ или же командами В X Y /?1; rl:—x—y, <о-.=х<у ПЕ - ДА НЕТ-........
298 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 Если значение разности х—у вычисляется только для выра- ботки значения ш и больше нигде не используется, то значение разности можно и не записывать в память (указав в качестве адреса результата вычитания адрес 0000). Операции отношения = и #= могут быть разумно применены лишь к целочисленным значениям, так как вещественные числа являются приближенными и поэтому их сравнение на точное ра- венство не имеет смысла. Если целые числа представляются в машине в форме с плавающей точкой, то для выработки значения со в случае операций отношения == и #= можно использовать логи- ческую операцию сравнения, по которой устанавливается тож- дественное совпадение двух машинных слов. При программировании отношений следует также использовать то обстоятельство, что в УМИР-3 операции перехода совмещены с операцией пересылки. Так, оператор присваивания v-.=x+y<Zz может быть реализован следующей программой (через Т и F по- прежнему обозначены константы, представляющие значения true и false соответственно): С X Y /?1; rl:=x+y В R\ Z —; co:=x+z/<z ПЕ Т L V; и.=true; при co=true переход на L ПЕ — V; t>:=false L: Итак, мы рассмотрели все элементы, из которых складывается программирование вычисления значения простого логического выражения. При программировании простых логических выражений, ко- торые чаще всего используются в условиях для разветвления вы- числительного процесса, имеется дополнительный источник опти- мизации программ, который заключается в учете свойств логиче- ских операций и специфики программируемого выражения. Если, например, логическое выражение является логической суммой нескольких слагаемых, то оно заведомо имеет значение true, если это значение имеет хотя бы одно слагаемое. Поэтому, как только в процессе вычислений встретится слагаемое, которое имеет зна- чение true, этот процесс можно закончить, не вычисляя значений последующих слагаемых, что и следует предусмотреть в программе. Аналогично, если программируемое выражение является логиче- ским произведением, то оно будет иметь значение false, если это значение имеет хотя бы один из сомножителей. Этот источник оптимизации особенно эффективен в том случае, когда слагаемые (сомножители) являются отношениями, поскольку в этом случае для вычисления значения каждого из них требуется
5 4] ПРОГРАММИРОВАНИЕ ПРОСТЫХ ЛОГИЧЕСКИХ ВЫРАЖЕНИЙ 299 не одна, а несколько команд. При этом для достижения наибольшей эффективности программы целесообразно слагаемые (сомножи- тели) вычислять в порядке возрастания их сложности. Кроме того, если значение логического выражения использу- ется только для разветвления вычислительного процесса, то это значение нет необходимости получать в явном виде, в ячейке памяти (на что требуются дополнительные команды) — достаточно лишь выработать признак со в соответствии с значением данного логи- ческого выражения. Пример 5.5. Запрограммировать условие if x<Zy/\y^\/\a<f)\/a=b/\y^() then Конечно, программирование этого условия можно вести по схеме r:=B; if г then где В — логическое выражение, заданное в исходном условии, а оператор г:=В программировать рассмотренным выше способом; В этом случае программа, реализующая оператор г:=В, будет содержать 19 команд (по 3 команды на каждое из пяти фигурирую- щих здесь отношений и 4 команды на реализацию заданных логи- ческих операций). Учитывая, что на вычисление значения каждого отношения требовалось бы 2 или 3 такта, на выполнение этой про- граммы затрачивалось бы от 14 до 19 тактов работы машины. Кроме того, две команды (и два такта) требуется на реализацию условия if г then. Если использовать упомянутый выше дополнительный источник оптимизации, то заданное условие можно реализовать по следую- щему алгоритму (приведенная его запись на алголе наиболее близка к машинной реализации): co:=a=fe; if ”| со then go to М; co:=0<z/; if-] со then go to ДА-, M: <л:=х<у, if-] со then go to HET\ co:=1/0; if co then go- to HET\ co:=a<0; if-] co then go to HET-, ДА: ......... HET: Этот алгоритм реализуется следующей программой, в которой через ЕД обозначена константа, представляющая единицу: Н А В —; а>:=а=Ь ПУ — М —; if “j со then go to М В — Y —; со:=0<«/ ПУ — ДА —; if"] со then go to ДА М: В X Y —; <л:=х<у ПУ — НЕТ —; if "| со then go to НЕТ В Y ЕД —; со:=г/0 ПЕ — НЕТ —; if со then go to НЕТ
300 основные ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. S В А — —; и:=а<0 ПУ — НЕТ —; if 1 <о then go to НЕТ ДА-. . Как видно, эта программа, реализующая заданное условие, содержит всего 10 команд, а на ее выполнение будет затрачиваться от 4 до 10 тактов работы машины. Конечно, этот дополнительный источник оптимизации может использоваться и при программировании операторов присваива- ния, правыми частями которых являются простые логические выражения. 5.5. Программирование условных выражений и операторов Программирование операторов присваивания с условными (ариф- метическими и логическими) выражениями не содержит в себе каких-либо новых элементов, которые не были бы рассмотрены ранее. Действительно, оператор присваивания вида V:=if В then El else Е где В — логическое, Е1 — простое, а Е — произвольное арифме- тическое (логическое) выражения, по своему смыслу эквивалентен условному оператору If В then V:=E1 else V:=E Программа, реализующая этот оператор, имеет уже знакомую нам блок-схему, изображенную на рис. 5.2. Что касается фигурирующего здесь условия, то оно сводится к уже рассмотренным видам условий: г:=В; if г then V:=E1 else V:=E Если В и El — простые выражения, то программирование исход- ного оператора присваивания сводится к программированию уже рассмотренных нами операторов с простыми выражениями и к программированию разветвления в зависимости от значения ло- гической переменной, что также очевидно. При этом можно использовать все рассмотренные ранее ис- точники оптимизации программ, в частности: — если выражения В, Е1 и Е содержат общие подвыражения, то их следует вычислять в одном месте программы (учитывая, что выражения Е1 и Е находятся на разных ветвях вычислений); — если значение выражения В используется только для раз- ветвления вычислительного процесса, то можно ограничиться вы- работкой значения & в соответствии с значением выражения В, не получая этого значения в ячейке памяти; -
5.61 ДЕЙСТВИЯ НАД ХОДАМИ 301 — при выработке значения используемого для разветвления вычислительного процесса, учитывать специфику логического вы- ражения в условии. Пример 5.6. Запрограммировать оператор присваивания 4 z*.=if xf2+yf2^a then (x+z/)/2 else x|2x (x+z/+a) Здесь каждое из подвыражений xf 2 и х+у встречается дважды, поэтому их значения вычислим в одном месте, до разветвления вы- числительного процесса: rl: —х 12; г2: =х+у\ г:=if rl +у f 2sgZa then r2/2 else rl X (r2+a) Значение отношения в условии используется только для разветв- ления вычислительного процесса, поэтому его достаточно получить в регистре о. Таким образом, исходный оператор присваивания х может быть реализован следующей программой: У X X /?1; rl:=xf2 с X Y R2-, г2:=х+у У Y Y R3; гЗ:=у]2 с R3 R1 R3‘, гЗ—х f 2 А-у 12 в А R3 —; <о: —а<х 12+у f 2 ПЕ — £1 —; При os true переход на £1 ВПА 1 R2 Z; г= (х+у)/2 ПБ — £2 —; Переход на £2 £1: С А R2 R2-, г2=х+у+а У R1 R2 Z; z=xf2x (х+«/+п) £2: Если какое-либо из выражений В или Е в исходном операторе присваивания также является условным, то вместо соответствую- щего элемента приведенной выше блок-схемы следует подставить точно такую же блок-схему. Так, если Е — простое, а В — услов- ное выражение вида if Bl then В2 else ВЗ, где Bl, В2 и ВЗ — простые логические выражения, то придем к блок-схеме, изображенной на рис. 5.3. Програм- мная реализация этой блок-схемы очевидна. Очевидно также, что и программирование услов- ных операторов не содержит каких-либо новых эле- ментов, так как программирование таких операторов сводится к программированию безусловных операторов и к программированию разветвлений. V:=Ef 5.6. Действия над кодами До сих пор мы рассматривали только операции I г над числами и логическими значениями. Однако с | помощью ЭВМ можно перерабатывать самую раз- 1 личную информацию по разным правилам. При Рис. 5.3.
302 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ 1Гл. 5 рассмотрении понятия алгоритма уже отмечалось, что с по- мощью цифр 0 и 1 можно закодировать любую информацию и, следовательно, ее можно представить в виде машинных слов. Для облегчения же ее переработки среди машинных операций наряду с операциями логического типа имеется специальная группа опе- раций. 5.6.1. Операции над кодами. При выполнении операций этой группы, имеющихся в наборе операций УМИР-3, каждое машинное слово а:а45а44.. .аг тракту- ется либо как одно 45-разрядное двоичное целое число без знака, либо как пара целых чисел без знака а1:а45а44.. .а37 («код операции» слова а, поскольку в этих разрядах слова, трактуемого как команда, изображается код операции) и а2:азва35.. .ах («мантисса» слова а, поскольку в этих разрядах слова изображается мантисса числа), над которыми и выполняются те или иные арифметические дей- ствия. Эти операции, в частности, удобны при обработке символь- ной информации. 5.6.1.1. Операции сдвига. Рассматриваемые здесь операции сдвига служат для умножения одного из упомянутых выше целых чисел (al или а2) на число вида 2W (tn — целое), что в двоичной системе счисления эквивалентно сдвигу цифровой части числа относительно фиксированного положения точки. Во всех этих операциях основным аргументом, который подвергается преобра- зованию, является слово у, выбираемое по адресу А2ИСП. Сдвиг мантиссы по адресу. С ДМ А (14). Содержание операции: zl:=yl; z2:=(y2-2m) mod 236. Это значит, что целое без знака у2 (мантисса слова у) сдвигается на |т| разрядов влево при т^О и вправо при т<0; двоичные цифры, выходящие при этом сдвиге за пределы разрядов 36-4-1, не учитываются, а в освободившиеся раз- ряды с другого конца мантиссы записываются нули. Получившийся 36-разрядный код принимается в качестве ?2 (мантиссы слова-резуль- тата г), а в качестве z\ (кода операции слова-результата z) принима- ется у\. Значение т задается в команде в явном виде непосредственным адресом А\ИСП — так же, как и в случае операций изменения по- рядка по адресу. При г2=0 вырабатывается co=true. Так, если у=(305 3777 2527 0625), то по команде СДМА 3 у z будут получено слово z=(305 7772 5270 6250) и выработано зна- чение w=false. Сдвиг мантиссы по порядку. СДМП (34). Эта операция отлича- ется от предыдущей только тем, что в качестве т принимается по- рядок р числа х, выбираемого по адресу А\ИСП. Сдвиг слова по адресу. СДСА (54),. В отличие от операций сдвига мантиссы, по этой операции сдвигается все 45-разрядное слово у*
6.6] ДЕЙСТВИЯ НАД КОДАМИ 303 Двоичные цифры, выходящие за пределы разрядов 454-1, опуска- ются, а в освободившиеся с другого конца слова разряды записы- ваются нули. Полученное слово принимается в качестве результата г. При г=0 вырабатывается co^true. Значение т задается так же, как и для операции С ДМ А — непосредственным адресом А\ИСП. Так, если //=(345 2222 3333 4444), то по команде СДСА ‘7764’ у г (в которой задано т=—12) получится слово г=(000 0345 2222 3333). Сдвиг слова по порядку. СДСП (74). Отличие этой операции от предыдущей состоит лишь в том, что в качестве т принимается по- рядок числа, выбираемого по адресу А1ИСП. Операции сдвига по адресу обычно используются в том случае, когда величина и направление сдвига известны заранее, при состав- лении программы. Если же эти параметры сдвига определяются только в процессе выполнения программы, то обычно удобнее ис- пользовать операции сдвига по порядку. 5.6.1.2. Специальные операции над кодами. Рассматриваемые здесь операции этой группы в машине УМИР-3 предназначены глав- ным образом для действий над командами. Сложение мантисс. СМ (13). Содержание операции: г1:=х1; z2:=(x24-//2) mod 2s*. Таким образом, в качестве кода операции г1 результата принимается код операции х\ аргумента, выбираемо- го по адресу А\ИСП, а в качестве мантиссы z2 результата берутся 36 младших разрядов суммы мантисс аргументов. Если при сложе- нии мантисс возникала единица переноса из старшего, 36-го разряда (которая не учитывается в результате), то вырабатывается w=true. Вычитание мантисс. ВМ (33). Содержание этой операции ана- логично предыдущей, с той лишь разницей, что здесь из мантиссы х2 вычитается мантисса у2; результат вычитания также берется по модулю 23‘. При х2<//2 вырабатывается значение <o=true. Так, если х=(503 3333 4444 5555), то по команде СМ х у г при у=(п 0 7777 0000 0001) вырабатывается слово г : 503 3332 4444 5556 а по команде ВМ х у г при //=(л0 0001 0000 0001) вырабатывается слово г: 503 3332 4444 5554 5.6.2. Преобразования кодов. Рассмотренные машинные операции над кодами — наряду с опе- рациями логического типа — позволяют достаточно удобно перера- батывать различного рода нечисловую информацию, представляе- мую в! виде машинных слов.
304 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл 5 Одним из примеров такой нечисловой информации являются команды самой программы, действия над которыми широко исполь- зовались в ЭВМ ранних выпусков. Пусть, например, из команды А: я 0 Л1 Л2 АЗ нужно получить команду В: Я0ЛЗЛ2+1 Л1 где л, 0, Л1, Л2 и ЛЗ — те же самые, что и в команде Л (в предпо- ложении, что Л2<77778). Очевидно, что операции арифметического типа не пригодны для решения этой задачи, поскольку здесь и слова представляют собой не числа, и требуемое преобразование не носит арифметического характера. Для выполнения преобразований подобного рода и слу- жат логические операции и операции над кодами. Решение нашей задачи может быть осуществлено различными способами. Можно, например, сначала получить слова: rl: л0 0000 Л2-Н 0000 г2: 000 ЛЗ 0000 0000 гЗ: 000 0000 0000- Л1 Тогда слово В можно сформировать из этих трех составных частей с использованием операции логического сложения (или операции сложения мантисс). Для получения слова rl можно сначала в слове Л заменить на нуль («погасить») все его разряды, представляющие первый и второй. адреса в команде, изображаемой этим словом, оставив без изменения остальные разряды (с использованием операции логического умно- . жения и константы 777 0000 7777 0000), а затем прибавить единицу к содержимому второго поля адреса (с использованием операции сложения мантисс). Для получения слова г2 достаточно мантиссу слова Л сдвинуть влево на 24 разряда и погасить левые его 9 разрядов (впрочем, мож- но сохранить исходные значения этих разрядов, используя при по- следующем формировании слова В соотношение (ауа)=а). Анало- гичным образом можно получить и слово гЗ. Итак, поставленная задача может быть решена по следующей программе: И A Cl rl; г1:л0 0000 Л2 0000 CM rl С2 rl; г1:л0 0000 Л2+1 0000 СДМА 24 А г2; г2:яв ЛЗ 0000 0000 ИЛИ rl r2 rl; г1:я0 ЛЗ Л2+1 0000 СДМА —24 Л г2; г2:л0 0000 0000 Л1 ИЛИ rl r2 В-, В:яв АЗ Л2+1 Л1 в которой используются следующие константы: С1: 777 0000 7777 0000 С2: 000 0000 0001 0000 По этой программе преобразованию подвергается только ман- тисса слова Л, поэтому — кроме логических операций — ис-
6.6] ДЕЙСТВИЯ НАД КОДАМИ 305 пользовались только операции над мантиссами. В общем случае- преобразованиям могут подвергаться любые разряды машинных слов, и тогда приходится прибегать й к другим операциям над ко- дами. Пример 5.7 Запрограммировать оператор С [/]:= "| (Л1ЛДВ 1/1) где А, В и С — логические векторы, содержащие по 45 компонент,, причем каждый из векторов представлен одним машинным словом, а целочисленное значение /(l^fc^45) задано в виде числа единиц в. поле второго адреса слова L L: ООО 0000 I 0000 Как мы знаем, все машинные операции логического типа трак- туют машинные слова, являющиеся операндами, как логические- векторы, и каждая такая операция применяется ко всем парам ком- понент с одинаковыми номерами векторов, являющихся аргумен- тами данной операции. В нашем же случае заданные логические операции надо выполнить только над компонентами с определен- ным номером I (будем считать, что нумерация компонент совпадает с нумерацией разрядов машинного слова). Поэтому при програм- мировании надо предусмотреть, чтобы в векторе-результате новое значение получила только одна из его компонент (с номером /), а. значения остальных компонент остались без изменения. Для до- стижения этой цели также придется использовать операции над кодами. При составлении программы предусмотрим выполнение задан- ных логических операций над всеми парами компонент векторов А и В, используя машинные операции логического типа, но чтобы не- изменить значений остальных компонент вектора С, промежуточ- ные результаты предварительно получим в рабочих ячейках. Если запасти константу С1: 777 7777 7777 7777 представляющую значение true во всех разрядах слова, то первый: этап решения задачи можно реализовать командами: И А В rl; г1[/]:=Л1/]ДВ[/1 (/=1,2.......45) И rl Cl rl; г1[/]:")(Л ШДВ [/]) (/=1, 2.....45) Далее значение, зафиксированное в разряде с номером / слова rl, надо записать в разряд с номером / слова С, сохранив без изменения остальные его разряды. Если предварительно в /-й разряд слова С записать цифру 0 (значение false), то для записи в этот разряд но- вого значения можно воспользоваться соотношением (aVfalse)=a и применить операцию логического сложения. Однако машинная операция ИЛИ применяется ко всем парам разрядов в словах-ар- гументах. Чтобы все разряды слова С остались без изменения (кро- ме разряда с номером /, в который надо записать новое значение),., воспользуемся тем же самым соотношением и обеспечим, чтобы во
306 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 8 всех разрядах второго аргумента (кроме /-го) было зафиксировано значение false (цифра 0). Итак, в слове rl надо предварительно выделить его Ай разряд я погасить остальные его разряды — для этого достаточно слово rl логически умножить (по операции И) на слово г2, которое со- держит единицу в /-м разряде и нули в остальных разрядах. По- скольку значение / заранее неизвестно, то слово г2 нельзя запасти в качества константы. Однако его можно получить из константы С2: ООО 0000 0000 0001 путем сдвига этого слова на /—1 разрядов влево. При этом можно воспользоваться операцией сдвига слова по адресу (СДСА). Чтобы А\ИСП в команде с этой операцией был равен /—1, значение / занесем в индексный регистр, в поле первого адреса этой команды запишем адрес Л1 =7777, задающий значение —1, и снабдим первый адрес в команде признаком модификации: PQ __ £ . р', = 1 СДСА ‘7777’() С2 г2; г2 [/]=!; г2 [Л=0 (/¥=/) Теперь у слова г! можно выделить Ай разряд, погасив остальные его разряды: И rl г2 rl; Н[/]= 1 (Л[/1АВ[/1); Н[Л=0 (/¥=/) Для гашения Аго разряда в слове С можно использовать машин- ную операцию логического умножения (И), взяв в качестве второго аргумента слово, содержащее нуль в Ам разряде и единицы в осталь- ных разрядах. Это слово можно получить из сформированного ра- нее слова г2 путем его «инвертирования», т. е. путем замены единиц на нули, а нулей — на единицы. Такое инвертирование достигается применением операции сравнения, если в качестве второго аргу- мента взять слово С1 (единицы во всех разрядах): н r2 С1 г2; и С г2 г2; г2[/]=0, г2[Л=С |Л (/¥=/) или r2 rl С\ Итак, । экончательпо программа примет вид: И А В И; И rl С1 /Т; Н[Л:= "1 (Л[ЛДВ|Л) (/=1,2, . • 45) PC — L " "" f СДСА -1() С2 г2-, г2[/]=1, г21Л=0 (/=#=/) И rl г2 Н; гШЬз-ЦЛ1/1ДВ1Л), г1[Л=0 (»¥=/) Н г2 С1 г2; г2[/]=0, г2[/]=1 (/#=/) И С г2 г2; r2[/l=0, r2l/J=Cl/l (iV=/) ИЛИ г2 rl С; В этой программе используются константы: СТ: 777 7777 7777 7777 С2: 000 0000 0000 0001 Принятый ранее способ хранения значений логических пере- менных, при котором для каждой простой переменной отводится отдельная ячейка памяти, может привести к весьма нерациональ-
5.7] ПРОГРАММИРОВАНИЕ ЦИКЛОВ 307 ному ее использованию — на самом деле в одной ячейке можно хра- нить значения до 45 логических переменных, отводя для каждой из них один двоичный разряд. Однако такой компактный способ хранения значений логических переменных из-за специфики ма- шинных операций логического типа повлечет и определенную спе- цифику в программирование логических выражений. В этом слу- чае также приходится прибегать к использованию операций над кодами. Пример 5.8. Запрограммировать оператор г:=“|(хДг/) в предположении, что значения переменных х, у и z хранятся в од- ном и том же слове А, в его разрядах с номерами 44, 2 и 40 соот- ветственно. В этом случае — прежде чем выполнять соответствующие ма- шинные операции логического типа, каждое из значений х и у надо представить отдельным машинным словом, отведя для изобра- жения каждого значения разряд с одним и тем же номером — только после этого можно применять соответствующие машинные операции. Вычисленное логическое значение затем надо записать в соответ- ствующий разряд слова А (отведенный для переменной z), сохранив без изменения остальные его разряды (как и в предыдущем примере). Если условиться значения аргументов предварительно представ- лять в первом разряде машинного слова и запасти константы Т: ООО 0000 0000 0001; С: 767 7777 7777 7777; С [401=0; С Ш = 1 (/#=40) то заданный оператор может быть реализован следующей программой: СДСА —43 А rl; rl[l]:=x СДСА —1 А г2; r2[l]:=z/ И rl г2 И; И rl Т rl; rl 111= 1 (х/\у) И rl Т rl; rl[l]=“| (хДу); rl [Л=0 (»¥=1) СДСА 39 rl rl; г1[40]^-](хДг/); rl [i]=0 (»¥=40) И А С Л; Л[40]:=0 ИЛИ А rl Л; Л[40]:=г1 [40] (s"] (хД«/)) Как видно, экономия памяти за счет компактного хранения значений логических переменных влечет за собой дополнительные команды при программировании логических выражений. 5.7. Программирование циклов 5.7.1. Общие вопросы программирования циклов. Во всех рассмотренных ранее в этой главе примерах программ каждая команда выполнялась машиной не более одного раза, так что общее число операций, выполняемых машиной по каждой из таких
308 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 программ, не превосходит числа команд в программе. Если бы это обстоятельство имело место всегда, то эффективность использова- ния ЭВМ была бы крайне мала — хотя бы потому, что время, затра- чиваемое человеком на получение каждой машинной команды, срав- нимо со временем выполнения одной операции с использованием, например, настольных вычислительных полуавтоматов, являвших- ся до появления ЭВМ основными вычислительными средствами. Однако тот факт, что программа во время ее выполнения на ЭВМ хранится в памяти машины и что все ячейки памяти одинаково до- ступны для процессора, позволяет машине ту или иную группу команд выполнять многократно, т. е. циклически. Это обстоятель- ство является очень важным, ибо дает возможность с помощью сравнительно небольшого числа команд в программе заставить ма- шину произвести большой объем вычислений. Именно поэтому цик- личность алгоритмов является наиболее важным их качеством. Чтобы обеспечить многократное выполнение машиной какой- либо группы команд (которую и будем называть циклом), достаточно завершить эту группу командой перехода на первую ее команду, причем, чтобы это повторение было не бесконечным, а выполнялось нужное число раз, этот переход должен быть условным, в зависи- мости от выполнения некоторого условия, определяющего необхо- димость очередного повторения этой группы команд. При этом весь- ма типичным является случай, когда момент окончания цикличе- ского вычислительного процесса определяется текущим значением некоторой переменной, называемой параметром цикла, так что достаточно типичной является изображенная на рис. 5.4 схема циклической программы или ее части: Рис. 5.4. Поскольку цикличность является наиболее характерной особен- ностью подавляющего большинства практически используемых ал- горитмов, вопрос программирования циклов является одним из важнейших в программировании. Примерами такого рода циклических алгоритмов являются алго- ритмы, задаваемые операторами цикла в алголе. Заметим, что общая методика программирования таких операторов фактически уже была дана при рассмотрении семантики этих операторов, так как для каждого типа элемента списка цикла был показан способ замены оператора цикла последовательностью других операторов, к про- граммированию которых и сводится программирование исходного оператора цикла (программирование условных или составных one-
8.7Г ПРОГРАММИРОВАНИЕ ЦИКЛОВ 309 раторов не содержит каких-либо новых моментов по сравнению с рассмотренными ранее). Особенности программирования циклов связаны с необходи- мостью особенно тщательной их оптимизации, поскольку при вы- полнении программы подавляющая часть машинного времени за- трачивается именно на выполнение циклов. В ряде случаев несколь- ко лишних команд или использование более сложных (длительных) машинных операций в цикле могут весьма заметно сказаться на общем времени выполнения программы на машине из-за того, что эти лишние команды или сложные операции будут выполняться мно- го раз. Поскольку программирование операторов цикла сводится к программированию последовательности других операторов, то все рассмотренные ранее источники оптимизации остаются в силе. Не- которые общие источники оптимизации, характерные для циклов (вынесение за пределы цикла вычисления значений выражений, не изменяющихся внутри цикла; понижение сложности используе- мых в цикле операций и др.) были рассмотрены в главе 2. Наряду с этим при машинной реализации циклов возникают и дополнительные источники оптимизации программ. Общим новым моментом, который возникает при программировании любого цикла, является организация нужного числа повторений цикла, которая может быть различной для разных циклов. Одним из дополнитель- ных источников оптимизации и является учет специфики програм- мируемого цикла при решении вопроса об организации числа его повторений. Кроме того, для повышения эффективности цикличе- ских программ в ЭВМ обычно предусматривается ряд специальных операций, использование которых также позволяет получить более качественную программу. 5.7.2. Циклы итерационного типа. Для циклов итерационного типа характерным является то обсто- ятельство, что число повторений цикла заранее не известно, а опре- деляется по ходу вычислений в зависимости от получаемых резуль- татов. Наиболее характерный представитель циклов этого вида зада- ется оператором цикла алгола с элементом списка типа пересчета: for V:=A while В do S Здесь оператор S должен выполняться нуль или более раз, причем перед каждым его новым повторением значение параметра цикла V перевычисляется по одним и тем же правилам, задаваемым арифмети- ческим выражением А. Этот процесс должен закончиться, как только при очередном (может быть и самом первом) значении V логическое выражение В примет значение false — при этом значении V опера- тор S выполняться уже не должен. Как видно, здесь число выпол- нений оператора S в общем случае заранее не известно, а зависит от получаемых результатов. Циклы подобного рода чаще всего
310 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. & встречаются при использовании итерационных методов решения задач, поэтому их обычно называют итерационными циклами. При рассмотрении семантики операторов цикла был показан общий способ замены такого оператора цикла последовательностью других операторов: L: V:=A; if "]В then go to M; (5.1} S; go to L; M: К программированию этой последовательности операторов и сво- дится программирование исходного оператора цикла. Однако для сокращения числа команд в цикле такую замену в ряде случаев бывает целесообразно сделать несколько иначе. Если, например, известно, что одно дополнительное выполнение оператора S не приведет к неправильным результатам (что всегда имеет место в итерационных процессах), то приведенный выше оператор цикла можно заменить следующей последовательностью операторов: L: V:=A; S; (5.2) if В then go to L Здесь в цикле содержится только один оператор перехода вместо двух в общем случае, что позволяет сократить число команд в цикле и в ряде случаев получить экономию в числе требуемых тактов (если число повторений цикла больше числа тактов, требуемых на одно дополнительное выполнение оператора S). Пример 5.9. Составить программу вычисления у = ]^х с заданной точностью е>0, пользуясь итерационным методом Нью- тона: Уп+1=Уп+(х/уп—уп)/2 (n=0, 1, . . .) который сходится при любом начальном приближении t/0. Итера- ционную формулу можно записать в виде Уп+1=Уп+оа> где vn=(x/yn—yn)/2 является поправкой к предыдущему прибли- жению уп. Будем считать, что требуемая точность достигнута, если очередная поправка достаточно мала, т. е. при |vn|<8 эту поправку можно уже не учитывать и прекратить процесс вычислений. Заметим, что значения уп и vn используются только для полу- чения очередного приближения, так что длительное их хранение не имеет смысла и поэтому значения очередного приближения и оче- редной поправки можно присваивать одним и тем же простым пере- менным (т. е. хранить в одних и тех же ячейках памяти), Таким об- разом, этот вычислительный процесс можно описать следующей блок-схемой (рис. 5.5).
5.7] ПРОГРАММИРОВАНИЕ ЦИКЛОВ 311 На алголе этот алгоритм можно записать в виде (приняв а—1): у:=1; for и:=(х/г/—у) 12 while abs (vj^eps do y:=y+v Учитывая, что оператор S в данном случае содержит всего одну опе- рацию и что его дополнительное выполнение только повысит точ- Рис. 5.5. пость получаемого результата, замену оператора цикла последова- тельностью других операторов можно сделать по схеме (5.2): Z/: = l; L:v:=(x/y—у)/2\ y:=y+v, if abs (v) eps then go to L Программа, реализующая этот алгоритм, будет иметь вид: п ЕД — У ; L: Д X Y В Y /?1; rl = V=(X/y—у)/2 ВЛД 1 7?1 С Y У ; y:=y+v ВА /?1 EPS <3):—abs (v)<.eps ПУ — L Как видно, приведенная здесь последовательность команд, начиная с метки L, будет выполняться машиной многократно — до тех пор, пока не будет достигнута заданная точность результата, так что по этой небольшой программе машина на самом деле может выполнить большое количество операций (если для достижения заданной точ- ности необходимо сделать большое число итераций). Заметим, что число итераций, необходимых для получения требуемой точности результата, существенно зависит от выбора начального приближе- ния yQ. Поэтому для получения эффективной программы, предназначенной для решения поставленной задачи, следовало бы для каждого значения аргу- мента х выбирать свое начальное приближение у0, учитывая, например, что если х = то Конечно, для получения хорошего началь- ного приближения потребуются дополнительные команды, но поскольку они будут выполняться один раз, до входа в цикл, то общее быстродействие про- граммы повысится за счет сокращения числа повторений цикла, необходимых для достижения требуемой точности. Этот пример иллюстрирует тот факт, что в ряде случаев быстродействие программы может быть повышено за счет уве- личения общего числа команд (и наоборот). Поскольку, однако, подобного рода вопросы относятся скорее к курсу «методы вычислений», мы в дальней- шем на них останавливаться не будем. Элемент списка типа пересчета может задавать и вполне опреде- ленное число повторений цикла, причем дополнительное выполне-
312 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ (Гл. 5 ние оператора S может привести к неправильному результату. Такая ситуация имеет место, например, в алгоритме у:=а\ i:=0; for i: =i+1 while i^20 do y:=yXx предназначенном для вычисления y=ax™ (без использования опе- рации возведения в степень). Однако указанная выше более эф- фективная схема замены (5.2) может быть использована и в этих случаях, если известно, что оператор S должен выполняться хотя бы один раз — для этого достаточно соответствующим образом скорректировать условие окончания (или продолжения) цикла: у:=а\ i:=0; L: у.=уХх\ if i=/=20 then go to L Рассмотренный выше способ экономии команд в цикле применим и для операторов цикла с элементом списка типа арифметической прогрессии: если известно, что в операторе цикла вида for V:=A1 step А2 until A3 do S оператор S должен выполняться хотя бы один раз, то данный опе- ратор цикла можно заменить следующей последовательностью дру- гих операторов, в которой также содержится только один оператор перехода вместо двух в общем случае: V:=A1; L: S; V:=V+(A2); if sign (A2)X(V—(A3))<0 then go to L В большинстве случаев знак А2 известен заранее — в таких слу- чаях условие можно записать в виде if V^A3 then при А2>0, и в виде if V^A3 then при А2<0. При этом, как уже отмечалось ранее, вычисление тех частей выражений Al, А2, АЗ, которые не меняются в цикле, следует производить до входа в цикл. Следует выделить еще один часто встречающийся случай опера- тора цикла, который имеет вид for V:=A1, А2 while В do S Здесь список цикла состоит из двух элементов. Замена при про- граммировании этого оператора цикла последовательностью дру- гих операторов по общей схеме может привести к неэффективной программе. Однако здесь первый элемент списка гарантирует, что оператор S заведомо выполняется хотя бы один раз, и поэтому та- кой оператор цикла может быть заменен следующими операторами: V:=A1; L: S; V:=A2; if В then go to L 5.7.3. Циклы с известным числом повторений. Весьма типичными являются циклы, число повторений которых известно заранее — либо во время составления программы, либо к моменту входа в цикл.
5 7) ПРОГРАММИРОВАНИЕ ЦИКЛОВ 313 / Циклические процессы подобного рода на алголе чаще всего за- даются операторами цикла с элементом списка типа арифметиче- ской прогрессии. Среди таких операторов цикла заслуживает вни- мания наиболее часто употребляемый оператор цикла вида for V: = l step 1 until n do S где V — целочисленная переменная, n — целое число или цело- численная переменная, значение которой не изменяется в процессе выполнения данного оператора цикла. В этом случае число повторений цикла известно заранее или к моменту выполнения оператора цикла и управление числом повто- рений может быть реализовано достаточно просто. Если это число заведомо отлично от нуля, т. е. оператор S должен выполняться хотя бы один раз, то в соответствии с содержательным смыслом данного оператора цикла его можно заменить последовательностью других операторов по схеме: V:=l; L: S; V:=V+1; if V#=n+1 then go to L или по схеме: V:=0; L: V:=V+1; S; if V=/=n then go to L В данном случае параметр цикла выполняет роль счетчика числа повторений цикла: к моменту выполнения условного оператора значение V показывает, какой по счету раз должен будет повто- ряться цикл (в первой из схем замены), либо какой по счету раз был повторен цикл (во'второй схеме замены), и условием окончания цик- ла служит совпадение текущего значения V с его известным конеч- ным значением. Программирование каждой из приведенных выше двух схем не содержит каких-либо новых элементов и может быть выполнено с использованием рассмотренных ранее приемов. Однако во многих случаях управление числом повторений цикла может быть осуще- ствлено более эффективно за счет использования соответствующих* машинных операций, в частности — операций условного перехода по содержимому индексных регистров. Операции подобного рода обычно присутствуют в наборе операций машины, имеющей в своем составе индексные регистры. Такие операции есть и в наборе опера- ций УМИР-3. При выполнении команды, содержащей код каждой из таких операций, производится сравнение текущего содержимого регистра / либо с первым исполнительным адресом А1ИСП в данной команде, либо с числом К, представленным в машинном слове, которое хра- нится в памяти по адресу А\ИСП. В последнем случае это машин- ное слово трактуется как команда, и в качестве К принимается вто- рой адрес Л2 в этой команде (адреса Л1 и ЛЗ в этой команде должны
314 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 быть равны нулю, а код операции может быть произвольным). На- пример значение К=5 должно быть представлено по адресу А1ИСП в виде машинного слова А1 ИСП.лв 0000 0005 0000 В зависимости от результата сравнения значения F с А\ИСП (или с числом К) осуществляется переход либо по адресу А2ИСП* либо к следующей по порядку команде. Операция перехода в коман- дах, содержащих код каждой из этих операций, совмещена с опе- рацией занесения в индексный регистр нового значения, в качестве которого берется адрес АЗИСП, так что по одной команде можно осуществить и условный переход, и изменение текущего содержимого индексного регистра. Операции этой группы сведем в таблицу, в которой указано усло- вие перехода по адресу А2ИСП — при невыполнении этого условия сохраняется естественный порядок выполнения команд. Выполнение каждой операции перехода завершается действием Р\~АЗИСП независимо от выполнения условия перехода. Значение по этим операциям не изменяется. Название операции 0 Переход при F меньше адреса /7Л4(12) Переход при F не меньше адреса ПН(32) Переход при F меньше слова /7Л1С(40) Переход при F не меньше слова /7ЯС(60) Условие перехода по А2ИСП F < АХИСП F^ АХИСП F<K F^K Такт работы УМИР-3 при выполнении команд с данными опе- рациями отличается от типового такта: этап 6° здесь отсутствует, а этапы 5° и 7° при выполнении, например, операции 0=12, получают следующее содержание: 5°. itF<AXHCn then С:=Д2ЯС/7; 7°. F-^АЪИСП Отсюда видно, что исполнительные адреса формируются с использо- ванием старого значения F, которое было к началу выполнения команды, что это же значение F используется в сравнении, и что новое содержимое регистра F формируется независимо от резуль- тата сравнения. Рассмотрим использование приведенных выше операций услов- ного перехода на следующем примере. Пример 5.10. Составить программу для вычисления #=ах21. Поскольку в машине нет операции возведения в степень, то сведем ее к последовательности операций умножения по следующему алго- ритму: у:=а; for r. = l step 1 until 21 do у:=уХх Произведем замену оператора цикла следующим образом: у:=а\ Z:=0; L: у:=уХх; f:=H-l; if £<21 then go to L
6.7] ПРОГРАММИРОВАНИЕ ЦИКЛОВ 315 В этом алгоритме значение i используется только для управления числом повторений цикла, поэтому значение i будем получать в индексном регистре, используя операцию условного перехода по содержимому этого регистра для управления числом повторений цикла. Здесь число повторений цикла известно заранее, поэтому можно использовать одну из операций, в которых значение F срав- нивается с А\ИСП. Поскольку по команде условного перехода можно одновременно изменить и значение i (т. е. содержимое ре- гистра F), то для управления числом повторений в цикле достаточно одной команды: П А — У; у:=а РА — 0 —; F:=0 L: У У X У; у:=уХх ПМ 21 L 1( ); при F<21 переход на L; F: — F+1 Заметим, что использование непосредственного адреса в команде условного перехода позволяет не запасать в программе константу, представляющую число 21. Заметим также, что программа для вы- числения у=ахп (п>0 — целое) отличалась бы от приведенной программы только последней командой (ПМС N L 1()), если зна- чение п задано в ячейке N в виде условно-целого числа. 5.7.4. Управление переменными адресами. Как мы уже отмечали, на практике наиболее часто использу- ются такие циклы, в которых одна и та же группа команд последо- вательно применяется к различным элементам какой-либо структу- ры, и в этих случаях в программе появляются переменные адреса, с помощью которых производится ссылка на разные элементы этой структуры. Для управления такими переменными адресами прежде всего и служат индексные регистры. С управлением переменными адресами с помощью индексных регистров в простейших случаях мы уже знакомы по главе 4. Здесь мы остановимся на управлении переменными адресами с помощью индексных регистров и в более сложных случаях. Напомним, что идея использования индексных регистров за- ключается в расчленении переменного адреса на две составляющие: постоянную и переменную. Постоянная составляющая определяет- ся только распределением памяти, но не зависит от индексов, а переменная составляющая зависит от значений индексов. Для ссыл- ки на значение переменной с индексами в том или ином поле адреса в команде указывается постоянная составляющая А данного пере- менного адреса, и этот адрес снабжается признаком модификации по определенному индексному регистру. При составлении програм- мы должно быть обеспечено, чтобы к моменту выполнения этой ко- манды в указанном индексном регистре находилась переменная составляющая V данного переменного адреса. Поскольку при вы- полнении команды используются исполнительные (модифициро-
316 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 ванные) адреса, то фактическое обращение к памяти будет произ- ведено по исполнительному адресу АИСП—А+V. В простейшей случае, когда все переменные адреса имеют одну и ty же переменную составляющую, программы для УМИР-3 будут аналогичны программам, рассмотренным в главе 4. Пример 5.11. Даны вещественные векторы х, у [0:10]. ю Составить программу для вычисления г= 2 xt-yt. z=o Алгоритм решения этой задачи на алголе можно записать так: z:=0; for t:=0 step 1 until 10 do z:=z+x [ilXy [t] Пусть компоненты вектора x размещены в группе ячеек памяти с адресами 1000—1012, а компоненты вектора у — в группе ячеек с адресами 1013—1025. Тогда адр.х [Л=1000-Н=Х+», адр.у [i]=1013+i=y+i. Если переменной г предварительно присвоено нулевое значение, то все дальнейшие вычисления реализуются парой команд вида У X+'l Y+i R-, Cl R Z-, которые надо последовательно выполнить для всех значений i от 0 до 10. Как видно, здесь значение параметра цикла i используется толь- ко в качестве переменной составляющей в адресах X+i и Y+i и для управления числом повторений цикла. В связи с этим значение i можно получать только в индексном регистре F, используя его и для управления переменными адресами, и для управления числом пов- торений цикла, так что решение поставленной задачи можно полу- чить по следующей программе: П 0 — Z; z:=0 РА — () —; F:=0 (i:=0) L: У Х() У () R\ r:=x[i]Xy[i] С Z R Z; z:=z+r ПМ 10 L 1 (); при Г<10 переход на L; Г:=Е-Ь1 Поскольку выполнение операции ПМ завершается занесением в регистр F значения АЗИСП—A3+F=F+l, что в данном случае означает увеличение значения F на единицу, то первый и второй ис- полнительные адреса в команде L при каждом очередном ее выпол- нении увеличиваются на единицу, хотя сама эта команда в памяти остается неизменной. Если нумерация компонент какого-либо вектора а начинается не с нуля, а с некоторого целого числа k, то при нашем соглашении ставить в соответствие идентификатору массива адрес (а при сим- волическом программировании — имя) А той ячейки, начиная с
6.7] ПРОГРАММИРОВАНИЕ ЦИКЛОВ - 317 которой фактически размещаются компоненты массива, будем иметь адр.а [П=Л+ (i—k)=(A—k)+i где (Л—k) — постоянная составляющая переменного адреса, ко- торую условимся называть адресом нулевой компоненты (т. е. ком- поненты а [0], которой на самом деле может и не быть). Так, если бы в примере 5.11 компоненты векторов были перенумерованы от* 2 до 12, то при прежнем распределении памяти мы имели бы адр.х [i] = 1000+(i—2)=7776+i=(X—2)+i=adp.x [014-i адр.у [i]=1013+(t—2) = 1011-Н=(У—2)+i=adp.y (01+i и программа имела П 0 РА — L: У X—2() С Z ПМ 12 бы вид: — Z; г:=0 2 __; F:=2 У—2() R- r:=x[F]xy[F} R Z; z:—z+r L 1(); при F<12 переход на L; F:=F-\-l В подобных случаях при программировании бывает удобно изменить нумерацию компонент. Так, если бы в рассматриваемой задаче фигурировали векторы х [2 : 12] и у [2 : 12], размещенные в памяти начиная с ячеек X и У соответственно, то при составлении про- граммы их можно рассматривать как векторы х [0 : 10] и у [0 : 10] с тем же размещением в памяти. Поскольку в данном случае нумера- ция компонент безразлична, то первый вариант программы на самом деле годится и для другой нумерации компонент. Если в вычислениях одновременно используются компоненты вектора (векторов) с разными номерами, например хк и xt то переменные составляющие адресов будут, вообще говоря, различ- ными, и управление такими переменными адресами усложняется. Однако один из часто встречающихся случаев, когда I—fe=const, легко сводится к рассмотренному выше случаю: поскольку l—C+k (С — константа), то адр.х [k\~ адр.х [0]+& адр.х [1}=адр.х [0]-Н=(адр.х [0]+С)-|-&. В итоге переменные составляющие у каждого из адресов оказались одинаковыми — обе они равны значению k. Поэтому для получения нужных исполнительных адресов в регистре F достаточно иметь значение k-, для ссылки на компоненту хк в программе надо указать адр.х {0], а для ссылки на компоненту хг— адрес (адр.х (ОЦ-С), снабдив каждый из этих адресов признаком модификации по ин- дексному регистру. До сих пор, рассматривая вопросы управления переменными ад- ресами, мы имели в виду простейшую структуру — одномерный массив. Особенность этой структуры состоит в том, что порядковый номер элемента структуры однозначно определяет и место этого эле-
.318 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 мента в памяти (т. е. его адрес), и если для размещения каждого эле- мента отводится одна ячейка, то переход к следующей по порядку ячейке означает и переход к следующему по порядку элементу, так что последовательный перебор всех элементов структуры обеспе- чивается последовательным увеличением переменного адреса на единицу, начиная с некоторого его начального значения. Однако, как мы знаем из главы 3, некоторые структуры отобра- жаются на одномерный массив (а значит, и на линейный участок па- мяти) другим способом — при помощи сцепления. В этом случае структура состоит из отдельных элементов — звеньев, каждое из ко- торых состоит из информационной части и справки, в которую входит ссылка на следующее звено. С помощью ссылок и осуществляется сцепление звеньев в единую цепочку. Здесь последовательные эле- менты структуры могут размещаться в памяти в произвольном по- рядке, поэтому характер зависимости адреса элемента от его номера отличается от рассмотренных выше случаев — эту зависимость нельзя выразить в виде определенной формулы. Это обстоятельство влечет за собой определенную специфику в управление перемен- ными адресами. Рассмотрим эту специфику на примере одной из простейших та- ких структур — строки, которая отображается на одномерный массив Т [О:АП. Напомним (см. раздел 3.3), что под строкой понимается упоря- доченная последовательность отдельных символов. Строка начи- нается с заголовка, который размещается в фиксированных пози- циях массива Т (с номерами 0 и 1), причем в позиции с номером О содержится ссылка на первое звено строки, а в позиции с номером 1 — ссылка на первую свободную позицию в массиве Т. Каждое звено строки содержит две составляющие: ссылку sz на следующее по порядку звено (т. е. sz есть номер позиции в массиве Т, начиная с которой размещается это следующее звено) и инфор- мационную часть rz (код символа). Последнее звено строки содержит ссылку, равную нулю, что и является признаком конца строки. При представлении строки в памяти УМИР-3 мы условимся от- водить для ее хранения массив ячеек памяти с последовательными адресами (который также будем обозначать через Т). Для эконом- ного расходования памяти каждое звено строки будем представлять одним машинным слово. В качестве ссылки sz в очередном звене, как обычно, примем относительный номер ячейки в массиве Т, в которой размещается следующее звено. Ссылку будем представлять в поле второго адреса машинного слова, а код символа — в поле третьего адреса: а: ООО 0000 sz rz Если через Т, как обычно, обозначить и адрес ячейки, начиная с которой в памяти размещается массив с именем Т, a sz есть ссылка, содержащаяся в текущем звене, то адрес следующего звена строки
6.7] ПРОГРАММИРОВАНИЕ ЦИКЛОВ 31» равен T+s^ Таким образом, Т есть постоянная составляющая пе- ременного адреса, с помощью которого производится ссылка на текущий элемент структуры, а переменной составляющей этого ад- реса является соответствующая ссылка. Это обстоятельство и сле- дует учитывать при управлении переменными адресами в программе, задающей действия над элементами такой структуры. Приме'р 5.12. Пусть дана строка, элементами которой явля- ются буквы русского алфавита, причем в качестве кода буквы при- нимается ее порядковый номер. Требуется подсчитать число вхож- дений I в эту строку буквы, код которой задан значением целочис- ленной переменной В — этот код представлен в поле третьего адреса машинного слова (в остальных его разрядах содержатся нули). Если исходить из обычного предположения, что в массиве Т для каждого звена строки отводится пара позиций, в первой из ко- торых содержится ссылка на следующее звено, а во второй — код символа, то алгоритм решения поставленной задачи на алголе можно записать так: /:=0; i:=0; М: i:=T [i]; if i=0 then go to KOH', if T [i+ll=B then /:=/+!; go to M-, KOH: При составлении символической программы для УМИР-3 учтем, что каждое звено строки представляется одним машинным словом, из которого надо выбирать как ссылку sh так и код символа г{. Значение I будем получать в виде условно-целого числа, т. е. это значение будем представлять в поле второго адреса соответст- вующего машинного слова. Если запасти константы С1: ООО 0000 0001 0000; С2: 000 0000 0000 7777; то поставленная задача может быть решена по следующей программе: п 0 — L\ /:=0 РА — 0 t:=0 М: PC — Т( ) —• i:=T[t| ПМ 1 кон 0(); при i=0 переход на КОН И ТО С2 выделение кода символа г{ Н R В • ПУ — м при г^В, переход на М СМ L с\ L-, /:=/+! ПБ — м , переход на М КОН: При первом выполнении команды с меткой М в регистр F из заго- ловка строки будет выбрана ссылка на первое звено. Следующая по порядку команда проверяет, имеется ли в строке такой очередной
320 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 элемент: если ссылка, зафиксированная в регистре F, равна нулю {первый раз это может иметь место в случае пустой строки), то эта команда осуществляет переход по метке КОН и на этом обработка строки заканчивается. В противном случае выполняются команды, обрабатывающие этот очередной элемент. Эта обработка завершается переходом на команду с меткой М, которая заносит в регистр F •ссылку, содержащуюся в обработанном звене, и тем самым осущест- вляется переход к следующему звену. Зависимость адреса компоненты от значений индексов заметно усложняется в случае многомерных массивов, поскольку эта зави- симость теперь будет определяться и способом размещения в памяти отдельных компонент, и размерами массива по каждому измере- нию. Например, матрицу а [1 : п, 1 : т] можно разместить в памяти, начиная с ячейки А, двумя способами. При ее размещении «по стро- кам» сначала в т последовательных ячейках размещаются элементы первой строки, в следующих т ячейках — элементы второй строки и т. д. В этом случае адрес компоненты a [i, /] (i — номер строки, j — номер столбца) будет определяться формулой адр. a[i, j]=A+m(i—1)+(/—1)=(Л—т—l)+tni+j= =адр. а [0,0]4-/ш'+/, где (Л—т—1) — постоянная составляющая адреса (адрес «нуле- вой» компоненты). Если же матрицу разместить в памяти по столбцам, то адрес компоненты a [i, j] будет определяться по другой формуле: адр. а К, /]=Л+п(/—l)+(i—1)=(Л—п—l)+n/+i= =адр. а [0, 0)+n/+i. Мы условимся компоненты многомерного массива размещать в па- мяти в лексикографическом порядке, т. е. в порядке возрастания •старшинства наборов значений индексов, определяющих положение компоненты в массиве. При этом из двух наборов значений индексов старшим считается тот, у которого при сопоставлении соответству- ющих индексов слева направо раньше встретится индекс, име- ющий большее значение. Данному порядку размещения соответ- ствует размещение матрицы по строкам. Как видно, в случае многомерного массива для модификации адресов в индексном регистре надо иметь значение переменной сос- тавляющей, получаемое по более сложному правилу. Поскольку любой многомерный массив при его представлении в памяти на самом деле сводится к приведенному одномерному массиву, то при программировании в ряде случаев бывает удобно исходный массив трактовать как одномерный массив, используя для указания нужной его компоненты приведенный индекс, како- вым является ее порядковый номер в одномерном массиве (см. разд. 3.6). Например, упомянутую выше матрицу а [1 : п, 1 : ml можно трактовать как вектор а [1 : nXm], а для ссылки на нужную компоненту вместо записи a [i, /1 использовать запись
5.7) ПРОГРАММИРОВАНИЕ ЦИКЛОВ 321 a [k], где k — приведенный индекс Если заданный алии ритм требует последовательного просмотра всех компонент исход* ного массива, то действия над таким массивом при программирова- нии можно свести к тем же действиям над приведенным массивом, используя рассмотренные ранее приемы программирования. Например, алгоритм замены каждого отрицательного элемента матрицы а [1 : 10,1 :20] его квадратом можно записать на алголе в виде оператора цикла for i:=l step 1 until 10 do for /:=1 step 1 until 20 do if a U, /]<0 then a [i, j]:=a [i, /1 f 2 Однако для составления программы исходную матрицу удобно рас- сматривать как вектор а [0:199], а алгоритм записать в виде for t:=0 step 1 until 199 do if a [i]<0 then a [i]: = a [i] f 2 Программная реализация этого алгоритма очевидна. В рассмотренных выше примерах имел место простейший слу- чай, когда все модифицируемые адреса имели одну и ту же перемен- ную составляющую, так что при их модификации можно было ис- пользовать одно и то же содержимое индексного регистра, равное текущему значению этой общей переменной составляющей. В общем же случае разные адреса, подлежащие модификации, могут иметь различные переменные составляющие, так что при их модификации нельзя использовать одно и то же содержимое индексного регистра. Естественно, в таких случаях возникают определенные трудности при программировании. Эти трудности преодолеваются сравнительно легко, если в ма- шине имеется несколько индексных регистров и система команд позволяет модифицировать каждый адрес по любому из индексных регистров, а еще лучше — сразу по нескольким индексным регист- рам. Однако на самом деле число индексных регистров обычно весьма ограничено, поэтому достаточно типичной является задача модифи- кации адресов с использованием недостаточного числа индексных регистров, и как крайний случай — с использованием только од- ного индексного регистра (что и имеет место на машине УМИР-3). Общая методика программирования в подобных случаях состоит в том, что различные переменные составляющие модифицируемых адресов помещаются в ячейках памяти (в таком виде, чтобы их легко можно было переслать в индексный регистр) и в нужные моменты времени передаются в индексный регистр для модификации соот- ветствующих адресов, так что один и тот же индексный регистр используется для временной фиксации значений разных перемен- ных составляющих модифицируемых адресов. ' Если команда допускает модификацию адресов только по одному индексному регистру, а в ней используются адреса с разными пере- Э. 3. Любинский и др.
322 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ (Гл 5 менными составляющими, то приходится выбирать такую последо- вательность команд, чтобы в одной и той же команде не фигуриро- вали адреса с различными переменными составляющими. Различные переменные составляющие у модифицируемых адресов - чаще всего встречаются во вложенных циклах, поэтому данную ме- тодику особенно часто приходится применять при программирова- нии таких циклов. Проиллюстрируем ее на следующем примере. Пример 5.13. Даны целые и, т, вещественные вектор х [1:ти] и матрица 1 :ml. Получить вектор у [1:п], являю- щийся произведением матрицы а на вектор х: (i = l, 2, .... n). /=1 Решение этой задачи можно осуществить по следующему алгоритму: for г. = 1 step 1 until п do begin w:==0; for /: = 1 step 1 until m do IA:u:=u+a [i, j]Xx[j]\ L2:y U‘]:=w end Пусть массивы x, у и а размещены в памяти, начиная с ячеек с адресами X, Y и А соответственно. Тогда адр.х [j]=(X—l)+j, adp.y\i]=(Y—\)+i, адр.а [i, j]=A+m (i—1)+(/—1)=(Л—1)+/ = =(A—\)+k. Из приведенных формул видно, что переменные составляющие всех трех переменных адресов различны. Переменные адреса будут ис- пользоваться в командах, реализующих операторы с метками LA и L2. Чтобы в одной и той же команде не использовались адреса с различными переменными составляющими, заменим оператор с мет- кой ZJ двумя операторами: L1: г:=х1/1; u:=u+rXa [i, /1 При программировании надо предусмотреть, чтобы в регистре F во время выполнения первого из них находилось значение /, а во время выполнения второго — значение k. При выполнении опера* тора с меткой L2 в регистре F должно находиться значение I. Поскольку значение k зависит и от /, и от /, представим эту ве- личину в виде двух слагаемых, каждое из которых зависит только от одного из параметров цикла: k=p+j, где р=т (i—1). Теперь конкретизируем вопросы управления переменными адре- сами и числом повторений каждого из циклов. Рассмотрим сначала внутренний цикл. Будем исходить из того, что к началу выполнения оператора цикла по параметру / в ячейках памяти хранятся значе- ние i и соответствующее ему значение p=m(i—1). Тогда текущее значение j будем получать в регистре F, и лишь на то время, когда
6Jj ПРОГРАММИРОВАНИЕ ЦИКЛОВ , 323 значение j должно использоваться для вычисления переменной cqc* тавляющей k=p+j адреса адр. a Ji, /] и регистр F должен использо- ваться для хранение этой переменной составляющей, значение / будем запоминать в ячейке памяти. Таким образом, внутренний оператор, цикла заменим следующей последовательностью операто- ров, в которой учтено использование индексного регистра: F: = l; LI: r.^x [F]; /:=F; k:=p+j- F:=k-f u:=u+rxa IF]; F:=/; „ if F<.m then begin F:=F-H; go to LI end При программировании внешнего цикла текущее значение I так- ' же будем получать в регистре F, используя его для управления пере-, менным адресом адр. у |«] и для управления числом повторений этого ’ цикла. Значение p—(i—1) т, соответствующее текущему значению i, будем получать в ячейке памяти. На время выполнения внутреннего цикла значение i, находящееся в регистре F, будем запоминать в" ячейке памяти, а по окончании выполнения внутреннего цикла — восстанавливать это значение I в регистре F: F:=l; р:=0; Q: ы:=0; i:=F; F: = l; LI: r.—x IF]; /:=F; ft:=p+/; F:=fe; a:=u+rXfl IF]; F:=j; if F<jn then begin F:=F+1; go to LI end; F:=i; L2: у [F]:=«; p:=p+nr, if F<n then begin F:=F-H; go to Q end При составлении программы учтем то обстоятельство, что по од- ной команде с операцией РА или PC можно и запомнить текущее „ содержимое регистра F в памяти, и занести в этот регистр новое содержимое. В итоге получим следующую программу: РА 1 —; это соответствует i:=l П — — Р; р:=0 Q: П — — U; и:=0 РА 0() 1 /; i:—F\ F: = l (запоминание i и /: = 1) L1: П X—1() — R; r:=xlj] РА 0() 0() J-, j:=F СМ Р J К', k:=p+j PC — к —F:=k У R А— -1() R: r:=rXa[i,j] си R U\ w.=u-\-r PC — J F:=/ ПМС М L1 1 (); при j<m переход на LI и /:=/-]-1 PC — / —; F:=i 11»
324 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 L2: П U — Y—1 (); у [i]:=u СМ Р М Р\ р-.=р+т ПМС N Q 1(); при i<n переход на Q, и Читателю предлагается проследить выполнение этой программы при каких-либо конкретных значениях пит (например, при п=2 и т=3). В заключение рассмотрим пример циклической программы, предназначен- ной для решения задачи неарифметического характера. Пример 5.14. Дан целочисленный вектор /[0:100], первые по порядку компоненты которого представляют собой коды основных символов алгола, причем цифры кодируются целыми числами от 0 до 9, а буквы—последова- тельными целыми числами от 10 до некоторого числа (обозначим его через /V), определяемого числом допустимых букв в конкретном представлении языка. Таким образом, вектор t трактуется как код некоторого текста на алголе. Предполагается, что текст содержит не более 100 основных символов и завер- шается признаком конца (обозначим его через Q), код которого есть 3778. Требуется составить программу, которая логической переменной I при- сваивает значение true, если заданный текст является идентификатором, а значение false—в противном случае. Чтобы текст был идентификатором, надо, чтобы первый его символ был буквой, и чтобы в нем не встречались символы, отличные от букв и цифр. Один алгоритм решения такой задачи был приведен в разд. 2.9.1. Здесь он будет несколько видоизменен. begin integer/; if /[0] < 10v/[0] > AZ then go to Л4; /:= true; /:= 0; for /:= H 1 while /(/] Q do if /(/] > N then go to Af; go to KOH\ Af:Z:= false; KOH: end Поскольку диапазон используемых в данной задаче целых (неотрицатель- ных) чисел, являющихся кодами основных символов алгола, весьма ограничен, то было бы нерационально отводить для каждого из них отдельную ячейку памяти. Поэтому будем считать, что каждый код символа представляется одним байтом (восьмеркой битов), а в каждой ячейке хранится по 5 кодов символов: если для хранения текста отведена группа ячеек памяти, начиная с 4, то будем считать, что последовательные коды символов // (/ = 0, 1, 2, ,..) раз- мещаются следующим образом: А 4+1 45... 40... 32... 24... 16... 8. .1 0...0 Zo 6 t9 t< 0...0 t» ... Такой способ обеспечивает компактное хранение текста в памяти, хотя и за- трудняет работу с ним, так как очередные его символы приходится выбирать из разных позиций ячеек. Теперь можно считать, что мы имеем дело с матрицей а[0:19, 0:4], каж- дая очередная строка которой представляет коды очередной пятерки символов текста. Чтобы упростить программу, при обработке текста будем выносить оче- редную строку с номером i матрицы а (представленную одним машинным сло- вом) в фиксированную ячейку, содержимое которой будем трактовать как
6.7] ПРОГРАММИРОВАНИЕ ЦИКЛОВ 32$ ве*™Р 6(0:4]—будем считать, что для этого введена в употребление процедура Тогда алгоритм, который будет реализован программой, на алголе можно ваписать в виде следующего блока: begin integer Z, /, г; integer array 6(0:4); r:=a(0, 0]; if r < 10 vr > W then go to Af; Z:=true; Z:=0; HA4:BblB(i)\ Z:=Z+1; /:=0; P:r:=6[/]; if r = Q then go to KOH; if r > N then go to M; if j < 4 then begin go to P end; go to НАЧ; Af:Z:= false; KOH: end При составлении программы условимся значение г представлять в младших разрядах машинного слова—тогда для выделения символа bj из слова Ъ надо это слово сдвинуть вправо на 8(4—/) разрядов и у сдвинутого слова выде- лить младшие 8 разрядов, записав нули в остальные разряды. Поскольку величина сдвига меняется при изменении /, то будем задавать ее порядком q промежуточной переменной s. В каждом из циклов значение соответствующего параметра (Z и /) будем хранить в индексном регистре. Если запасти следующие константы: Т.000 0000 0000 0001; true С 1:000 0000 0000 0012; 10 С2:000 0000 0000 N ; Код последней буквы СЗ:И0 4000 - 0000 0000; Число х = (1/2)-2“32 С4:000 0000 0000 0377; Код Q и маска для выделения 8 младших разрядов слова то программа будет иметь вид*. СДСА —32 А R; г:=а(0, 0] ВМ R С\ —; (о:= г < 10 ПЕ — М —; it G) then go to M ВМ С2 R —; (o:=r > N ПЕ — М —; if co then go to M П Т L\ l:= true РА •мм — —; F:=O(Z:=O) НАЧ: П И() — B; 6:=a[Z, j] (/ = 0, 1, 4) РА 1() 0 /; Z:= i+ 1; F:=0 (/:=0) П сз — Q; порядок q ——32 Р: СДСП Q В R> 1 r. Ь f/1 И R С4 /?;/ r— 6[/J Н R С4 —; <o:=r = Q ПЕ - кон —; if © then go to KOH ВМ С2 R —; (»:= r > N ПЕ М —; if о then go to M СПА 8 Q Q; q:=q-\-8 ПМ 4 Р 1( ); if / < 4 then begin /:= /+1; go to P end PC — Z —; F:—i ПБ — НАЧ -; go to НАЧ М: П L\ Z:= false КОН: Заметим, что поскольку операции перехода совмещены с пересылкой, то команду с меткой М можно сэкономить, заменив каждую из команд ПЕ М командой ПЕ — КОН L;
326 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. $ 5.8. Подпрограммы 5.8.1: Понятие и назначение подпрограммы. ' При решении самых различных задач довольно часто возникает необходимость в разных местах общего алгоритма решения данной задачи использовать один и тот же частный алгоритм, имеющий не- которое самостоятельное значение. Например, в разных местах об- щего алгоритма может использоваться одна и та же функциональная зависимость, определяемая соответствующим частным алгоритмом. При составлении машинной программы в этом случае в разных ее местах приходится несколько раз выписывать аналогичную после- довательность команд, задающую на языке машины этот частный алгоритм. Так, при вычислении по формуле • c=(a+b)k+bm, где^ а, Ъ и с вещественные переменные, a k и т — целочисленные переменные, принимающие неотрицательные значения, дважды используется функциональная зависимость у=хп, которая может быть на языке машины реализована следующей последовательно- стью команд (через ЕД обозначена единица); н N — —; со:=п=0 ПЕ ЕД кон У; у: = 1; if <о then go to КОН РА — 1 —; F: = l L: У Y X У; ПМС N L 1 (); if F<n then КОН: begin F:—FA-\ go to L end При составлении программы для заданных вычислений эту по- следовательность команд придется выписать дважды с незначитель- ными изменениями, причем можно так спланировать последователь- ность действий: x:=a+t; п:=&; с:=у; х\=Ь\ п:=т\ у:=х]п\ с:=с+у что эти изменения будут обусловлены исключительно различным расположением в памяти каждой из этих последовательностей команд, вычисляющих у=хп. Очевидно, что такое многократное включение в программу по су- ти дела одной и той же последовательности команд привело бы к увеличению длины программы, затрат времени и труда на ее состав- ление и вероятности допущения ошибок. Поэтому более рациональ- ной была бы такая организация программы, чтобы эта многократно используемая последовательность команд присутствовала только в одном месте программы, к которой и производилось бы обращение с целью ее выполнения каждый раз, когда это необходимо. Такая последовательность команд, которая выделена из основ- ной программы и может использоваться в разных ее местах, назы- вается подпрограммой, а переход к подпрограмме с заданием всей
5.8) ПОДПРОГРАММЫ W необходимой для ее выполнения информации называется'обращением к подпрограмме. В этом случае выполнение программы можно изобразить в виде следующей схемы, в которой стрелками показан порядок Выпол- нения отдельных ее частей, а у стрелок, связывающих основную программу с подпрограммой, указан порядковый номер обращения к подпрограмме (см. рис. 5.6). В алголе для обеспечения такой компактности записи алгоритма служит, в частности, аппарат процедур, так что в машинной про- грамме подпрограмма соответству* ет описанию процедуры, а обраще- ния к подпрограмме соответствуют указателям функции и операторам процедуры. Что же нужно сделать для того, чтобы приведенная выше по* следовательность команд, реа: лизующая функциональную зави- симость у=хп, могла быть исполь- зована в качестве подпрограммы? Заметим прежде всего, что по исноьная программа Рис. 5.6. окончании выполнения подпрог- раммы необходимо обеспечить возврат в основную программу, так что выполнение подпрограммы должно завершаться безусловным переходом к одной из команд основной программы. Но поскольку обращения к подпрограмме могут производиться из разных мест основной программы, то адрес, по которому надо осуществить воз- врат, не только неизвестен при составлении подпрограммы, но и ме- няется от одного обращения к другому. Следовательно, этот адрес как-то должен быть передан подпрограмме при обращении к ней. Далее возникает вопрос о том, на какие истинные адреса должны быть заменены символические адреса X, N и У, использованные в написанных выше командах, которые должны войти в подпрограм- му. Ведь на самом деле эти символические адреса не представляют каких-либо реальных объектов, а являются параметрами подпрограммы (аналогично формальным параметрам в описании процедуры на алголе), которые должны быть конкретизированы при каждом обращении к подпрограмме заданием соответствующих фак- тических параметров (аналогично заданию фактических параметров в указателе функции Или операторе процедуры в алголе). Итак, обращение к подпрограмме включает в себя: а) задание информации о ее фактических параметрах; б) задание информации, необходимой для обеспечения возврата в нужное место основной программы по окончании выполнения под- программы; в) переход на начало подпрограммы.
828 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ 1Гл. 8 В зависимости от того, как будут решены эти вопросы, т. е. в за- висимости от выбранного способа обращения, должен решаться и во- прос о том, как должна быть организована сама подпро- грамма с тем, чтобы были обеспечены и её настройка на работу о заданными фактическими параметрами, и возврат в нужное место основной программы. При решении всех этих вопросов необходимо обеспечить как удобство использования подпрограмм, так и доста- точно' высокую эффективность всей программы в целом. Проще всего решается вопрос о переходе к подпрограмме — для этого достаточно, зная её начало в памяти, записать в основной про- грамме команду перехода. Несколько сложнее обстоит дело с заданием информации о факти- ческих параметрах из-за разнообразия имеющихся здесь возможно- стей. Мы ограничимся рассмотрением простейших случаев, когда фак- тическими параметрами подпрограмм являются отдельные значения и простые переменные. В этих случаях подпрограмме передаются либо сами эти значения, либо адреса значений или простых пере- менных. 5.8.2. Передача подпрограмме значений. Рассмотрим сначала случай, когда основная программа и под- программа обмениваются только значениями, т. е. все фактические параметры вызываются значением. В этом случае алгоритм решения рассматриваемой нами задачи на алголе можно записать в виде сле- дующего блока: begin real procedure СТЕП (х, п); value х, п; real х; integer п; begin real г, integer i; r:=l; for r: = l step 1 until n do r:=rXx; СТЕП-.—г end; СЧЕТ-.с.—СТЕП (a+b,k)+CTEn (b, m) end Здесь символическим адресам X и АГ, которые являются парамет- рами подпрограммы, надо поставить в соответствие определенные ячейки памяти, в которые и будут помещаться конкретные значения аргументов при каждом обращении к подпрограмме. Что касается получаемого значения функции, то его тоже надо поместить в опре- деленной ячейке памяти, известной основной программе. Для удобства обращений к подпрограммам целесообразно для этих целей выбрать фиксированные ячейки, чтобы их адреса вообще не зависели от конкретного распределения памяти. Мы условимся для задания аргументов использовать ячейки па- мяти с последовательными номерами, начиная с 0002, а вычислен- ное значение функции всегда оставлять на месте первого аргумента, т. е. в ячейке 0002.
6.8] ПОДПРОГРАММЫ 329 Заметим, что если на машине имеется стек, то удобно при обра- щении к подпрограмме значения аргументов засылать в стек, а полу- чаемое подпрограммой значение функции оставлять в вершине стека, использовав при этом (и тем самым исключив из стека) заданные зна- чения аргументов. Возврат из подпрограммы также можно обеспечить по-разному. Можно, например, при обращении к подпрограмме передавать ей через индексный регистр тот адрес L в основной программе, по ко- торому надо осуществить возврат в основную программу; для этого перед переходом на подпрограмму достаточно выполнить команду РА— L—; и если по окончании выполнения подпрограммы этот адрес L на- ходится в индексном регистре, то в качестве последней команды под- программы надо выполнить команду /75-0 0- На некоторых машинах для этих целей предусматривается специ- альная машинная операция. В УМИР-3 такой операцией является безусловный переход с возвратом: ПВ (16). При выполнении коман- ды вида л 16 Л1 А2 АЗ в счетчик команд С заносится адрес А2ИСП (что означает безуслов- ный переход по этому адресу), а по адресу АЗИСП записывается слово вида 016 0000 А1ИСП 0000 представляющее собой команду безусловного перехода по А1ИСП, заданному в выполняемой команде перехода с возвратом. Таким образом, с помощью одной такой команды можно осуществить пере- ход к подпрограмме и сформировать ту команду, выполнением ко- торой должно завершиться её выполнение. Вообще говоря, в коман- де перехода к подпрограмме в качестве АЗИСП можно было бы за- дать адрес последней команды в подпрограмме, но тогда этот адрео зависел бы от размещения подпрограммы в памяти. Поэтому, для упрощения обращения к подпрограммам, мы условимся формируе- мую команду возврата всегда оставлять в фиксированной ячейке 0001. Теперь в качестве иллюстрации рассмотрим реализацию приве- денного выше блока, задающего вычисление с=(а+6)*+6“. Составим сначала основную программу, реализующую оператор о меткой СЧЕТ — в предположении, что подпрограмма уже состав? лена и её первая команда помечена меткой СТЕП. При выбранном нами способе обращения к подпрограммам первое обращение к подпрограмме СТЕП для вычисления (а+d)* будет иметь вид: СЧЕТ'. С А В R2; г2:=а+Ь ПК — R3; r3:=k £1: ПВ £1+1 СТЕП /?1; rl: ПВ —£1+1 —
330 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 Далее мы исходим из того, Что подпрограмма должна в ячейке R2 получить значение (r2) f (гЗ), т. е. значение (а+Ь)*, и осуществить переход (используя информацию, заданную в ячейке 7?1) по адресу L14-1 (т. е. к команде основной программы, следующей за командой L1). Поэтому вслед за командой L1 запишем команды для вычисле- ния Ьт, опять обращаясь к подпрограмме.СТЕП, но поскольку при этом обращении опять будет использоваться ячейка /?2, то предва- рительно надо использовать результат, полученный в этой ячейке при первом обращении к подпрограмме: П R2 — С-г П В — R2-, г2:=Ь П М — /?3; гЗ:—т L2: ПВ L2+1 СТЕП /?1; Здесь мы находимся в аналогичной ситуации: по окончании вы- полнения подпрограммы в ячейке R2 будет получено значение Ьт и будет осуществлен переход к команде основной программы, сле- дующей за командой L2, так что теперь закончим основную про- грамму командой С С R2 С; При составлении подпрограммы следует исходить из того, что к началу её выполнения в ячейке R2 (0002) находится значение осно- вания степени х, в ячейке R3 (0003) — значение показателя степени п (в виде условно-целого числа), а в ячейке /?1 (0001) — слово вида ПВ — L — Подпрограмма должна в ячейке R2 получить значение хп и осущест- вить переход по адресу L, заданному в ячейке /?1. С учетом сказанного можно составить такую подпрограмму: СТЕП: н R3 — <о:=п=0 ПЕ ЕД КОН R+ г4: = 1 РА — 1 F:=l ЦИКЛ: У R4 R2 R+ г4:=г4Хг2 ПМС R3 ЦИКЛ 1 0; КОН: П /?4 — R2-, PC — —• F:=L ВЫХ: ПВ - 0() Возврат ЕД: КОНСТ 1.0 Теперь остается решить вопрос о том, где поместить подпрограм- му как часть программы. Этот вопрос не является принципиальным и может решаться автором программы по-разному. Вообще говоря, для устранения лишних команд безусловного перехода все подпро- граммы целесообразно размещать в конце программы, после команды останова. Можно, наоборот, разместить их в начале про- граммы, записав перед ними в качестве первой команды основной части программы команду безусловного перехода на её продолже- ние, размещенное вслед за всеми подпрограммами. Поскольку мы
6.8] ПОДПРОГРАММЫ 331 рассматриваем не цельные программы, а их фрагменты, то нам более удобен второй способ. В этом случае программа будет иметь вид: ПБ - СЧЕТ > Обход подпрограммы СТЕП-. Н R3 — ПЕ ЕД КОН R4-, РА — 1 — • цикл-. У R2 R4 ; ПМС R3 цикл Н ); Подпрограмма КОН: П R4 - R2-, PC — R1 —— • вых-. ПБ - 0( ) — • ЕД: конст 1.0 • СЧЕТ-. С А В Я2;) обращение к подпрог- П К - ЯЗ; . рамме для вычисления LA: ПБ L1 +1 СТЕП Rl; J (а + Ь)к П R2 - С; с = (а-Ь-Ь)к П В — Я2;) обращение к подпрог- П М - R3; рамме для вычисления L2: ПВ L2-J-1 СТЕП Rl; J bm С С - R2 с-, c = (a+b)k+bm 5.8.3. Передача подпрограмме адресов. Рассмотрим теперь другой крайний случай, когда вся информа* ция о фактических параметрах задается подпрограмме в виде адресов (значений фактических параметров или переменных, являющихся фактическими параметрами). Алгоритм решения рассматриваемой нами задачи запишем в виде следующего блока алгола: begin procedure СТ (х, п, у); value х, n; real х, у; integer п; begin integer i; t/: = 1.0; for i: = l step 1 until n do y.=y~Xx end; real u, v; СЧЕТ-. u-.=a-\-b\ CT(u, k, v)\ CT (b, m, и); c:—v+u end Теперь при каждом обращении к подпрограмме будем каким- либо образом передавать ей адреса значений фактических парамет- ров, соответствующих формальным параметрам х и п, а также адрес простой переменной, поставленной в соответствие формальному параметру у. Подпрограмма должна быть организована так, чтобы в процессе своего выполнения она сама обеспечивала «настройку» для работы с заданными ей адресами. Передача подпрограмме адресов также может быть реализована по-разному. Можно, например, перед переходом к подпрограмме помещать эти адреса в определенном, заранее известном месте па- мяти (в стеке, в фиксированных ячейках и т. д.). Такой способ уп- рощает доступ к задаваемым адресам и их использование подпро-
332 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. S граммой, но менее удобен при обращениях к ней, так как требует ряда дополнительных команд пересылок и, возможно, ряда констант. Другой способ состоит в том, чтобы задавать эти адреса в произволь- ном месте памяти, в достаточно удобной и компактной форме, а при обращении к подпрограмме передавать ей информацию о том, где заданы эти адреса. Мы остановимся на втором способе и условимся задавать эти адреса в строках основной программы, непосредственно следующих за командой перехода (с возвратом) к подпрограмме. Формат каж- дой из этих строк примем таким же, как формат команды, и адреса фактических параметров — в порядке их следования — будем ука- зывать в последовательных адресных полях первой, затем второй и т. д. строк. Если, например, некоторая подпрограмма РР имеет пять пара- метров, то обращение к ней будет иметь вид £: ПВ L+l РР R\ — adp.ai адр.а3 адр.а* —. адр.а.4 адр.аъ — где адр. ак—адрес, соответствующий очередному фактическому параметру (значению или простой переменной). Этот способ позволяет задать нужные адреса в весьма компактной форме и не требует передачи подпрограмме дополнительной инфор- мации о том, в каком месте памяти заданы эти адреса: поскольку при переходе к подпрограмме в ячейке фиксируется адрес L+1, то он как раз и определяет то место, где заданы передаваемые подпрограм- ме адреса — они заданы в последовательных ячейках памяти, на- чиная с ячейки L+1. В связи с тем, что число параметров у каждой подпрограммы фиксировано, то ей известно и число I строк, ис- пользуемых в основной программе для задания соответствующих адресов. Поэтому, используя заданный адрес L-H, подпрограмма может определить и адрес L-H+1, по которому нужно обеспечить возврат в основную программу. При таком способе обращения к подпрограмме основная про- грамма рассматриваемых нами вычислений будет иметь несколько иной вид. Перед первым обращением к подпрограмме вычислим значение «=о+6: С А В U\ Далее запишем обращение к подпрограмме для вычисления v—uk: £1: ПВ £14-1 СТ RI; — U К V; Теперь мы исходим из того, что подпрограмма получит значение ц=и* и обеспечит возврат к команде £14-2 основной программы; на- чиная с этой команды, запишем второе обращение к подпрограмме:
ПОДПРОГРАММЫ 333 5.8J Л ПВ L1+3 СТ — В М i/;\ В результате подпрограмма получит значение и=Ьа и вернется к команде L14-4, которой завершим наши вычисления: С V U С; При составлении подпрограммы в данном случае следует исхо- дить из того, что к началу ее выполнения в ячейке R1 находится слово (ПВ — L —) и что по адресу L, заданному в ячейке R1, хранится слово (— X N Y), где X, N и У — адреса фактиче- ских параметров, соответствующих формальным .параметрам х, п и у. Подпрограмма должна значение основания степени выбрать из ячейки с адресом X, значение показателя степени — из ячейки о адресом N, а вычисленное значение степени запомнить в ячейке о адресом Y, после чего обеспечить переход к команде с адресом L+1, При составлении подпрограммы можно поступить следующим об- разом. Поскольку адреса, передаваемые подпрограмме при разных обращениях к ней, задаются в разных строках основной программы, то для упрощения использования подпрограммой этих адресов их целесообразно сначала переслать в фиксированную ячейку, напри- мер в R2, используя заданный при обращении адрес L: СТ: PC - R\ F:=L П 0( ) — R2; г2: ООО X N Y Далее, используя полученные в ячейке R2 адреса X и W аргументов, можно значения этих аргументов также переслать в фиксированные ячейки памяти, например в ячейки R3 и R4 соответственно: PC — R2 ••• * F:=N п 0() — R4; г4:= п СДСА -12 R2 R3; гЗ: ООО 0000 X N PC — R3 » F:—X П 0() — R3: гЗ:— х Теперь по значениям х и п, находящимся в ячейках R3 и R4, вы- числим х", поместив результат в фиксированную ячейку R5: Н R4 — — • СО ; = п =0 ПЕ ЕД КОН R5-, г5:= 1 РА — 1 ЦИКЛ: У R5 R3 R5; г5 := г5хх ПМС R4 ЦИКЛ 1 ( ); Далее полученный результат из ячейки R5 перешлем по за- данному при обращении адресу Y (который зафиксирован в ячейке R2 в поле третьего адреса): КОН: СДМА 12 R2 R2-, г2: л0 N Y 0000 PC - R2 —; F :=Y П R5 — 0( ); «/:=г5
334 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл 5 Выполнение требуемых вычислений закончено, теперь нужно обес- печить возврат в основную программу по адресу L +1 (адрес L задан в. ячейке R1). Кроме того, надо запасти константу—число 1: PC — Rl F:—L ПБ —1( ) —; Переход по адресу £ + 1 ЕД: КОНСТ 1.0 Константа 1.0 В целом программа рассматриваемого примера будет иметь вид: ПБ - СЧЕТ » Обход подпрограммы СТ: PC — R1 — • П 0( ) — R2; PC R2 — • П 0( ) — R4; СДСА — 12 R2 R3; ♦ PC — R3 — • П 0() — R3; Н R4 — — * ПЕ РА ЕД кон 1 R5; • Подпрограмма цикл-. У R5 R3 R5; ПМС R4 ЦИКЛ 1( ); КОН: СДМА 12 R2 R2; PC — R2 .—' П R5 — 0( ); PC — R1 ПБ — К ) » ЕД: КОНСТ 1.0 СЧЕТ: С А В V ; i и : = а + b £1: ПВ £1 + 1 ст Rl; Обращение к под- программе; — и к V; 1 у: = u f k £2: ПВ £2+1 ст /?1; Обращение к под- программе; , — В м и ; i u: = b\tn с V и С ; < с: — u-j-v Как видно, в данном случае обращение к подпрограмме упрости- лось за счет ликвидации ряда команд пересылок, и основная часть программы стала более компактной и наглядной. Однако эти удоб- ства использования подпрограммы достигнуты за счет усложнения последней, поскольку теперь на подпрограмму — по сравнению с предыдущим вариантом — возложены дополнительные функции по её настройке на работу с задаваемыми при обращении адресами фак- тических параметров. Для выполнёния этих функций в подпрограм- ме пришлось предусмотреть дополнительные группы команд (фор- мирующие части).
5.8) X подпрограммы 335 В общем случае при знании информации о фактических пара- метрах рассмотренные выше способы могут комбинироваться; одни фактические параметры могут задаваться путем передачи под- программе их значений, другие v путем передачи их адресов. Вы- бор того или иного способа определяется и назначением подпро- граммы, и характером параметров; хи соображениями удобства'и эффективности использования подпрограммы. При составлении подпрограмм следует учитывать то обстоятель- ство, что обращение к ним может производиться из какого-либо цикла, для организации которого используется индексный регистр. Поэтому, чтобы обращение к подпрограмме не нарушало органи- зацию цикла, необходимо позаботиться о том, чтобы содержимое индексного регистра не изменилось в результате обращения к под- программе. Конечно, задачу сохранения содержимого индексного регистра можно возложить на основную программу*. В этом случае перед каждым обращением к подпрограмме, использующей индекс- ный регистр для своих внутренних целей, в основной программе можно запоминать текущее содержимое этого регистра в одной из рабочих ячеек, например путем выполнения команды РА 0( ) 0( ) R а при возврате из подпрограммы восстанавливать его содержимое с помощью команды PC — R — Однако для упрощения использования подпрограмм задачу сохра- нения содержимого индексного регистра целесообразно возложить на каждую из подпрограмм. Если какая-либо подпрограмма ис- пользует этот регистр для своих целей, то она должна предваритель- но запомнить исходное содержимое регистра, а перед возвратом в основную программу — восстановить это содержимое. Например, подпрограмму СТЕП, сохраняющую содержимое ре- гистра F, можно составить следующим образом: СТЕП'. П R\ — вых-, Формирование воз- врата Н R3 — — ' ПЕ ЕД кон РА 0() 1 R ; r:=F; F;=l ЦИКЛ: У R2 «1 ; ПМС R3 ЦИКЛ 1(); F: —г (восстановле- PC — R — » ние F) КОН: п — Д2; ВЫХ: п — — : Возврат ЕД: конст 1.0
336 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 5 Здесь /? — рабочая ячейка, в которой/запоминается исходное со- держимое F. В этой подпрограмме пришлось использовать перемен- ную команду (с меткой ВЫХ.)', наличие только одного индексного регистра не позволяет решить этузадачу иным способом, ибо по- следняя команда — команда возврата — тоже содержит перемен- ный адрес, для управления крторым требуется использование ин- дексного регистра. / / 5.9. Стандартные подпрограммы К понятию подпрограмма мы пришли в связи с тем, что довольно часто в разных местах общего алгоритма решения той или иной за- дачи приходится использовать один и тот же частный алгоритм, на- пример вычисление одной и той же функции, но при разных значе- ниях ее аргумента. Однако весьма типичной является и такая ситуация, когда один и тег же частный алгоритм используется при решении самых разных задач, например переводы чисел из одной системы счисления в дру- гую, вычисление элементарных функций и т. д. И если какой-либо из таких часто используемых алгоритмов однажды был реализован в виде подпрограммы, то возникает естественное желание исполь- зовать в дальнейшем эту подпрограмму как готовую часть любой другой программы, в которой встречается этот алгоритм, а не из- готовлять ее заново. Если организовать сбор и хранение изготов- ленных подпрограмм, то можно существенно упростить и ускорить составление других программ, в которых могут быть использованы уже имеющиеся подпрограммы. Если накопленный набор подпро- грамм достаточно богат, то составление каждой новой программы может в значительной мере свестись к записи обращений к имею- щимся подпрограммам, а заново придется программировать лишь недостающие части. Однако ясно, что реальная экономия труда и времени программиста будет зависеть не только от количества имеющихся подпрограмм, но и от простоты и удобства их использо- вания. Между тем при использовании готовых подпрограмм возникает ряд проблем, связанных с хранением имеющихся подпрограмм, размещенйем используемых подпрограмм в памяти машины, их вво- дом в память и организацией их взаимодействия с основной про- граммой. Поэтому для обеспечения большего удобства практической работы выбирается определенная система использования под- программ, в которой тем или иным способом решаются все возни- кающие здесь проблемы. Эта система предъявляет определенные требования к подпрограммам с точки зрения их организации и оформления. Подпрограммы, которые удовлетворяют всем требова- ниям выбранной системы, называются стандартными подпрограм-
5.9J СТАНДАРТНЫЕ ПОДПРОГРАММЫ 337 мами (СП), а имеющийся №Mfop СП, организованный соответствую- щим образом, называется библиотекой СП. Наибольший эффект достигался бы при такой системе, когда ис- пользование любой библиотечной подпрограммы при составлении какой-либо новой программы матчем отличалось от использова- ния обычной машинной операции. В этом случае наличие библио- течных подпрограмм — с точки зрения программиста — было бы равносильно расширению набора машинных операций, несмотря на то, что такое расширение реализовало не за счет усложнения аппаратуры машины, а программным путей. В этом отношении можно пойти и еще дальше: с помощью под- программ можно не только расширить существующий, но и создать любой новый набор достаточно содержательных операций, включая и желаемую форму представления чисел. Например, на машине, непосредственно оперирующей только над числами с фиксирован- ной точкой, программным путем можно ввести операции над чис- лами с плавающей точкой — для этого достаточно выбрать опреде- ленный способ представления таких чисел в виде машинных слов и составить соответствующие подпрограммы, с помощью которых ре- ализуются арифметические операции над числами, представленны- ми этим способом. Основная трудность в использовании готовых подпрограмм сос- тоит в том, что одну и ту же подпрограмму при ее использовании в разных программах приходится помещать в разные места памяти, тогда как любая машинная программа будет правильно выполнять свои функции только при том распределении памяти, в расчете на которое она была составлена. В связи с этим любое изменение в рас- пределении памяти, вообще говоря, влечет за собой необходимость изменения и текста машинной программы, т. е. ее переработки; Пе- реработку программы, связанную с изменением места в памяти самой программы, называют ее настройкой по месту. В качестве иллюстрации рассмотрим подпрограмму, реализую- щую процедуру алгола procedure М(х, у)\ value х; real х, у, z/:=if х>0 then (х+2) f 2/8 else х f 4—2 Обращение к этой подпрограмме будем производить рассмотренным ранее способом: сначала значение первого фактического параметра (вызываемого значением) помещается в фиксированную ячейку 0002, а затем записывается пара строк вида ПВ L НАЧМ 0001 L: 000 0000 Y 0000 где Y — адрес второго фактического параметра (вызываемого по имени). При составлении подпрограммы не будем обеспечивать сох-
338 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. & ранение исходного содержимого индексного регистра, и необходи- мые рабочие ячейки запасем в самой Подпрограмме. Тогда подпро- грамма, записанная на языке машиш/в расчете на то, что она будет размещена в памяти с ячейки ОЮОУможет быть следующей: НАЧМ: 0100 002 0000 0002 0000 <»>: — х > 0 0101 076 0000 /0106 0000 Переход по ю = false 0102 001 0002/ 0116 0117 г = х + 2 0103 005 0П7 0117 0117 г = (х+2)а 0104 046 003 0117 0117 ВПА-. г: = г/8 0105 056,0000 0111 0000 Безусловный переход 0106 005 0002 0002 0117 г =х2 0107 005 0117 0117 0117 г = х* ОНО 002 0117 0116 0117 г = х4—2 0111 072 0000 0001 0000 F: = L 0112 272 0000 0000 0000 F: = Y 0113 100 0117 0000 0000 г 0114 072 0000 0001 0000 F:= L 0115 256 0000 0001 0000 Возврат к L -f-1 0116 002 4000 0000 0000 Число 2 0117 000 0000 0000 0000 Рабочая ячейка Все адреса, фигурирующие в этой подпрограмме, делятся на два класса: а) Абсолютные адреса, которые вообще не зависят от распре- деления памяти: это адреса стандартных рабочих ячеек (0001, 0002); адрес особой ячейки 0000; первый адрес (0003) в команде вычитания из порядка адреса (в команде с адресом 0104), поскольку он явля- ется непосредственным операндом, а не адресом ячейки памяти; адреса, которые представляют собой добавку к содержимому ин- дексного регистра, для получения исполнительных адресов (на- пример, второй адрес в команде возврата); адрес 4000 в записи кон- станты. б) Внутренние адреса, с помощью которых производится ссылка на внутренние для данной подпрограммы объекты: ее команды, за- пасенные в ней константы и рабочие ячейки. Ясно, что внутренние адреса должны изменяться при перемещении данной подпрограммы в памяти. Если строки подпрограммы перенумеровать, начиная с нуля, то каждый внутренний адрес sz- имеет вид Sj=HA4+i, где НАЧ — начало данной подпрограммы в памяти, a i — относитель- ный номер строки в данной подпрограмме, на которую ссылается этот адрес, так что изменение значения НАЧ влечет за собой и из- менение каждого внутреннего адреса. Так> если нашу подпрограмму разместить в памяти с ячейки 0220, то она должна принять следующий вид:
5.91 СТАНДАРТНЫЕ ПОДПРОГРАММЫ 339 НАЧМ: 0220 002 0000 0002 0000 и: = х > 0 * % 0221 076 0000\0226 0000 Переход по cos false 0222 001 0002 0236 0237 г=х-Ь2 0223 005 0237 0237 0237 г = (х + 2)2 0224 046 0003 0237 0237 ВПА-. г. = г/8 0225 056 0000 02314000 Безусловный переход 0226 005 0002 0002 0237 г — хг 0227 005 0237 0237 0237 г = х* 0230 002 0237 0236 0237' г —х* — 2 0231 072 0000 0001 0000 F:—L 0232 272 0000 0000 0000 F'.= Y 0233 100 0237 0000 0000 у.-=г 0234 072 0000 0001 0000 F:=L 0235 256 0000 0001 0000 Возврат к L+ 1 0236 002 4000 0000 0000 Число 2 0237 000 0000 0000 0000 Рабочая ячейка Как видно, настройка подпрограммы по.ее месту производится по формальным правилам и поэтому может быть поручена самой машине с помощью специальной обслуживающей программы. Для этого надо так оформить каждую библиотечную подпрограмму, что- бы было ясно, какие из используемых в ней адресов являются аб- солютными, а какие — внутренними, и чтобы переработка внутрен- них адресов в истинные производилась достаточно просто. В качестве иллюстрации одного из возможных способов решения всех указанных выше проблем рассмотрим реальную систему ис- пользования стандартных подпрограмм. 5.9.1. Интерпретирующая система ИС-2. Эта система использования стандартных подпрограмм была раз- работана применительно к машине М-20 и нашла широкое приме- нение (с некоторыми ее модификациями) на всех машинах типа М-20 (М-20, БЭСМ-4, БЭСМ-4М, М-220). Эта система допускает исполь- зование в стандартных подпрограммах абсолютных и внутренних адресов. Требования к СП. Если строки СП считать перенумерованными от 0 до n—1, где п — длина СП, то для ссылки на строку с номером i должен использоваться адрес вида s,=2000+i. Другими словами, каждая СП должна составляться в предположении, что она будет размещена в памяти, начиная с ячейки 2080. Таким образом, все внутренние адреса s{, используемые для ссы- лок на строки данной СП, удовлетворяют условию 2000<s,.<2000+n. Все остальные адреса считаются абсолютными. Однако из этого правила различения абсолютных и внутренних адресов необходимо сделать некоторые исключения. Дело в том, что
340 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ (Гл. 6 СП может содержать в себе некоторое ч^ело «абсолютных» констант, например чисел. Машинные слова, /представляющие такие кон- станты, естественно, не зависят от распределения памяти и поэтому не должны подвергаться какой-либо переработке в процессе «на- стройки» данной СП на отведенное ей место в памяти. Однако адреса в таких словах (рассматриваемых по формату команды) тоже могут удовлетворять тому же сформулированному выше условию, что и внутренние адреса. В связи с этим в системе ИС-2 принято согла- шение о том, что все такие «инвариантные» (т. е. не подлежащие изме- нению) строки СП должны быть собраны в одну группу, а этой груп- пе должна непосредственно предшествовать специальная строка вида ООО 0000 I 0000 являющаяся признаком того, что далее следует группа из I инвари- антных строк. Перерабатывающая программа (будем называть ее сокращенно ИС) выявляет в перерабатываемой СП первую по по- рядку строку такого вида, и если она есть, то оставляет без изме- нения следующие за ней / строк. Кроме того, в качестве внутренних не должны рассматриваться адреса, которые являются непосред- ственными операндами (например, первый адрес в командах сдвига по адресу и т. д.). На каждой машине все такие операции, в которых адрес используется в качестве непосредственного операнда, извест- ны, так что ИС в своей работе учитывает код операции в очередной перерабатываемой команде. Итак, теперь четко сформулировано правило различения абсо- лютных и внутренних адресов. Кроме того, каждый внутренний адрес sz содержит в себе и часть информации, необходимой для его переработки в истинный: если внутренний адрес имеет вид sf=20004- -Н‘, а на самом деле СП размещена в памяти с ячейки ан, то он дол- жен быть заменен истинным адресом, равным значению aH4~i. Та- ким образом, для настройки СП по ее месту в памяти достаточно к каждому внутреннему адресу прибавить одну и ту же поправку Д=а„—2000. Организация библиотеки. В системе ИС-2 все включенные в библиотеку СП постоянно хранятся во внешней памяти машины. Каждой СП присвоен определенный номер (четырехзначное вось- меричное число), который играет роль имени данной СП. Подпро- граммы объединены в библиотеку с, помощью каталога, называе- мого постоянной таблицей характеристик (ПТХ): в этой таблице для каждой СП отведена строка (номер которой равен номеру СП), в которой содержится информация о длине СП и ее месте во внешней памяти. ПТХ находится в фиксированном месте памяти, известном программе ИС. Размещение СП. Во время выполнения основной программы об- служивающая программа ИС постоянно находится в оперативной памяти. Для этих целей зарезервированы ячейки 7500 — ТТЛ,
5.9] ;ТАНДАРТНЫЕ ПОДПРОГРАММЫ 341 которые не должны использоваться в основной программе для иных целей. Для размещения используемых СП отводится массив ячеек, который называется рабочим полем, непосредственно примыкаю- щий к массиву памяти, отведенному для ИС. В качестве стандарт- ного начала рабочего поля принята ячейка 7200, однако програм- мист имеет возможность задать другое начало рабочего поля. Опре- деление места на рабочем поле для используемых СП осуществляет сама ИС. Обращение к СП. Для использования какой-либо СП в основной программе на самом деле записывается обращение к ИС, в котором — наряду с адресами, передаваемыми подпрограмме — указывается и номер требуемой СП. Например, для использования СП, пред- назначенной для вычисления y=f(x), это обращение имеет вид х: ПВ х + 1 7501 7610 X Ncn Y где 7501 и 7610 — фиксированные адреса, принадлежащие ИС, X и Y — адрес аргумента и результата соответственно, a Ncn — номер требуемой СП, который всегда задается в поле второго адреса стро- ки, следующей за командой перехода с возвратом к ИС. Напомним, что выполнение команды х состоит в записи в ячейку 7610 слова ПВ 0000 х+1 0000 и безусловного перехода 'по адресу 7501, с которого и начинается программа ИС. В процессе своего выполнения ИС производит чте- ние СП с заданным номером Ncn из библиотеки на свободное место рабочего поля, ее настройку применительно к этому месту, и осу- ществляет переход на начало этой СП. Схема работы ИС. В общих чертах работа ИС происходит сле- дующим образом. По номеру Ncn, заданному при обращении, в ка- талоге отыскивается строка, относящаяся к данной СП. Используя содержащуюся в ней информацию о длине СП и ее месте во внешней памяти, ИС прочитывает данную СП в начало рабочего поля и про- изводит ее настройку на это место по сформулированным выше пра- вилам, после чего осуществляет переход на начало СП. Каждая СП составляется исходя из того, что в ячейке 7610 со- держится адрес х+1 ячейки, в которой указаны адреса фактиче- ских параметров. Формирующая часть СП производит ее настрой- 'ку для работы с этими фактическими параметрами, после чего вы- полняется основная часть СП и осуществляется возврат в основную программу, для чего используется адрес х+1, заданный при обра- щении в ячейке 7610. По окончании выполнения данной СП занимаемое ею место на ра- бочем поле считается свободным, так что следующая используемая СП вызывается на то же самое место рабочего поля. Такой метод ис- пользования СП называется интерпретацией: ИС с помощью соот- ветствующей СП интерпретирует (т. е. истолковывает и выполняет)
342 ОСНОВНЫЕ ПРИЕМЫ ПРОГРАММИРОВАНИЯ [Гл. § задание на выполнение того действия, которое сформулировано в основной программе в виде очередного обращений к ИС. Этот метод весьма эффективен с точки зрения экономного расходования памя- ти для размещения используемых СПгпоскольку каждая из них раз- мещается на одном и том же месте/то независимо от числа исполь- зуемых подпрограмм длина рабо^го поля должна быть достаточной лишь для размещения одной, самой длинной из используемых в дан- ной программе СП. Недостаток этого метода состоит в том, что вы- зов СП на рабочее поле и ее настройка по месту производятся зано- во при каждом очередном обращении к данной СП, что приводит к нерациональному расходованию машинного времени. В связи с этим в ИС приняты определенные меры для экономии машинного времени, затрачиваемого для этих целей, причем эти меры предпринимаются по двум направлениям. Во-первых, если последовательные по времени обращения про- исходят к одной и той же СП, то нет необходимости вызывать ее на рабочее поле каждый раз заново — достаточно это сделать при “первом из этих обращений. Во-вторых, если на рабочем поле имеется достаточно свободного места для размещения очередной требуемой СП, то можно сохра- нить на рабочем поле вызванные ранее подпрограммы. В том част- ном случае, когда все требуемые СП могут одновременно разме- ститься на рабочем поле, каждая из них будет вызываться из биб- лиотеки и настраиваться по месту только один раз. Для достижения этих целей ИС в процессе своей работы ведет учет вызванных на рабочее поле СП, и если при очередном обраще- нии требуемая СП уже находится на рабочем поле, то вызова этой подпрограммы из библиотеки не производится. Кроме того, ИС ве- дет учет свободного места на рабочем поле, и если этого места до- статочно для размещения очередной вызываемой СП, то все ранее вызванные СП сохраняются на рабочем поле. Если же свободного места оказывается недостаточно для вызова очередной СП, то все ранее вызванные подпрограммы «стираются» (т. е. все рабочее поле считается свободным) и очередная' СП размещается с начала рабо- чего поля. При этом ИС все время обеспечивает правильное взаимо- действие между основной программой и вызванными на рабочее поле подпрограммами. Дополнительные возможности ИС. Помимо своего прямого на- значения по вызову на рабочее поле требуемых СП и обеспечению их правильного взаимодействия с основной программой, ИС обеспечи- вает и некоторые дополнительные удобства программистам. Так, почти в каждой СП присутствуют вспомогательные коман- ды, предназначенные для тогб, чтобы по информации, заданной в результате выполнения первой команды обращения (ПВ х+1 7501 7610), извлечь адреса фактических параметров, заданные в после- дующих строках основной программы, для настройки СП на эти
5.9] СТАНДАРТНЫЕ ПОДПРОГРАММЫ г , 343 фактические параметры. Поскольку эти команды во всех СП прак- тически одни и те же, то они включены в состав ИС в виде некоторых ее «блоков». Поэтому при составлении каждой новой СП можно не включать в нее эту группу команд, а просто обратиться к соответ- ствующему блоку ИС, что позволяет составлять более компактные подпрограммы. Кроме того, в состав ИС включено достаточно мно- го наиболее часто используемых констант. Поскольку эти «стан- дартные» константы как составная часть ИС постоянно находятся в оперативной памяти, то нет необходимости запасать их в каждой программе. В ИС предусмотрены и некоторые другие возможности, удобные для программистов. Как видно на примере ИС-2, использование любой библиотеч- ной подпрограммы в этой системе действительно мало чем отлича- ется от использования обычных машинных операций—для этого в основной программе достаточно написать обращение к нужной под- программе по некоторому стандартному формату. По сути дела раз- ница заключается лишь в том, что если для использования обычной машинной операции в программе надо записать команду, представ- ляемую обычно одним машинным словом, то для использования бо- лее содержательной операции, реализуемой с помощью соответст- вующей СП, в программе надо записать «крупную» команду, пред- ставляемую двумя или более машинными словами. Поэтому с точки зрения программиста можно считать, что в его распоряжении имеется не реальная машина М-20, а некоторая вир- туальная (воображаемая) машина, которая в своем наборе опе- раций — наряду с машинными операциями М-20 — имеет и неко- торое число (определяемое числом включенных в библиотеку СП): значительно более содержательных операций, и что на этой другой машине имеется несколько разных форматов команд, в том числе и команд, представляемых двумя или более машинными словами. Впрочем, последнее обстоятельство не является необычным даже для машины М-20, поскольку для некоторых ее операций (например, операций обмена между оперативной и внешней памятью) команда записывается в виде пары машинных слов. Эти дополнительные возможности, предоставляемые програм- мисту, созданы не за счет дополнительной аппаратуры в машине, а за счет использования интерпретирующей системы ИС-2. Здесь мы встречаемся с очень важным и принципиальным фак- сом: с помощью набора специальных программ, а точнее — комп- лекса специальных программ (поскольку они взаимодействуют друг с другом и потому должны быть согласованы между собой) можно существенно расширить возможности ЭВМ без изменения ее аппара- туры. В последующих главах на этих вопросах мы остановимся более подробно.
Глава б СИСТЕМЫ ПРОГРАММИРОВАНИЯ Практика работы на первых ЭВМ в начале 50-х годов выявила ряд серьезных трудностей в их использовании. Ведь для решения на ЭВМ любой задачи необходимо предварительно изготовить соот- ветствующую машинную программу, а изготовление таких про- грамм—это весьма непростая и кропотливая работа, трудность вы- полнения которой резко возрастает по мере роста сложности самих программ. В изготовлении программы можно выделить два основных эта- па, на каждом из которых возникали свои, специфические трудно- сти. Первый этап — это разработка алгоритма решения задачи и составление соответствующей программы. Мы не будем останавли- ваться на трудностях, связанных с разработкой алгоритмов, по- скольку их рассмотрение выходит за рамки того круга вопросов, которым посвящена данная глава. А что касается составления про- грамм, то здесь основные трудности проистекали из-за того, что единственными языками программирования в то время были ма- шинные языки конкретных ЭВМ. Машинный же язык, как мы зна- ем по предыдущим главам, весьма специфичен и неудобен для его использования человеком. Эти трудности усугублялись тем, что первые годы использова- ния ЭВМ были годами становления и самих ЭВМ. В тот период по- являлось довольно много различных машин, каждая из которых име- ла свой язык, отличный от языка других машин (с многообразием машинных языков мы познакомились в главе 4). В результате про- грамма, изготовленная для одной машины, не могла быть использо- вана на другой. Это обстоятельство не только значительно услож- няло практическую работу (например, перед изготовлением про- граммы надо было знать, на какой именно машине будет решаться данная задача), но и приводило к большому дублированию работы, ибо для одного и того же алгоритма на каждую машину приходилось ваново изготовлять соответствующую программу. Второй этап в изготовлении программы — это верификация составленной программы. Мы не случайно используем два разных термина: «составление» (т. е. написание) и «изготовление» програм- мы. Дело в том, что первоначально составленная (даже на машин-
Гл. 61 СИСТЕМЫ ПРОГРАММИРОВАНИЯ 345 х ном языке) программа очень редко оказывается пригодной для ре- шения той задачи, для которой она предназначалась — из-за того, что эта программа на самом деле оказалась либо ошибочной, либо недостаточно эффективной. В связи с этим каждая вновь составлен- ная программа должна пройти этап ее проверки на правильность и эффективность. В случае обнаружения ошибок и (или) выявления тех или иных недостатков в программу приходится вносить соот- ветствующие исправления и изменения. Этот этап изготовления про- граммы также весьма специфичен и трудоемок. В частности, такой метод проверки программы, как внимательный просмотр и анализ человеком каждой из написанных команд, на практике оказывается весьма неэффективным и поэтому для проверки программ прихо- дится применять специальные приемы. Внесение изменений в ма- шинную программу также связано с большими трудностями, о ко- торых мы говорили при переходе к составлению программ в символи- ческих обозначениях. Все это привело к тому, что на первых порах изготовлением про- грамм занимались лица, имевшие специальную подготовку и опыт работы на данной ЭВМ, которых называли «программистами». Про- граммисты являлись своего рода посредниками между «пользова- телями», т. е. лицами, желавшими решить на машине ту или иную свою задачу, и ЭВМ. По мере роста парка ЭВМ и повышения их быстродействия, а также расширения круга пользователей увели- чивалась потребность и в программистах. Очень скоро стало оче- видным, что никакой количественный рост программистов не смо- жет поспеть за ростом потребностей в них, и что требуется иное, научное решение этой проблемы. Кроме того, наличие промежуточ- ного звена между пользователем и ЭВМ в лице программиста вызы- вало и ряд других трудностей, тормозивших широкое внедрение ЭВМ в практику повседневной работы специалистов разных профи- лей. Так что единственный реальный выход из положения заклю- чался в том, что сам пользователь должен был стать и программис- том. Однако для этого нужно было так упростить работу по изго- товлению программ, чтобы ее могли самостоятельно выполнять сами пользователи. Проблема упрощения работы по изготовлению программ зани- мала умы программистов с самого начала появления этой категории специалистов, и к тому времени, когда эта проблема выросла до серьезных масштабов, программисты уже наметили и практически опробовали два основных пути ее решения. Первый путь — это обеспечение возможности многократного последующего использования результатов однажды проделанной ра- боты. Его суть состоит в том, чтобы однажды изготовленную про- грамму, предназначенную для решения какой-либо задачи, в даль- нейшем использовать в качестве готовой программы (или части дру- гой, более сложной программы) каждый раз, когда возникает необ-
846 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 6 ходимость решения аналогичной задачи, а не изготовлять эту про- грамму заново. Одним из примеров реализации такой возможности является рассмотренная в главе 5 система использования стандарт- ных подпрограмм ИС-2, л Второй путь — это возможно более полная автоматизация тех видов работ, которые приходится выполнять в процессе изготовле- ния программ, т. е. передача их выполнения самой машине. Этот путь базируется на универсальности ЭВМ: если какая-либо работа, возникающая по ходу изготовления программ, может быть настоль- ко формализована, что правила ее выполнения можно сформу- лировать в виде алгоритма, то можно составить соответствующую машинную программу, по которой ЭВМ и сможет выполнять эту работу. Одним из важнейших шагов в направлении упрощения изготов- ления программ явилось создание в конце 50-х годов проблемно-ори- ентированных алгоритмических языков типа алгол-60. Как мы видели, эти языки прежде всего рассчитаны на удобство их исполь- зования человеком для записи алгоритмов. Они практически не за- висят от особенностей конкретных ЭВМ и близки к обычной матема- тической символике, что обеспечивает простоту их изучения и ис- пользования широким кругом пользователей. Благодаря наличию таких языков пользователи получили удобное средство для форму- лирования своих алгоритмов. Эти языки позволили эффективно ре- шить и проблему широкого обмена разработанными алгоритмами, поскольку к каждому из таких языков предъявляется требование удобства его использования для публикаций. Однако сами по себе алгоритмические языки еще не решают всей проблемы, стоящей даже на первом этапе изготовления программы. Ведь программа, написанная на таком языке, не может быть не- посредственно выполнена какой-либо ЭВМ — для этого она долж- на быть предварительно переведена на язык данной машины (в пос- леднее время предпринимаются попытки приблизить уровень ма- шинного языка к уровню языков типа алгол-60, однако на этом пути имеется немало трудностей, поскольку повышение уровня машинного языка ведет к значительному усложнению аппарату- ры ЭВМ). Такой перевод, вообще говоря, представляет собой достаточно сложную задачу, и если бы его приходилось делать человеку, то эффективность использования алгоритмических языков была бы весьма невелика. На самом деле работу по переводу программ с ал- горитмических языков на языки машин удалось переложить на ЭВМ, используя их универсальность. Для этого к каждому алгорит- мическому языку предъявляется требование (которое должно быть выполнено, иначе язык окажется нежизнеспособным), чтобы он до- пускал формальный перевод на языки машин, т. е. чтобы правила такого перевода можно было сформулировать в виде алго-
Гл. 6] СИСТЕМЫ ПРОГРАММИРОВАНИЯ 347 ритма. Но если этот алгоритм однажды представить в виде маЩин- . ной программы, то затем с ее помощью сама машина может осуществ- лять перевод любой программу» записанный на д^ннрм алгоритми- ческом языкег на язык конкретной машины (не обязательно именно" той, на которой осуществляется перевод). Такая специальная про- грамма-переводчик называется "транслятором (здесь слово «транс- ляция» используется в смысле «перевод с одного языка на другой»). Таким образом, наличие алгоритмических языков и соответствую- щих трансляторов практически полностью решает проблему полу- . чения машинных программ пользователями, даже не знакомыми с языками машин. . Двигаясь по упомянутым выше двум путям и комбинируя их между собой, программисты постепенно разработали и создали для каждой машины целый комплекс приемов и средств, в который — наряду с трансляторами — входит и ряд других специальных Про- грамм, предназначенных для выполнения с помощью ЭВМ различ- ных видов работ, возникающих в процессе изготовления программ. При этом отдельные компоненты этого комплекса рассчитаны на их достаточно тесное взаимодействие, в результате чего они образуют некоторую систему, получившую название система програм- мирования, Система программирования является срставной частьк> математического обеспечения^ которым снабжается каждая ЭВМ (о других компонентах математического обеспечения ЭВМ мы будем говорить в последних главах). ЭВМ вместе с ее математическим обеспечением называют вы- числительной системой. Таким образом, в настоящее время пользо- ватель имеет дело не просто с ЭВМ, а с вычислительной системой», которая предоставляет ему значительные удобства и оказывает раз- личного рода услуги, существенно упрощающие пользователю ра- боту по изготовлению своих программ. Разумеется, пользователь должен знать возможности той вычислительной системы, на кото- рой он работает, и уметь ими пользоваться. Появление достаточно развитых систем программирования на- столько упростило процесс изготовления программ, что практически отпала необходимость в программистах как посредниках между поль- зователями и ЭВМ — теперь каждый пользователь, как правило» является и программистом. Профессиональных же программистов стали называть системными программистами, поскольку их ос- новной задачей как раз и является разработка и создание систем программ специального назначения для новых ЭВМ или усовершен- ствование и развитие существующих систем, в частности — про- грамм, входящих в систему программирования. В настоящее время существует много различных систем програм- мирования. Поэтому мы не будем рассматривать какую-либо кон- кретную систему, а остановимся на наиболее существенных и типич- ных чертах таких систем и на отдельных их компонентах.
S48 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. в 6.1. Модульное программирование 6.1.1. Понятие и назначение модуля. Как уже отмечалось, трудоемкость изготовления программ вызы- вает естественное желание однажды приготовленную программу впоследствии использовать многократно. Простейшая возможность многократного использования про- граммы открывается, например, в том случае, когда программа из- готовлялась для решения какой-либо задачи применительно к не- которым конкретным исходным данным, а затем возникает необхо- димость решать эту же задачу для других исходных данных. Если в программе предусмотреть ввод исходных данных, например с пер- фокарт, то единственное, что нужно сделать для повторного исполь- зования этой программы — подготовить перфокарты с нужными исходными данными; сама же программа не требует каких-либо ее изменений. Однако такая возможность встречается сравнительно редко. Более широкие возможности многократного использования про- граммы открываются в том случае, когда она может быть достаточ- но просто использована как часть другой, более общей программы, во взаимодействии с другими ее частями. Если, например, програм- ма предназначена для вычисления какой-либо функции y~f(x), то в принципе ее можно использовать при решении любой задачи, где возникает необходимость вычисления той же самой функции — надо лишь обеспечить, чтобы значение аргумента вычислялось, а полученное значение функции использовалось нужным образом в другой части программы, предназначенной для решения этой задачи. Когда мы говорим о повторном использовании программы, то речь идет, вообще говоря, не о буквально той же самой задаче, для решения которой она предназначалась. Более того, такая програм- ма сама по себе может вообще не быть предназначена для решения какой-либо задачи — эта задача будет решаться только в резуль- тате взаимодействия нескольких программ. Например, программа может быть предназначена лишь для реализации одного из числен- ь ных методов вычисления определенного интеграла у~ (x)dx, а исходя из того, что пределы интегрирования а и b будут заданы, значение у — использовано в другой программе, а вычисление кон- кретной функции /(х)'будет реализовано третьей программой. Оче- видно, что такая программа численного интегрирования вообще не может быть выполнена сама по себе, а может использоваться только в некотором контексте, т. е. во взаимодействии с другими программами. Возможность последующего многократного использования про- граммы в разных контекстах надо учитывать еще при ее написании,
«.и МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ 349 а для того, чтобы «настройку» этой программы на тот или иной конк- ретный контекст можно было осуществлять достаточно просто, она должна быть и оформлена соответствующим образом. Программа, рассчитанная на ее многократное использование в различных контекстах и оформленная соответствующим образом, называется модулем. Использование в программировании модулей (или «модульное программирование») позволяет применять для разработки сложных программ прием, хорошо известный в других сферах человеческой деятельности — промышленности, строительстве и т. д. Этот при- ем заключается в том, что какая-либо сложная проблема для ее ре- шения расчленяется на ряд более простых подпроблем, решение каждой из которых, естественно, представляет из себжуже более простую задачу. Например, создание предприятия по производству автомобилей, исходя только из начальных видов сырья (различных металлов, пластмасс, стекла, каучука и т. д.) было бы слишком сложной задачей. На самом деле проблему производства (и даже проектирования) автомобилей расчленяют иа ряд более простых проблем: изготовление (проектирование) двигателя, шасси, электро- оборудования и т.д.— эти отдельные узлы и являются по сути дела «модулями», из которых затем собирается готовый автомобиль. Если при этом имеется достаточный ассортимент таких модулей и при их изготовлении учитывались заранее сформулированные стандарты, обеспечивающие их стыковку друг с другом, то из этого ассортимен- та модулей можно достаточно легко и просто собирать готовые ав- томобили, и даже разных марок. Аналогично обстоит дело и в сов- ременном домостроении, где из одних и тех же готовых модулей (ба- лок, плит, панелей, лестничных маршей и т. д.) быстро собирают до- ма с различной архитектурой, этажностью, типами квартир и т.д. Точно такой же прием применим и в программировании: если с помощью ЭВМ нужно решить какую-либо сложную проблему, то ее можно расчленить на ряд более простых подпроблем; каждую (или некоторые) из них, в свою очередь, можно расчленить на еще более простые подпроблемы и т.д.— до тех пор, пока изготовление программы для каждой из них не будет представлять из себя уже достаточно простую задачу. Мы уже знакомились ранее с этим методом программирования «сверху — вниз». Если расчленение проблемы на подпроблемы и определение интерфейса между ними проведены достаточно четко, то каждую подпроблему можно программировать отдельно и неза- висимо от других. Это, в частности, дает возможность при разработ- ке больших программ использовать коллектив программистов, каж- дому из которых поручается составить программу для отдельной под- проблемы и которые могут работать параллельно и независимо друг от друга. Такой параллелизм работы программистов позволяет су- щественно сокращать общие календарные сроки изготовления боль-
350 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. в ших программ. Если отдельные части программы будут изготовлены, с учетом определенных требований,, обеспечивающих достаточно, простую их стыковку, т. е. будут оформлены в виде модулей, то об- щая программа решения исходной проблемы может быть «собрана» из этих готовых модулей. Следует отметить, что в программировании использование моду- лей дает особенно большой эффект в связи с тем, что здесь (в отли- чие, например, от строительства) использование какого-либо моду- ля для решения конкретной задачи вовсе не означает его физиче- ского потребления и тем самым невозможность его использования для решения других задач. На самом деле один и тот же програм- мный модуль, будучи однажды изготовленным, в дальнейшем мо- жет использоваться многократно, при решении любой конкретной проблемы, в которой в качестве одной из ее частей фигурирует под- проблема, решаемая с помощью этого модуля. В связи с этим можно организовать сбор изготовленных модулей, их хранение в библио- теке модулей и предоставлять их для всеобщего использования, что является эффективным средством экономии труда программистов. С понятием модуля мы частично уже знакомы по предыдущей главе, где было рассмотрено понятие стандартной подпрограммы, так как последняя по сути дела является модулем. Действительно^ каждая такая подпрограмма тоже рассчитана на ее использование в разных программах, т. е. по существу в разных контекстах, и при каждом использовании подпрограммы ее нужно «настраивать» на конкретный контекст — настраивать по ее месту в памяти и на за- даваемые фактические параметры. . При составлении подпрограмм мы исходили из того, что все пара- метры каждой подпрограммы являются динамическими, т. е. соот- ветствующие им фактические параметры задаются уже в процессе, выполнения программы машиной, при каждом очередном обращении к данной подпрограмме. При этом функция настройки на фактиче- ские параметры возлагалась ца саму подпрограмму, поэтому в каж- дой из них предусматривается соответствующая «формирующая» часть, выполнение которой и осуществляет настройку подпрограм- мы на заданные при обращении фактические параметры. Динамические параметры обеспечивают большую гибкость ис- пользования подпрограммы, так как они позволяют обращаться к одной и той же подпрограмме из разных мест основной программы и притом с разными фактическими параметрами. Однако в ряде слу- чаев указанное свойство становится не достоинством, а недостатком подпрограммы. Действительно, пусть в основной программе имеется единствен- ное обращение к данной подпрограмме, содержащееся к тому же в некотором цикле. В таком случае при каждом обращении к подпро- грамме, естественно, будут задаваться одни и те же фактические па- раметры (мы будем иметь в виду случай вызова параметров по име-
6.1] > МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ ? 35J ни, когда при обращении указывается место фактических парамет- ров в памяти машины), расположение которых в памяти при выпол- нении данной программы остается неизменным. Настройка же под- программы на эти фактические параметры будет производиться каж- дый раз заново, что приведет к бесполезным по сути дела затратам машинного времени и тем самым к снижению быстродействия про- граммы в целом, может быть весьма существенному (если цикл, в ко- тором содержится обращение^ выполняется много раз). Очевидно, что в этом случае было бы гораздо эффективнее произвести настрой- ку подпрограммы на ее фактические параметры только один раз, во время включения ее в общую программу в качестве одной из ее составных частей, т. е. сделать параметры подпрограммы cniamu- ческими. Это позволило бы также не предусматривать в подпрограм- ме ее формирующую часть, а а основной программе не нужно было бы задавать информацию о фактических параметрах — все это поз- волило бы не только повысить быстродействие программы, но и со- кратить затраты памяти для ее хранения. Заметим, что рассмат- риваемый случай при модульном программировании является до- статочно типичным, ибо модуль нередко представляет собой зна- чительную часть общей программы и используется только в одном ее месте. Довольно часто встречаются и случаи, когда при разных обраще- ниях к подпрограмме меняется лишь часть фактических параметров. В таких случаях для достижения наибольшей эффективности разум- но динамическими сделать только те параметры, которые меняются от одного обращения к другому, а остальные параметры сделать статическими. Недостатком подпрограммы является и то, что она по сути дела взаимодействует только с той программой, которая обращается к нэй, поскольку вся информация, необходимая для выполнения под- программы, задается в обращении. Между тем в ряде случаев воз- никает необходимость обеспечить непосредственное взаимодействие одной программы сразу с несколькими другими программами, при- чем хотелось бы иметь возможность реализовать такое взаимодейст- вие достаточно просто и максимально эффективно. Модуль — в отличие от подпрограммы — может иметь как дина- мические, так и статические параметры (этот вопрос решается ав- тором модуля), и, кроме того, он допускает весьма широкое взаимо- действие с другими модулями, чем и обеспечивается высокая эффек- тивность использования модулей. Таким образом, понятие модуля является дальнейшим разви- тием понятия подпрограммы, а последняя является частным слу- чаем модуля, который программисты выделили и начали использо- вать раньше всего. Современные системы программирования строятся именно в рас- чете на модульное программирование. И хотя в разных системах
852 СИСТЕМЫ ПРОГРАММИРОВАНИЯ (Гл. 6 могут допускаться разные формы взаимодействия модулей, предъяв- ляться различные требования к их оформлению, применяться раз- ные способы хранения и использования модулей, в этих системах есть и много общего. В частности, в системах программирования обычно различаются три формы представления модулей, соответст- вующие трем этапам получения программы (разработка алгоритма и его запись на удобном для человека языке программирования, со- ставление символической программы, ее кодирование с целью по- лучения машинной программы): 1. Пользовательская форма. 2. Загрузочная форма. 3. Абсолютная форма. Модуль, представленный в этих формах, часто называют соот- ветственно модулем пользователя, модулем загрузки и абсолютным модулем. Модуль пользователя. В этой форме модуль выражается на язы- ке программирования, удобном для человека. Таким языком может быть алгоритмический язык высокого уровня типа алгол-60 (за- метим, что в самом алголе-60 нет модульности — он рассчитан на запись программы в целом, и даже описание процедуры само по себе не является законченным текстом, что является существенным недостатком этого языка). Примером языка высокого уровня, рас- считанного на модульное программирование, может служить язык фортран. Для составления модулей пользователя может использо- ваться и машинно-ориентированный язык типа автокода (языку фортран и автокоду посвящаются следующие две главы). В настоя- щей главе для составления модулей мы будем использовать симво- лическое программирование. Модуль пользователя обычно состоит из двух частей: тела и паспорта. Тело модуля является основной его частью, определяю- щей суть модуля (аналог тела процедуры в алголе), а паспорт — это описательная часть модуля: он содержит дополнительную инфор- мацию, необходимую для последующего использования модуля (ана- лог заголовка процедуры в алголе). Модуль загрузки. Поскольку модули предназначаются для их многократного использования, они обычно записываются для хра- нения в специально организуемые на машине библиотеки, откуда затем они вызываются по мере надобности для работы в различных контекстах. Известно однако, что языки программирования, удоб- ные для человека, весьма далеки от машинного языка как по со- держанию употребляемых в нем операторов, так и по форме их пред- ставления. В связи с этим перевод модуля пользователя на машин- ный язык представляет из себя достаточно сложный и трудоемкий алгоритм, и было бы расточительством заново применять его при каждом очередном использовании модуля. Поэтому такой перевод обычно расчленяют на два этапа.
«.11 МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ 353 Первый этап состоит в переходе от пользовательской формы к такой форме представления модуля, которая максимально близка к машинной форме, но которая еще допускает настройку модуля для его работы в разных контекстах. Такая форма представления моду- лями называется загрузочной формой или языком загрузки. Переход от пользовательской формы к загрузочной форме модуля осуществ- ляется с помощью соответствующего транслятора. Трансляция каж- дого модуля выполняется всего один раз, после чего он хранится в библиотеке уже в загрузочной форме. Второй этап перевода состоит в настройке загрузочного модуля для работы в конкретном контексте. Этот этап называется загруз- кой, а программа, по которой он выполняется, называется загрузчи- ком. Под термином загрузка понимается ввод модуля в память машины, его размещение в отведенном для него месте памяти с на- стройкой по этому месту, а также настройка модуля на заданные фак- тические параметры, которые соответствуют его статическим фор- мальным параметрам. Поскольку загрузка модуля производится каждый раз, когда модуль включается в новый контекст, загрузочная форма и должна быть как можно ближе к машинному языку, чтобы загрузка выпол- нялась максимально просто и быстро. Отметим, что с языком загрузки непосредственно не имеет дела ни пользователь, ни машина — этот язык является внутрен- ним языком системы программирования, и поэтому единственные требования, которым он должен удовлетворять, состоят в том, что- бы обеспечить компактную форму представления модулей в машине и минимизировать долю работы, падающую на загрузчик, посколь- ку он выполняется машиной чаще, чем транслятор. Загрузочный модуль, как правило, также состоит из двух час- тей — тела и паспорта, причем паспорт, в котором содержится до- полнительная информация о модуле, используемая при его загруз- ке, также представляется в форме, удобной для загрузчика. Абсолютный модуль — это модуль, полученный в результате загрузки: он представлен на языке машины, настроен по своему мес- ту в памяти и. на взаимодействие с другими модулями, так что аб- солютный модуль является частью машинной программы, пригод- ной для ее непосредственного выполнения машиной. Таким образом, при модульном программировании программа, предназначенная для решения какой-либо конкретной задачи, по- лучается путем загрузки всех модулей, образующих эту программу. Если при этом можно использовать готовые модули, хранящиеся в библиотеке, то заново нужно изготовить лишь недостающие мо- дули. В связи с этим наличие достаточно богатой библиотеки моду- лей позволяет существенно ускорить и упростить работу по изго- товлению программ решения конкретных задач. Ч Э. 3. Любинский я др.
354 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. в 6.1.2. Составление модулей. Для иллюстрации работы с модулями рассмотрим следующий пример. Пусть требуется вычислить у = fl + j xs+x_|_0>5 dx}. 4 о ' Из этой задачи можно выделить две достаточно самостоятельные части: вычисление интеграла и вычисление подынтегральной функ- ции. Если для каждой из них будут составлены свои программы, то надо будет составить еще программу, которая реализует остальные действия, необходимые для решения данной конкретной задачи, и из этих трех программ — как готовых составных частей — собрать Рис. 6.1. программу, предназначенную для решения поставленной задачи. Чтобы сделать такую «сборку» возможной, каждая из этих трех программ должна быть оформлена в виде программного модуля, рас- считанного на взаимодействие с другими модулями. Графически взаимодействие выделенных нами модулей может быть выражено блок-схемой, приведенной на рис. 6.1, в которой стрелка => означает связку типа «обращение к подпрограмме с об- ратной связью». Чтобы программирование каждого модуля можно было проводить независимо от других модулей, сформулируем бо- лее четко назначение каждой из двух выделенных нами частей. а) Вычисление интеграла должно производиться по формуле прямоугольников: ь а где xt=a+(i—l/2)h, h—(b—a)/n (i=l, 2, ... n). Исходны- ми данными для этого модуля являются пределы интегрирования а и Ь, а также п — число узлов интегрирования. Результатом явля- ется значение интеграла у. Выполнение этого модуля должно за- вершаться переходом в некоторую точку обратившегося к нему мо- дуля. При выполнении данного модуля должен использоваться мо- дуль вычисления подынтегральной функции. б) Вычисление подынтегральной функции должно производить- ся по формуле — х3—1 У xa-J-x-f-0.5"
«.и МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ 355 Входной величиной этого модуля является х, а результатом — зна- чение у. Как мы уже отмечали, один и тот же модуль желательно затем использовать при решении любой задачи, где требуются аналогич- ные вычисления. С этой точки зрения важно, чтобы при составлении каждого модуля можно было использовать произвольные обозначе- ния, т. е. без их согласования с обозначениями, принятыми в дру- гих модулях. Перейдем теперь к составлению модулей. Модуль вычисления интеграла {INT). Алгоритм, реализуемый данным модулем (дадим ему имя INT), определим в виде описания процедуры алгола: procedure INT(a, b, п, f, у, М); real а, b, у, integer n; real procedure f; label M; begin real h, x\ h:—(b—a)/n; i/:=0; for x:=a+h/2 step h until b do y:=y+f(x)-, y:—yxh-, go to M end При составлении каждого из модулей мы будем рассматривать край- ний случай, когда все его параметры являются статическими, т. е. при обращении к модулю не будет задаваться какой-либо информа- ции о расположений его фактических параметров и, следовательно, в модулях не должно быть формирующих частей, а настройка моду- ля на фактические параметры должна производиться во время его. загрузки. Если исходить из того, что целые и вещественные значения пред- ставляются одинаково, в виде нормализованных чисел с плавающей запятой, то символическая программа, представляющая собой тело модуля 1N НАЧ: ЦИКЛ: LI: , может быт ь следующей: В В А R-, /i: = (6—а)/п Д R N Н\ п — — У", у:=0 ВПА 1 н R; х:= a+h/2 С А R X; ПБ — F ♦ t'—f (х) С У Т У; y:=y + t С X Н X; х:= x+h В в X со: = x>b ПУ — ЦИКЛ » Переход при со = false У У Н Y; y:=yxh ПБ —— М Возврат ПАМ в ПАМ 11 ПАМ If Рабочие ячейки ПАМ 1 R: Н: X: Т: Дадим классификацию имен, использованных в данном модуле. 12*
356 СИСТЕМЫ ПРОГРАММИРОВАНИЯ (Гл. в Имена НАЧ, ЦИКЛ, Li, R, Н, X и Т обозначают объекты (ко- манды и промежуточные результаты), принадлежащие данному мо- дулю, т. е. внутренние для него объекты; имена внутренних объек- тов часто также называют внутренними именами. Очевидно, что адреса внутренних объектов, на которые должны быть заменены их имена при получении абсолютного модуля, полностью определя- ются началом данного модуля в памяти машины. Так, если этот мо- дуль будет размещен с ячейки 0100, то строки программы, помечен- ные указанными выше именами, будут размещены соответственно в ячейках с адресами 0100, 0105, 0106, 0114, 0116 и 0117 — эти адреса и будут абсолютными адресами указанных объектов. Внутренние имена НАЧ, Li, X и Т играют несколько особую роль. Дело в том, что эти имена (а точнее, объекты с этими именами) должны использоваться в других модулях: на команду НА Ч дол- жен осуществляться лереход из того модуля, который будет обра- щаться к данному модулю; на команду Li должен осуществляться возврат из модуля, предназначенного для вычисления подынтег- ральной функции, по окончании его выполнения, причем этот мо- дуль из ячейки X должен брать значение аргумента, а в ячейку Т — помещать результат, т. е. вычисленное значение функции. Такие имена называются входными именами или просто входами этого мо- дуля. Имена А, В, N, У, F и М используются для ссылок на объекты, принадлежащие другим модулям, т. е. внешние по отношению к данному модулю объекты. В связи с этим такие имена называются внешними. Адреса, на которые должны заменяться внешние имена, завйсят уже от общего распределения памяти между модулями. Например, имя F используемся для ссылки на ту команду, с кото- рой должно начинаться выполнение модуля, предназначенного для вычисления подынтегральной функции; если этот модуль будет раз- мещен в памяти с ячейки 0200 и его выполнение должно начинаться с первой его команды, то значением имени F (абсолютным адресом, на который должно быть заменено это имя) будет адрес 0200. Заметим, что объекты с именами X и Т (значение аргумента и по- дынтегральной функции) должны использоваться как в нашем мо- дуле INT, так и в модуле, предназначенном для вычисления этой функции (пусть этому модулю дано имя FUN). Эти объекты мы вклю- чили в состав модуля 1NT, поэтому для него эти объекты являются внутренними, а для модуля FUN они будут внешними. Однако эти объекты можно было бы включить и в состав модуля FUN, и тогда эти объекты для него были бы внутренними, а для модуля INT — внешними. Это замечание относится к любым общим переменным, совместно используемым несколькими модулями, поскольку их с одинаковым успехом можно включить в любой из этих модулей. Для упрощения дальнейшего использования составленного нами модуля снабдим его паспортом, в котором содержится информация
6.1] МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ 357 об использованных в модуле именах, необходимая для организаций последующего взаимодействия этого модуля с другими модулями. При этом условимся считать, что первая строка паспорта должна содержать имя модуля и число строк в теле модуля (т. е. число ячеек памяти, необходимое для его размещения): 1NT: 16 • НАЧ: ВХОД 0; Вход в модуль INT (строка 0) £1: ВХОД 6; Точка возврата в модуль INT X: ВХОД 14; Аргумент функции (строка 14) Т: ВХОД 15; Значение функции (строка 15) F: ВНЕШ ; Вход в модуль f(x) М: ВНЕШ ; Точка возврата из модуля INT А: ВНЕШ ; Нижний предел В: ВНЕШ ; Верхний предел N: ВНЕШ ; Число узлов Y: ВНЕШ ; Значение интеграла (строка 6) Этот паспорт составлен в предположении, что у нас нет никакой дополнительной информации о тех модулях, с которыми будет взаи- модействовать данный модуль. В строках паспорта, относящихся ко входам данного модуля, указаны номера соответствующих строк в теле модуля (нумерация ведется, начиная с нуля) — эта информа- ция будет нужна при загрузке. Впрочем, эту информацию на дан- ном этапе можно было бы и не задавать — ее занесение в паспорт можно сделать и на этапе трансляции. Модуль вычисления функции (FUN). Теперь составим модуль с именем FUN, предназначенный для вычисления подынтегральной функции //=f(x)=(x3—1)/(х2+х+0.5). Алгоритм этого модуля можно задать в виде описания процедуры алгола procedure FUN(x, у, М): real х, у, label М: begin у:=(х f 3—1)/(х f 2-f-x+0.5); go to М end Тело модуля, реализующего эту процедуру, можно записать так: НАЧ: ........... X X У R: г = х3 У R X R Ч~ 1; rl = хВ 9 В R4-1 С1 R + 1; rl=X»-l с R X R: Г =Ха+ V с R С2 R: г =xs4-x4-0.5 Д Я + 1 R Y-, У = г\/г ПБ — М ' » Возврат С1: конст 1.0 С2: конст 0.5 * Константы' R: ПАМ 2 9 Рабочие ячейки В этом модуле имена НАЧ, Cl, С2 и /? являются внутренними, причем имя НАЧ является входом модуля, а имена X, Y и М яв- ляются внешними, так что паспорт этого модуля будет иметь вид:
358 СИСТЕМЫ ПРОГРАММИРОВАНИЯ 1Гл. 6 FUN; 11 НАЧ; ВХОД 0; Вход в модуль (строка 0) X: ВНЕШ ; Аргумент функции Y: ВНЕШ ; Значение функции Af: ВНЕШ ; Точка возврата из модуля FUN Этот паспорт опять составлен в предположении, что у нас нет ника- кой информации о модулях, взаимодействующих с данным модулем. Главный модуль (MOD). Наконец, составим модуль с именем MOD, который вместе с модулями INT и FUN образует программу решения поставленной задачи. Этот модуль является главным — в том смысле, что с его выполнения и должно начаться выполнение программы в целом. Модуль MOD должен реализовать те действия, которые необходимы для решения нашей конкретной задачи и кото- рые не предусмотрены в модулях 1NT и FUN. Чтобы по нашей программе можно было вычислять интеграл при разных пределах интегрирования и при разном числе узлов, предусмотрим ввод этих значений с внешних носителей. Алгоритм, реализуемый данным модулем, можно задать в виде следующего описания процедуры алгола (в предположении, что было дано опи- сание процедуры с некоторым именем L1, предназначенной для вы- числения определенного интеграла, и описание процедуры G, пред- назначенной для вычисления подынтегральной функции): procedure MOD; begin real al, a2, y\, y2; integer n; ввод (al, a2, n); Ll(al, a2, n, G, yl); y2:=(l+yl) f 2; вывод (y2) end Поскольку мы не рассматривали операций ввода/вывода на конкрет- ной машине, то будем использовать условную запись таких команд: будем считать, что команда вида ВВОД-------А означает ввод очередного числа с перфокарт в ячейку памяти А (с его переводом из десятичной в двоичную систему счисления), а ко- манда вида ПЧ А------- означает перевод числа, выбираемого из ячейки А, в десятичную систему счисления и его печать на рулоне бумаги печатающего устройства. Тогда символическая программа, представляющая со- бой тело модуля MOD, будет иметь вид: ВВОД — — А1; Ввод нижнего предела ВВОД — — А2; Ввод верхнего предела ВВОД — — N; Ввод числа узлов ПБ — L1 —; Переход на модуль интегриро- . вания
6.1] МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ 359 L2: а2 С ЕД У1 У2; «/2=14-С g(x)dx al У Y2 Y2 Y2; ПЧ Y2 — —; Печать результата СТОП — — —; Стоп ЕД: КОНСТ 1.0 ; Число 1 41: А2: N: У1: У2: ПАМ Ц ПАМ 1 ПАМ 11 Рабочие ячейки ПАМ 1 ПАМ 1) Для этого модуля внешним будет только имя 1А, а все остальные имена — внутренними, причем имена L2, А1, А2, N и Yi будут вхо- дами, поскольку соответствующие объекты должны использоваться в других модулях. В связи с этим паспорт данного модуля будет иметь вид: MOD: 14 L2: ВХОД 4; Точка возврата в модуль MOD (строка 4) 41: ВХОД 9; Нижний предел (строка 9) А2: ВХОД 10; Верхний предел (строка 10) N: ВХОД 11; Число узлов (строка 11) У1: ВХОД 12; Значение интеграла (строка 12) £1: ВНЕШ ; Вход в модуль интегрирования 6.1.3. Использование модулей. Итак, все части программы, предназначенной для решения по- ставленной задачи, составлены и оформлены в виде модулей. Но поскольку эти модули составлялись независимо друг от друга, и в частности — без согласования обозначений, то одни и те же объек- ты, используемые в разных модулях, обозначаются в них, вообще говоря, по-разному. Например, имя нижнего предела интегрирова- ния в модуле MOD есть 41, а имя этого же объекта в модуле INT есть А; возврат из модуля FUN осуществляется по метке М, тогда как на самом деле надо осуществить возврат к той команде модуля INT, которая помечена в нем меткой £1 и т. д. Если же какой-либо объект в разных модулях и обозначается одинаково, то это следует рассматривать не более, как случайное совпадение. И это вполне естественно, поскольку все внешние имена в каждом модуле явля- ются на самом деле статическими формальными параметрами, кото- рые — для согласованной работы модулей — должны быть замене- ны на соответствующие им фактические параметры. Эта замена дол- жна быть произведена загрузчиком в процессе загрузки модулей. Для этого загрузчику необходимо задать информацию о том, какой фактический параметр соответствует каждому из формальных па- раметров.
360 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 6 Такое задание информации о фактических параметрах делается с помощью паспортов модулей, а именно: для каждого внешнего имени, перечисленного в паспорте, указывается, какой вход какого модуля является соответствующим ему фактическим параметром. Таким образом, организация нужного взаимодействия используе- мых модулей сводится к уточнению их паспортов. В нашем случае такие уточненные паспорта примут следующий вид: Паспорт модуля INT: INT: 16 НАЧ'. ВХОД. 0 Вход в модуль INT LI: ВХОД 6 Точка возврата в модуль INT X: ВХОД 14 Аргумент функции Т: ВХОД 15 Значение функции F: ВНЕШ FUN. НАЧ F — это вход НАЧ в модуле FUN М: ВНЕШ MOD. L2 М—это вход L2 в модуле MOD А: ВНЕШ MOD. Al А —это вход А1 в модуле MOD В: ВНЕШ MOD. A2 В —это вход А2 в модуле MOD N: ВНЕШ MOD. N N — это вход N в модуле MOD Y: ВНЕШ MOD. YI У —это вход YI в модуле MOD Паспорт модуля FUN: FUN: 11 НАЧ: ВХОД 0 ; Вход в модуль FUN X: ВНЕШ INT.X ; X —это вход X в модуле INT Y — это вход Т в модуле INT М—это вход LI в модуле 1NT Y: ВНЕШ М: ВНЕШ INT.T ; INT.LI-, Паспорт модуля MOD: MOD: 14 L2: ВХОД 4 ; Точка возврата в модуль MOD А1: ВХОД 9 ; Нижний предел интегрирования А2: ВХОД 10 ; Верхний предел интегрирования N: ВХОД 11 ; Число узлов YI: ВХОД 12 ; Значение интеграла LI: ВНЕШ INT.HA4\ L\—это вход/7AV в модуле 1NT Для получения рабочей программы нужно для тела каждого модуля отвести свое место в памяти (информация о длине тела содержится в первой строке паспорта этого модуля). Допустим, что произведено следующее распределение памяти: MOD: 0100—0115; INT: 0116—0135; FUN'. 0136—0150. Поскольку определено начало каждого модуля в памяти, то тем самым определяются адреса и всех его внутренних объектов, в частности—адреса всех его входов, перечисленных в паспорте
6.1] МОДУЛЬНОВ ПРОГРАММИРОВАНИЕ 361 (адрес входа равен началу модуля в памяти плюс относительный номер строки, которая соответствует этому входу): Модуль MOD Модуль INT Модуль FUN adp.L2 =0104 adp.41=0111 адр. А2 = 0112 адр.М =0113 adp.Y 1=0114 адр. НАЧ =0116 адр. £1=0124 адр. Х = 0134 адр. Т = 0135 адр.НАЧ = 0136 По этим адресам входов определяются I адреса всех внешних имен, перечисленных в паспортах: Модуль MOD Модуль INT Модуль FUN adp.Ll =0116 adp.F =0136 адр.Х =0134 адр.М = 0104 adp.Y =0135 одр.А =0111 адр.М = 0124 адр.В =0112 адр.N =0113 adp.Y =0114 Итак, теперь определены абсолютные адреса для всех имен, использованных в каждом из модулей (адрес каждого внутрен- него имени М равен ан + Лм» где ан —начало в памяти данного модуля, а Дм—относительный номер строки в модуле, которой дано имя М), так что в итоге получаем следующие таблицы со- ответствия между именами объектов и их абсолютными адресами. Для модуля MOD: ЕД-.0110, 41:0111, 42:0112, М:0113, У1:0114, У2:0115, Ll:0H6. Для модуля FUN: €1:0145, С2:0146, /?:0147, Х:0134, У:0135, Л4:0124. Для модуля INT: ЦИКЛ-.0123, Я:0132, Я:0133, Х:0134, Т:0135, F:0136, 44:0104, 4:0111, В:0112, ЛГ:0113, У:0114. Для получения абсолютных модулей (а тем самым и машинной программы в целом) надо символические команды каждого загрузоч- ного модуля преобразовать в машинные: символический код опера- ции в каждой команде надо заменить на соответствующий цифровой код, а имя каждого объекта в адресной части команды — на его аб- солютный адрес, указанный в одной из приведенных выше таблиц. При этом в случае необходимости вычисляются значения выражений, фигурирующих в адресной части (например, выражение 7?+1, встречающееся в командах модуля FUN, надо заменить его значе- нием 1147+1=0150). Полученные машинные команды размещаются в последовательных ячейках памяти, отведенных для размещения этого модуля. При этом, конечно, каждую из запасенных констант
362 СИСТЕМЫ ПРОГРАММИРОВАНИЯ (Гл. в необходимо из ее символической записи преобразовать в соответст- вующее машинное слово. Например, модуль FUN преобразуется в следующую абсолют- ную форму: 0136 005 0134 0134 0147 г = х5 0137 005 0147 0134 0150 И = х3 0140 002 0150 0145 0150 Н=х3-1 0141 001 0147 0134 0147 Г = X3 + х 0142 001 0147 0146 0147 г = х2-|-х4-0.5 0143 004 0150 0147 0135 // = rl/r 0144 056 0000 0124 0000 Возврат в модуль 1NT 0145 001 4000 0000 0000 Число 1 0146 000 4000 0000 0000 Число 0.5 0147 000 0000 0000 0000 0150 000 0000 0000 0000 Рабочие ячейки Как видно, при использовании готовых модулей с целью полу- чения единой программы, предназначенной для решения конкретной задачи, пользователю нужно по существу лишь «состыковать» эти модули, заполнив в паспортах модулей строки, относящиеся к внеш- ним именам, причем число таких строк в реальных модулях, как правило, весьма невелико по сравнению с их собственными разме- рами. Заметим также, что выше мы рассматривали крайний случай, когда при составлении какого-либо модуля нет никакой дополни- тельной информации о других используемых модулях. На самом деле нередко (особенно при наличии библиотеки готовых модулей) при составлении одного модуля уже имеется информация о тех модулях, с которыми он должен взаимодействовать, и в этом слу- чае при составлении модуля можно сразу составить и его полный паспорт. Из рассмотренного примера видно также, что при наличии пол- ных паспортов всех используемых модулей дальнейшая работа по объединению модулей в единую машинную программу носит чисто технический характер, причем можно сформулировать четкие пра- вила ее выполнения. Именно поэтому выполнение этой работы и можно поручить самой машине с помощью специальной обслужи- вающей программы — загрузчика, которая входит в состав системы программирования. . 6.1.4. Общие объекты и их использование. Связь модулей по переменным величинам с помощью внешних имен вызывает определенные неудобства, поскольку эти величины в принципе можно включать в состав любого из модулей, используе- мых при решении данной задачи. В связи с этим приходится либо заранее договариваться о том, в состав каких модулей включать те
в. и МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ 363 или иные переменные, либо — если этот вопрос решается уже при использовании готовых модулей — существенно менять Паспорта модулей, добавляя или исключая строки, относящиеся к входам мо- дуля и внешним именам. Для устранения этих трудностей в некото- рых системах программирования связь модулей по переменным ве- личинам осуществляется через так называемые общие блоки данных. Общий блок данных — это по сути дела одномерный массив, ко- торый считается доступным для всех модулей, входящих в состав программы (поэтому он и называется общим). При этом каждый модуль может по своему усмотрению разместить в общем блоке лю- бые используемые в этом модуле переменные величины (простые переменные или массивы) с указанием номера позиции общего бло- ка, начиная с которой должна быть размещена каждая из этих ве- личин (нумерацию позиций условимся вести, начиная с нуля). При загрузке программы для общего блока отводится группа ячеек па- мяти с последовательными адресами, начиная с некоторого адреса АОБЩ. Если какая-либо величина с именем М была размещена в общем блоке, начиная с его к-й позиции, то имя М в результате за- грузки будет заменено на абсолютный адрес АОБЩА-к. Таким об- разом, если величина с именем АН одного модуля и величина с име- нем М2 другого модуля будут включены в общий блок и размещены в нем с одной и той же его позиции, то при загрузке каждое из этих имен Ml и М2 будет заменено на один и тот же абсолютный адрес, и тем самым эти величины фактически будут отождествлены. Это обстоятельство и позволяет весьма просто обеспечивать связь моду- лей по переменным величинам. В некоторых системах программирования допускается исполь- зование в любой программе нескольких общих блоков данных, и тогда им даются индивидуальные имена. Поскольку практически в любой задаче используется хотя бы один общий блок, то обычно одному из таких блоков можно не давать имени — это так называе- мый «непомеченный общий блок». Таким образом, при использовании общих блоков данных воз- никает еще один класс имен — общие имена, с помощью которых производятся ссылки на величины, включенные в общие блоки. В этом случае в паспорте модуля необходимо задать информацию о том, какие имена являются общими и на какую позицию какого об- щего блока ссылается то или иное общее имя. Например, рассмотренные ранее модули INT и FUN взаимо- действуют между собой по переменным, представляющим значение аргумента и значение подынтегральной функции. Еели эти значе- ния разместить последовательно в первых позициях общего блока с именем СОМ, то для согласования модулей INT и FUN по этим пе- ременным в паспорте модуля INT запишем строки X: ОБЩ СОМ.(У, Т: ОБЩ СОМА;
364 СИСТЕМЫ ПРОГРАММИРОВАНИЯ 1Гл. в а в паспорте модуля FUN — строки X: ОБЩ СОМ.О-, Y: ОБЩ СОМА; Если для общего блока СОМ в памяти будет отведено место, начиная с ячейки 0300, то имя X в модуле INT и имя X в модуле FUN будут заменены на абсолютный адрес 0300, а имена Т и Y соответственно — на .абсолютный адрес 0301. Используем этот аппарат согласования модулей для рассматри- ваемого нами примера. Тела модулей будут такими же (за исключе- нием того, что некоторые внутренние величины теперь перейдут в общий блок) — изменятся лишь паспорта модулей. Переменные, по которым осуществляется взаимодействие модулей, разместим в непомеченном общем блоке в следующем порядке: 0. Нижний предел интегрирования. 1. Верхний предел интегрирования. 2. Число узлов. 3. Значение аргумента функции. 4. Значение функции. 5. Значение интеграла. Тогда полные паспорта модулей примут следующий вид (длины некоторых модулей теперь уменьшатся за счет использования об- щего блока): Модуль MOD Модуль INT MOD’. 10 L2: ВХОД 4 1Л: ВНЕШ I NT.НАЧ А1: ОБЩ 0 А2: ОБЩ 1 N:- ОБЩ 2 У1: ОБЩ 5 INT: 14 НАЧ: ВХОД 0 1Л: ВХОД 6 F: ВНЕШ FUN.НАЧ М: ВНЕШ MOD. L2 X: ОБЩ 3 Т: ОБЩ 4 А: ОБЩ 0 В: ОБЩ 1 N: ОБЩ 2 Y: ОБЩ 5 Модуль FUN FUN: 11 НАЧ: ВХОД 0 . М: ВНЕШ INT.IA X: ОБЩ 3 Y: ОБЩ 4 Для загрузки необходима еще информация о длине каждого обще- го блока. Эта информация должна содержаться в паспортах моду- лей — ее задание производится в соответствии с определенным со- глашением по этому поводу, принятому в данной системе программи- рования.
«.!] МОДУЛЬНОЕ ПРОГРАММИРОВАНИЕ 365 6.1.5. Схемы трансляции и загрузки. Если при программировании не используются модули, то полу* чение и выполнение каждой программы идет по схеме, приведенной на рис. 6.2. Рис. 6.2. При модульном программировании эта схема приобретает вид, приведенный на рис. 6.3. Рис. 6.3,
366 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл..В. При работе по этой схеме пользователь обычно имеет возмож- ность формулировать системе задание на выполнение с помощью ЭВМ определенной работы, для чего используется соответствующий язык общения человека с системой. Эго задание может содержать в себе тексты модулей пользователя, подлежащие трансляции, ин- формацию о том, какие модули после их трансляции надо записать в библиотеку, указания системе о сборке интересующей пользова- теля программы из отдельных модулей, в том числе и готовых моду- лей, хранящихся в библиотеке, о выполнении полученной програм- мы и т. д. Если на машине решаются сравнительно несложные задачи, то первая из этих двух схем более эффективна, поскольку в ней от- сутствует один дополнительный этап (загрузка). Поэтому на малых Программист I Модули пользователя Рис. 6.4. ЭВМ именно такие схемы программирования и используются чаще всего. При решении же больших и сложных задач, которые при про- граммировании приходится расчленять на составные части, вторая схема имеет очевидные преимущества: а) имеется возможность осуществлять программирование по час- тям; б) имеется широкая возможность удобного использования гото- вых модулей;
•.2] ЗАГРУЗЧИКИ И РЕДАКТОРЫ СВЯЗЕЙ 367 в) если возникает необходимость внести в программу те или иные изменения, то. изменяется я заново транслируется не вся програм- ма, а, как правило, только один модуль. Заметим, что современные системы программирования являют- ся, как правило, многоязыковыми, т. е. для написания программ (и даже разных модулей одной и той же программы) могут исполь- зоваться различные языки программирования, наиболее удобные для тех или иных программ (модулей). В многоязыковой системе программирования последняя схема приобретает вид, приведенный на рис. 6.4. Все трансляторы осуществляют перевод модулей пользовате- ля на один и тот же язык загрузки, так что в дальнейшем загрузоч- ные модули могут использоваться независимо от того, с помощью какого транслятора был получен тот или иной модуль. Эта схема имеет то дополнительное преимущество, что несколь- ко упрощаются сами трансляторы, поскольку некоторые функции каждого из них передаются загрузчику. 6.2. Загрузчики и редакторы связей Одно из важнейших преимуществ модульного программирова- ния состоит в том, что однажды изготовленный модуль может затем многократно использоваться в различных контекстах. Каждое ис- пользование модуля подразумевает его загрузку, т. е. его ввод в оперативную память и настройку на конкретный контекст (т. е. настройку на отведенное ему место в памяти и на взаимодействие с другими модулями). Модули, представленные на языке загрузки, обычно хранятся в одной из библиотек во внешней памяти машины. Общие требования к языку загрузки уже были сформулированы — этот язык должен обеспечивать компактность представления моду- лей в памяти машины и простоту загрузки. Разумеется, эти требо- вания не определяют язык загрузки однозначно, так что даже для одной и той же машины многие возникающие здесь вопросы могут решаться различными способами. Рассмотрим более подробно один из таких языков применитель- но к учебной машине УМИР-3. Условимся операцию в команде задавать в виде машинного (цифрового) кода операции, а в каждом поле адреса задавать услов- ный адрес, состоящий из 14 двоичных разрядов Е1Е2Е8Е4 • • • Ем 6 А где 6 — признак адреса, а А — информация о нем: 5=00 — признак абсолютного адреса, и тогда Д — сам абсо- лютный адрес; 6=01 — признак внутреннего адреса, и тогда Д — сдвиг отно- сительно начала модуля;
368 СИСТЕМЫ ПРОГРАММИРОВАНИЯ (Гл. • . 6=10 — признак внешнего адреса, и тогда Д — порядковый но- мер внешнего адреса в таблице внешних адресов, содержащейся в паспорте модуля; 6=11 — признак общего адреса, и тогда Д — порядковый но- мер этого общего адреса в таблице общих адресов, содержащейся в паспорте модуля. В дальнейшем условимся считать, что связь модулей по пере- менным величинам осуществляется через единственный (непоме- ченный) общий блок, и все нумерации будем вести, начиная с нуля. На таком языке загрузки команда модуля FUN У X X R будет Представлена в виде 005 30000 30000 10011 (первая цифра в записи каждого условного адреса является четве- дичной, а остальные — восьмеричными), команда возврата в мо- руль INT ПБ — М — будет иметь вид 056 00000 20000 00000, а константа, представляющая число 1, будет записана в виде 001 04000 00000 00000. Как видно, для представления каждой команды или константы на таком языке загрузки одного машинного слова оказывается недо- статочно, поскольку требуется 51 двоичный разряд, а машинное слово УМИР-3 содержит только 45 разрядов. Для достижения мак- симальной компактности представления тела модуля в памяти ус- ловимся ту часть команды или константы, которая не поместилась в очередной ячейке, размещать в начале следующей по порядку ячейки, а очередную команду (константу) будем размещать с оче- редного свободного разряда очередной ячейки: Ради определенности будем считать, что каждая рабочая ячейка запасается в форме константы, представляющей число нуль. Для паспортов модулей тоже выберем определенную форму пред- ставления. На внутреннем языке системы каждому модулю можно присвоить определенный номер, играющий роль имени данного мо- дуля. Паспорт модуля будем представлять следующим образом (см. табл. 6.1).
6.21 ЗАГРУЗЧИКИ И РЕДАКТОРЫ СВЯЗЕЙ J69 Таблица 6.1 > Таблица входов (п0 строк) > Таблица внешних адресов (nt строк) > Таблица общих адресов (nt строк) Значение па задает наибольший номер позиции общего блока данных, используемой в данном модуле. Каждая строка таблицы внешних адресов имеет вид ООО Mt 0000 Лг где Mt — номер (имя) модуля, а Л; — относительный номер стро« ки в таблице входов, содержащейся в паспорте модуля Mt — на этот вход ссылается внешний адрес, соответствующий данной строке таблицы внешних адресов. Каждая строка таблицы входов (и общих адресов) имеет вид ООО 0000 0000 А, где Д/ — относительный номер строки в данном модуле (в общем блоке), которая является очередным входом данного модуля (на которую ссылается данный общий адрес). Так, если модулю FUN присвоен номер 1000,, а модулю INT — номер 1001,, то паспорт модуля FUN на внутреннем языке будет иметь вид: 001 0001 0002 0004 п0=1’, п, = 1, п2=2, п3=4 000 0000 0000 0000 Вход 1 (строка с номером 0000) 000 1001 0000 0001 Внешний адрес 1: вход 1 модуля 1001 000 0000 0000 0003 Общий адрес 1: строка 0003 общего блока 000 0000 0000 0004 Общий адрес 2: строка 0004 общего блока Будем считать, что все используемые модули хранятся в библио* теке. В ее каталоге для каждого модуля отведено фиксированное число строк, в которых содержится имя модуля, его длина и его место в библиотеке. Паспорта модулей можно хранить отдельно, и
370 СИСТЕМЫ ПРОГРАММИРОВАНИЯ (Гл. в тогда в каталоге содержится также информация о длине паспорта данного модуля и его месте в памяти. При наличии паспортов, уже заполненных взаимными ссылками, процесс загрузки модулей для получения рабочей программы можно разбить на два основных этапа. Первый этап носит подготовительный характер. Его задача со- стоит в том, чтобы выявить все модули, подлежащие загрузке для получения данной программы, произвести распределение памяти между программными модулями и общим блоком данных (т. е. для каждого из них отвести место в оперативной памяти) и определить истинные адреса всех внешних и общих объектов для каждого из модулей. Для достижения этих целей сначала составляется таблица рас- пределения памяти (ТРП), в которой для общего блока и для каж- дого из модулей содержится одна строка. Для общего блока в ТРП отведем строку с номером 0, которая должна иметь вид ООО 0000 L а0 где L — длина общего блока, а а0 — его начало в памяти. Осталь- ные строки ТРП, относящиеся к программным модулям, должны иметь вид 000 Mt Ц at где Mt — имя модуля, lt — его длина, at — начало этого модуля в оперативной памяти. Параллельно с этим формируется массив пас- портов используемых модулей; в этих паспортах для каждого ус- ловного адреса (входа, внешнего и общего адреса) на основе про- изведенного распределения памяти фиксируется соответствующий ему абсолютный адрес. Таким образом, объектами первого этапа ра- боты являются паспорта модулей и ТРП. При обращении к загрузчику ему задается в качестве фактиче- ского параметра имя главного модуля программы, подлежащей за- грузке. В начале своей работы загрузчик в строку с номером 0 таблицы распределения памяти заносит нулевое машинное слово, что означает действие L:=0. Кроме того, по заданному имени Mt главного модуля формируется первая строка ТРП в виде 000 Mt 0000 0000 и выполняется действие i: = l, где / —5гпРавлЯ1°Щая переменная, те- кущее значение которой равно номеру обрабатываемой строки ТРП. Затем начинается общий цикл работы загрузчика, алгоритм выпол- нения которого можно неформально описать следующим образом. Г. Из строки с номером i ТРП выбирается имя модуля М{. В каталоге библиотеки по имени Mt отыскивается его длина lt и записывается в поле второго адреса i-й строки ТРП, которая при- мет вид 000 Mi Ц 0000
6.21 ЗАГРУЗЧИКИ И РЕДАКТОРЫ СВЯЗЕЙ 371 2°. Паспорт модуля М{ считывается.в массив паспортов. Из прочи- танного паспорта выбирается значение п, и производится пересчет длины общего блока данных по формуле L:=max (L, л,). 3°. В прочитанном паспорте просматривается таблица внешних адресов; если в очередной ее строке встречается имя модуля Мк, для которого в ТРП не было заведено строки, то в конец ТРП до- писывается строка ООО Мк 0000 0000. 4°. Выполняется действие если в ТРП есть строка с но- мером i, то производится возврат к п. Г. По окончании этого цикла в ТРП будут заведены строки для всех; используемых модулей с указанием их длин, определена длина L, общего блока и прочитаны паспорта всех используемых модулей. 5°.На основании длин 1, модулей и длины L общего блока произ- водится распределение памяти, и истинное начало а( каждого моду- ля и общего блока записывается в поле третьего адреса соответст- вующей строки ТРП, которая примет вид 000 Mt lt at (для строки, относящейся к общему блоку, Л40=0 и l0=L). 6°. В паспорте каждого модуля просматривается таблица вхо- дов; в каждой строке этой таблицы относительный номер Ьц заме- няется абсолютным адресом aj+'A*r где at — начало в памяти дан- ного модуля, выбираемое из соответствующей строки ТРП. 7°. В паспорте каждого модуля просматривается таблица внеш- них адресов. Для каждой строки, которая, как известно, имеет вид 000 М/ 0000 ДА/ происходит обращение к строке с номером Д*, таблицы входов, со- держащейся в паспорте модуля Л1,; из этой строки выбирается ис- тинный адрес ак этого входа, и Д«; заменяется на ак. 8°. Во всех паспортах просматриваются таблицы общих адресов^ и в каждой строке этих таблиц, имеющей вид 000 0000 0000 Д^ относительный номер Д*; заменяется истинным адресом а0+А*/к где а« — начало в памяти общего блока, выбираемое из строки с но- мером 0 в ТРП. На этом первый этап работы загрузчика заканчивается. В ре- зультате его выполнения произведено распределение памяти и опре- делены истинные адреса всех внешних и общих объектов для каждо- го модуля, т. е. установлены взаимные связи между модулями при- менительно к конкретному варианту их взаимодействия. Поскольку этот этап имеет вполне определенное, самостоятельное значение, то*, часть загрузчика, выполняющую этот этап, часто выделяют в само-
372 СИСТЕМЫ ПРОГРАММИРОВАНИЯ / (Г4. в / стоятельную программу, которую называют редактором межмо- дульных связей (или просто редактором связей). В этом случае по окончании своей работы редактор связей обращается к собственно загрузчику, передавая ему информацию о расположении ТРП и массива паспортов (если они размещаются не на фиксированных местах). Далее выполняется второй этап, на котором производится не- посредственная загрузка модулей, алгоритм выполнения которой неформально можно описать следующим образом. Г. i:=l, где i — порядковый номер загружаемого модуля. 2°. Из i-й строки ТРП выбирается имя модуля М{. В каталоге библиотеки отыскивается информация о месте этого модуля в биб- лиотеке, и тело модуля прочитывается в рабочее поле загрузчика. 3°. Из прочитанного тела модуля последовательно выделяются предложения, представленные на языке загрузки, и преобразуются в машинные команды, что сводится к переработке каждого услов* ного адреса (УА) в абсолютный адрес (А). Для этого выделяется и анализируется признак б условного адреса: —если 6=00 (признак абсолютного адреса), то А—А; —если 6=01 (признак внутреннего адреса), то А=а;+Д; —если 6=10 (признак внешнего адреса), то в таблице внешних адресов паспорта данного модуля берется строка с номером А и со- держимое поля третьего адреса этой строки принимается в качест- ве А; —если 6=11 (признак общего адреса), то он перерабатывается Так же, как и внешний адрес, но с использованием таблицы общих адресов, содержащейся в паспорте данного модуля. Полученные абсолютные адреса используются для формирова- ния машинной команды, которая записывается в очередную ячейку памяти массива ячеек, отведенных для размещения данного модуля (Напомним, что это массив ячеек с адресами а{, ai+1,..., —1). 4°. t:=i+l. 5°. Если в ТРП есть строка с номером I, то происходит возврат к п. 2°. После того, как будут просмотрены и обработаны все строки ТРП, в памяти машины будет получена рабочая программа, гото- вая к ее 'выполнению машиной. Обычно загруженная программа подлежит выполнению, поэтому по окончании своей работы загруз- чик осуществляет переход на начало главного модуля (по адресу аи зафиксированному в строке ТРП с номером 1). Приведенные выше описания алгоритмов работы редактора свя- зей и загрузчика носят схематичный характер — ради простоты, изложения мы скорее перечисляли, что должно быть сделано, не уточняя, как это следует сделать. Поэтому упомянутые алгоритмы вряд ли годятся для их непосредственной реализации — для это- го они должны быть соответствующим образом конкретизированы.
- «.81 ТРАНСЛЯТОРЫ 373 6.3. Трансляторы Напомним, что транслятором называют программу, предназна- ченную для перевода модуля пользователя с данного языка програм-. мнрования на язык загрузки (или на язык машины). В качестве исходных данных для транслятора выступает текст программы, записанный на исходном языке программирования (например, алгол-программа). Этот текст должен быть представлен в цифровой форме и введен в память машины. Преобразование текста программы в. цифровую форму произво- дится автоматически на устройствах подготовки данных, работаю- щих автономно от ЭВМ. На таких устройствах имеется клавиатура, похожая на клавиатуру обычной пишущей машинки, с помощью которой оператор печатает написанный пользователем текст про- граммы. Клавиатура связана с каким-либо внешним носителем дан- ных, например с перфокартами; каждой клавише поставлен в соот- ветствие определенный цифровой код (ндпример, восьмиразрядное целое двоичное число). Аппаратура устройства обеспечивает авто- матическую перфорацию в очередной колонке перфокарты кода, по- ставленного в соответствие нажимаемой клавише, так что в итоге программа представляется в виде последовательности целых чисел фиксированной разрядности, каждое из которых является кодом оче- редного символа в тексте программы. Полученная последователь- ность целых чисел — код программы — может быть введена в ма- шину и подвергнута той или иной переработке. Результатом рабо- ты транслятора является программа, представленная на языке за- грузки или на машинном языке. В работе транслятора можно выделить четыре основных этапа,. Лексический анализ. Этот этап является предварительным: его основное назначение состоит в том, чтобы представить исходный текст программы в максимально компактном и удобном для даль- нейшей обработки виде. Полученный таким образом текст передает- ся в качестве исходных данных для следующей части транслятдра, называемой синтаксическим анализатором. Синтаксическому ана- лизатору обычно приходится просматривать этот текст многократ- но, поэтому его важно представить в компактном и удобном для об- работки виде. Например, в исходном тексте программы может содержаться большое число незначащих пробелов (в алголе, например, все про- белы за исключением их вхождения в понятие (строка) игнори- руются), поэтому их целесообразно удалить из текста на первом же этапе его обработки. Кроме того, во многих языках программирования используют- ся основные символы, которые отсутствуют на клавиатуре устройств подготовки данных. Такие символы при перфорации представляют- ся последовательностями других символов, имеющихся на клавиа-
374 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. & туре. Например, основные символы алгола типа begin на самом деле представляются обычно в виде 'BEGIN', символ присваивания : = представляется парой символов : и = и т. д. Очевидно, что каждый из таких «составных» символов удобно предварительно заменить определенным цифровым кодом. То же самое касается и констант: вместо того, чтобы в процессе анализа текста программы много- кратно иметь дело, например, с последовательностью символов 4-3.1415926, целесообразно заменить ее некоторым условным чис- лом, по которому было бы видно, что здесь речь идет о вещественном числе, и чтобы при необходимости можно было выбрать это число. Лексический анализатор может производить и другие преобразо- вания исходного текста, позволяющие облегчить последующий его синтаксический анализ. Отличительная особенность этапа лексического анализа состоит в том, что все преобразования на этом этапе производятся локально: четко определенные символы или группы символов заменяются на фиксирдванные коды или-группы кодов, в какой бы конструкции программы они ни встречались. В связи с этим этап лексического анализа называют иногда «кодировкой». Работу, выполняемую на этом этапе, удобно описывать в виде нормального алгоритма, на- пример: 'BEGIN' 331 'END' 332 —301 и Т. д. Синтаксический анализ. На этом этапе производится синтакси- ческий разбор исходного текста (преобразованного на этапе лекси- ческого анализа): в соответствии с синтаксисом языка в тексте, предъявленном для трансляции, выделяются все компоненты, из которых состоит программа — операторы, описания (и их отдельные составные части), определяется смысл и область действия различ- ных идентификаторов и т. д. При этом (поскольку текст программы приходится просматривать неоднократно) может производиться бо- лее глубокая кодировка транслируемой программы. Так, если од- нажды было выявлено, что данная последовательность символов яв- ляется идентификатором, то этот идентификатор запоминается в одной из таблиц, а в тексте программы он заменяется некоторым ко- дом, по которому легко установить, что здесь имеет место вхожде- ние идентификатора, и — в случае необходимости — найти этот идентификатор в соответствующей таблице. На этом этапе решается и еще одна важная задача: проверяется, является ли переданная синтаксическому анализатору последова- тельность символов допустимой в данном языке, т. е. является ли она с точки зрения синтаксиса программой в данном языке програм- мирования.
ТРАНСЛЯТОРЫ 37S «.3) Если в исходном тексте содержатся синтаксические ошибки^ то. задача синтаксического анализатора — выявить по возможности все такие ошибки и выдать пользователю диагностические сообще- ния, в которых указывается место в исходном тексте, где допущена эта ошибка, и ее характер. Развитая диагностика, в том числе и по- нятные для пользователя диагностические сообщения, выдаваемые транслятором на печать, не только существенно облегчают пользо- вателю работу по выявлению и устранению допущенных синтакси- ческих ошибок, но и облегчают процесс овладения новым для него языком программирования. Конечно, пользователю хотелось бы, чтобы транслятор сразу выявил все синтаксические ошибки в его программе, однако сделать это не всегда удается. Дело в том, что разные синтаксические ошибки могут по-разному влиять на работу синтаксического анализатора. Есть ошибки, которые транслятор может не только выявить, но и сделать достаточно разумное предположение относительно того, как их следует устранить. Такие ошибки можно назвать устра- нимыми. Например, если в алгол-программе записан оператор присваивания г:= sinx+cosy и в программе отсутствуют описания идентификаторов sinx. и cos у, то достаточно правдоподобным будет предположение, что пользова- тель забыл заключить в скобки аргументы стандартных функций sin и cos. Поэтому транслятор может сам внести соответствующие исправления в текст программы, заменив предыдущую запись на a:=sin(x)4-cos(^), сообщить пользователю об этом факте и (если ре было обнаружено более серьезных ошибок) довести трансляцию до конца. Если все сделанные предположения оказались правильными, то транслятор выработает правильную программу, так что пользо- вателю даже не нужно будет вносить изменения в текст программы. В некоторых случаях транслятор может сделать разумное пред- положение о характере ошибки и продолжить анализ текста про- граммы, но внести достаточно обоснованное исправление он не мо- жет, поскольку для этого необходимо знать существо решаемой зада- чи, а такой информацией транслятор, естественно, не располагает— такие ошибки можно назвать полуфатальными. Если, например, в алгол-программе встретился оператор перехода go to ММ, но в ней отсутствует оператор, помеченный меткой ММ, то транслятор может предположить, что пользователь забыл пометить нужный оператор этой меткой ММ. Внести же достаточно разумное исправление в программу, т. е. фактически пометить меткой ММ какой-либо оператор, конечно, невозможно без знания существа алгоритма. В этом случае транслятор не будет доводить трансля- цию до конца, но он может продолжить синтаксический анализ по- следующей части текста, исходя из гипотезы, что запись go to ММ.
376 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. в является оператором перехода — это дает возможность транслято- ру либо выявить и другие синтаксические ошибки в программе и выдать пользователю соответствующие диагностические сообщения, либо убедиться, что других ошибок в программе нет. Наконец, некоторые ошибки могут носить фатальный характер, когда без устранения этой ошибки практически невоз- можно продолжить даже синтаксический анализ, а любое непра- вильное предположение о том, как ее исправить, может привести к тому, что транслятор начнет «выявлять» ошибки, которых на самом деле в программе нет. Такая ситуация может возникнуть, например, при нарушении баланса открывающих и закрывающих операторных скобок в алгол-программе. В таких случаях транслятор вынужден прекращать свою работу. Синтаксический анализ производится на основе формального определения синтаксиса языка, на котором написан текст програм- мы. Нередко транслятор имеет в своем составе закодированные син- таксические формулы языка, и пытается «подогнать» под них ис- ходный текст так, чтобы он в конечном счете оказался значением понятия (программа > в этом языке. Не всякий синтаксис обеспечи- вает однозначный анализ текста. Например, при изучении алгола мы видели, что если бы в определении условного оператора между символами then и else допускался условный оператор, то анализ конструкции вида if Bl then if B2 then SI else S2 был бы неоднозначным. Кроме того, даже один и тот же язык можно описывать многими разными способами, одни из которых будут допускать быстрый син- таксический анализ, а другие — требовать перебора многих ва- риантов. Особенно удобно, когда по первому символу ясно, какое понятие он открывает. В алголе, например, встретив символ value, можно быть уверенным, что начался список значений. Однако по символу нельзя сказать, последует ли за ним выражение или но- вый элемент списка левой части оператора присваивания. Изучением подобного рода вопросов занимается одно из направле- ний теории программирования — теория формальных грамматик. Генерация объектной программы. На этом этапе произво- дится непосредственная трансляция исходной программы на объектный язык (язык загрузки или машинный язык), во время которой по текстам операторов на исходном языке генерируются (вырабатываются) соответствующие им последовательности команд, представленных на объектном языке. Если исходный язык программирования является машинно- ориентированным языком (типа языка символического программи- рования), то эта часть транслятора сравнительно проста. Однако чем выше уровень исходного языка, тем сложнее генерирующая часть
ел] ТРАНСЛЯТОРЫ 377 транслятора. Во-первых, укрупняются допустимые в нем операто- ры и усложняются допустимые операции, что ведет к увеличению соответствующих им последовательностей команд,, которые к тому же строятся по более сложным правилам. Во-вторых, языки высо- кого уровня позволяют оперировать со сложными структурами дан- ных, так что расшифровка, например, такого простого по записи опе- ратора, какх:=у, может быть весьма сложной, если х и у есть эле-' менты каких-либо сложных структур (например, таблиц). В некоторых трансляторах генерация команд производится по мере выделения синтаксических понятий, поэтому в таких трансля- торах этапы синтаксического анализа и генерации все время чере- дуются. Как уже отмечалось, при решении иа ЭВМ задач с большим, объемом вычислений очень важным показателем программы являет- ся ее быстродействие, а иногда — и требуемый объем памяти. В свя- зи с этим в ряде трансляторов предусматривается специальный этап их работы, во время которого производится оптимизация вырабо- танной объектной программы (простейшие методы такой оптимиза- ции были рассмотрены в главе 5). Оформление и выдача объектной программы. Это заклю- чительный этап работы транслятора. В соответствии с указа- ниями пользователя объектная программа (загрузочный модуль или машинная программа) может выводиться на перфокарты, записываться в архив системы, выдаваться на печать. По указанию пользователя на печать может выдаваться та или иная дополнительная информация, вырабатываемая транслятором. На- пример, на печать может выдаваться «билйстинг» (распечатка, в которой рядом с каждым оператором исходного языка печатается соответствующая ему последовательность команд объектного языка, выработанная транслятором), таблица идентификаторов, распреде- ление памяти, паспорт модуля и т. д. К трансляторам могут предъявляться, вообще говоря, различ- ные требования в зависимости как от категорий пользователей, так и от характера решаемых задач. Например, для лиц, начинающих осваивать данный язык программирования, наиболее важной ха- рактеристикой транслятора является полнота и простота понимания диагностических сообщений транслятора, что позволяет быстро отыскивать место и определять характер допущенных синтаксиче- ских ошибок, тогда как пользователь с большим опытом работы с данным языком предпочтет лаконичность таких сообщений. Если на машине решается большое число сравнительно мелких задач с. не- большим временем счета, то важное значение имеет скорость рабо- ты самого транслятора, а качество получаемых им программ менее существенно. Для сложных же задач с большим временем счета наи- более важное значение имеет оптимальность вырабатываемых тран- слятором программ в отношении требуемого машинного времени и
378 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. ft объема памяти для их выполнения^ Получение таких оптимальных программ, естественно, требует усложнения алгоритмов трансляции, что приводит к усложнению транслятора и увеличению времени его работы, однако для таких задач эти дополнительные расходы вполне оправданы, ибо они затем многократно окупятся за счет сокращения затрат машинного времени на выполнение оттранслированных про* грамм. В связи с этим в одной системе программирования может предусматриваться несколько различных трансляторов даже с од* ного и того же языка программирования, так что каждый пользова* тель может выбирать наиболее подходящий для него транслятор. Во многих трансляторах предусматриваются различные режимы их работы, позволяющие пользователю управлять объемом диагности- ческой и дополнительных выдач транслятора, качеством и характе- ристиками изготовляемой программы и т. д. 6.4. Отладчики программ 6.4.1. Отладка программы. Непосредственное решение задачи на ЭВМ начинается только после того, как в оперативной памяти машины будет получена соот- ветствующая машинная программа и ЭВМ начнет ее выполнение. Од- нако, как мы знаем, до этого момента задача должна пройти целый ряд этапов: а) Выбор метода ее решения. б) Разработка общей структуры алгоритма и его представление в виде схемы или блок-схемы (для несложных алгоритмов, общая структура которых достаточно очевидна, этот этап может и отсутст- вовать). в) Расчленение — в случае необходимости — общей задачи на составные части (для которых, возможно, снова выполняется п. б)) и составление необходимых модулей пользователя на подходящем язы- ке (языках) программирования. г) Перфорация полученных модулей пользователя для их после- дующей обработки на ЭВМ. д) Трансляция модулей пользователя на язык загрузки, для чего исходные модули должны вводиться в память машины. е) Загрузка модулей с целью получения рабочей программы и ее выполнения машиной. При этом на каждом из этапов, выполняемых человеком, имеет- ся так много возможностей допустить те или иные ошибки, что со- вершенно избежать их практически невозможно, особенно в случае сложных программ. Поэтому каждая вновь составленная програм- ма — до того, как она будет использоваться для решения конкрет- ной задачи — должна пройти специальный этап, называемый от- ладкой. Цель отладки состоит в том, чтобы выявить и устранить, допущенные ранее ошибки и в конечном счете прийти к обое-
«.«] • ОТЛАДЧИКИ,ПРОГРАММ 379 нованному заключению о правильности программы. В даль- нейшем мы не будем останавливаться на ошибках, связанных с этапом а) прохождения задачи, поскольку вопросы, касаю- щиеся выбора подходящего численного метода и обоснования его применимости в конкретной задаче, являются содержанием отдельного предмета «Методы вычислений». Ошибки, возникаю- щие на последних двух этапах, тоже не являются типичными: поскольку естественно предположить, что в самих трансляторах, а также в загрузчике ошибок нет, то остается возможность либо неправильного задания информации при обращении к этим компонентам системы программирования (что, как правило, об- наруживается самой системой, и пользователю выдаются соответ- ствующие диагностические сообщения), либо неправильного ввода данных с перфокарт, что случается достаточно редко. Так что наи- более типичными и часто встречающимися являются ошибки, со- держащиеся в модулях пользователя — они могут быть допущены либо самим пользователем, либо появиться в результате ошибок при перфорации. Ошибки в модуле пользователя можно разбить на два класса: синтаксические (появление в тексте модуля конструкций, не до- пускаемых синтаксисом используемого языка программирования) и содержательное (неправильно расставлены скобки в арифмети- ческом выражении, сформулировано ошибочное условие разветв- ления вычислительного процесса, неправильно задано число повто- рений цикла и т. д.), которые фактически приводят к тому, что в программе реализован какой-то другой алгоритм, а не тот, который имел в виду ее автор. Что касается синтаксических ошибок, то их выявление осуществ- ляется достаточно просто: любой транслятор на этапе синтаксиче- ского анализа текста, предъявленного ему для трансляции, выявля- ет подобного рода ошибки и выдает диагностические сообщения, в которых указывается место обнаруженной ошибки в тексте и ее ха- рактер. Получив диагностическую распечатку от транслятора, поль- зователь должен внести в текст программы соответствующие изме- нения и повторить трансляцию. Этот процесс продолжается до тех пор, пока при очередной обработке программы транслятор уже не выдаст диагностических сообщений об ошибках. Значительно сложнее обстоит дело с выявлением содержатель- ных ошибок, поскольку для их обнаружения нет какого-либо фор- мального аппарата. Развиваемые в настоящее время методы верифи- кации, т. е. формального доказательства правильности программ, встречают две серьезные трудности. Во-первых, как сформулировать, что такое «правильная программа»? Ведь строгая формулировка того, что должна делать программа, т. е. задание на программирова- ние, подчас ничуть не проще, чем сама программа. Таким образом, программисту пришлось бы вместо одной программы составлять две
380 СИСТЕМЫ ПРОГРАММИРОВАНИЯ (Гл. » эквивалентные программы, но на разных языках. Во-вторых, даже после этого установление эквивалентности формального задания на программирование и составленной программы оказывается чрез- вычайно сложной и трудоемкой задачей, которая в. общем случае алгоритмически неразрешима. Поиски в этом направлении продол- жаются и, вероятно, будут вестись все более интенсивно из-за чрез- вычайно большой практической значимости этой проблемы, однако на современном этапе для контроля правильности программ наи- более широко применяется метод тестов. Суть этого метода состоит в том, что по составленной программе на машине решается некоторый набор вариантов задачи с заранее известными ответами (эти варианты и называются тестами), и по- лученные результаты сравниваются с этими известными ответами. Несовпадение их между собой и свидетельствует о наличии ошибок в программе (в предположении, что в тестах ошибок нет). Следует обратить внимание на то, что характерной особенностью большинства реальных алгоритмов является их широкая развет- вляемость, так что для полной проверки программы необходимо проверить каждую из предусмотренных в программе ветвей. Меж- ду тем каждый тест обычно обеспечивает прохождение только по не- которым из этих ветвей. Так что успешное прохождение какого-либо одного теста, как правило, еще не свидетельствует об отсутствии в программе ошибок. Весьма возможно, что при этом тесте либо просто не выполнялись те ветви программы, в которых имеются ошибки, либо специфика теста была такова, что некоторые ошибки в программе не сказались на полученных результатах. Так, если в программе реализуются вычисления по формуле вида y=a-F(x)+2, то очевидно, что при исходном значении а=0 на значение у не будут влиять ошибки, допущенные при программировании выражения F(x). Поэтому для полной проверки программы необходим и пол- ный набор тестов. Подготовка такого полного набора тестов для сложных по своей структуре программ сама по себе может являться очень сложной проблемой, поэтому степень приближения к полноте проверки программы обычно определяется степенью ответственности за достоверность получаемых с ее помощью результатов. Анализ только окончательных результатов, получаемых про- граммой, обычно позволяет лишь сделать вывод о наличии в про- грамме каких-либо ошибок — как правило, без указания на то, где именно эти ошибки допущены и в чем они заключаются. Труд- ность отладки как раз и состоит в том, чтобы локализовать каждую из ошибок, т. е. по возможности сузить ту часть программы, про которую можно сказать, что именно она содержит ошибку. Если такая часть (группа машинных команд, оператор или его часть на языке программирования) выделена, то обычно не составляет большого труда внимательно ее проанализировать и понять, в чем состоит ошибка.
6.41 ОТЛАДЧИКИ ПРОГРАММ 38J Задача локализации ошибки значительно упрощается, если из- вестен ее характер. Так, если установлено, что какой-либо цикл в программе выполняется не такое число раз, которое должно быть, то ясно, что ошибку прежде всего следует искать среди команд, уп- равляющих числом повторений этого цикла. Для установления же характера ошибки приходится прибегать к анализу некоторых про- межуточных результатов выполнения программы. Чтобы получить исчерпывающую информацию о ходе выполнения программы, сле- довало бы после выполнения каждой команды выводить на печать ее адрес, вид самой команды, текущее содержимое индексных ре- гистров, значения аргументе», полученный результат и признак <о — по этой информации затем можно было бы шаг за шагом про- следить выполнение программы, проверить правильность выполне- ния каждой команды и тем самым обнаружить команды, выполне- ние которых впервые привело не к тем результатам, которые ожи- дались. Однако вывод такого объема информации потребовал бы слишком больших затрат машинного времени и бумаги, а чрезмер- ное обилие информации только затруднило бы ее анализ. Например, в процессе выполнения программы при умножении двух квадратных матриц 10-го порядка была бы выведена информация, относящаяся по меньшей мере к двум тысячам выполнений команд, по которым непосредственно вычисляются суммы вида ю («'. j = l. 2...10), k = \ не считая команд, предназначенных для управления переменными адресами и числом повторений трех вложенных друг в друга циклов. Для уменьшения объема информации, выводимой из машины при отладке, в программе выделяются некоторые характерные точки (конец вычислений по некоторой формуле или группе формул, начало или конец цикла и т. д.), которые разбивают весь процесс вычислений на отдельные этапы, и по достижении каждой из этих точек выводится та информация, на основании которой можно сделать определенные выводы о правильности выполнения очеред- ного этапа, а при наличии ошибок на этом этапе — определить их характер и местоположение. Так, если некоторый участок программы реализует вычисления по формулам, то в начале этого участка целесообразно вывести на печать значения исходных данных для этого участка, а по его окон- чании вывести на печать как окончательные, так и промежуточные его результаты, которые сохранились к этому времени в памяти (т. е. содержимое рабочих ячеек, используемых на этом этапе вы- числений). По окончательным результатам можно судить о правиль- ности данного участка программы, а анализ промежуточных ре- зультатов может помочь установить характер ошибок и тем самым.
382 A СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл 6 их место в программе. При этом, конечно, существенное значение имеет то, насколько удачно выбраны и узловые точки, и дополни- тельная информация, выводимая по их достижении. Из-за большого разнообразия составляемых программ, каждая из которых имеет свою специфику, довольно трудно сформулировать какие-то единые правила ведения отладки. Однако практика выра- ботала определенную методику, которой обычно придерживаются программисты при выполнении этой работы. Прежде всего, если общая программа разбивалась на модули, то отладку программы естественно начинать тоже с отладки отдельных модулей; после того, как все модули программы будут проверены и в случае необходимости исправлены, надо будет лишь проверить пра- вильность их объединения в единую программу. Аргументы в поль- зу такого подхода те же самые, которые привели и к составлению программы в виде отдельных ее модулей. Таким образом, дальше мы по сути дела будем говорить об отладке модулей. При этом отлажи- ваемый модуль часто бывает необходимо дополнить простейшими моделями взаимодействующих с ним модулей — с тем, чтобы можно было получить программу, которую ЭВМ способна выполнить — lb дальнейшем мы и будем иметь в виду отладку такой программы. Отладку программы обычно начинают с проверки ее линейных участков (не содержащих разветвлений и циклов), по которым реа- лизуются вычисления по формулам или другие простейшие преоб- разования данных — как правило, на эти участки приходится по- давляющая доля всех команд программы. Ясно, что пока не прове- рены эти участки, очень мало шансов получить правильные конеч- ные результаты. Выделение проверки линейных участков в один или несколько самостоятельных этапов отладки целесообразно и с той точки зрения, что для каждого линейного участка в отдельности гораздо проще подобрать сравнительно полный набор тестов, чем для всей программы в целом. При обнаружении каких-либо ошибок в программу необходимо внести соответствующие исправления и повторить проверку тех участков, в которые вносились изменения — с тем, чтобы убедиться в правильности этих изменений и удостовериться, что в этих участ- ках нет иных ошибок. После того, как линейные участки отлажены, целесообразно пе- рейти к проверке простейших циклов и локальных разветвлений, обращая особое внимание на проверку фактического числа повторе- ний циклов (т. е. по сути дела, опять на полноту набора тестов). Заключительным этапом отладки является проверка общей ло- гики программы, т. е. проверка предусмотренных в ней разветвле- ний и взаимодействия отдельных ее частей (например, обращений ж подпрограммам). Для этого этапа при подготовке набора тестов надо позаботиться о том, чтобы этот набор обеспечивал прохожде- ние по всем ветвям вычислений, предусмотренным в программе, а в
6.4] ОТЛАДЧИКИ ПРОГРАММ 383 процессе отладки надо убедиться в том, что каждый раз вычисле- ния идут по нужной ветви. Таким образом, обычный процесс отладки программ противополо- жен обычному методу их разработки — он проводится «снизу вверх». Следует подчеркнуть, что отладка является наиболее ответст- венным этапом прохождения задачи, поскольку этот этап является последним, после которого изготовленная программа передается для ее.использования с целью решения конкретных задач. С другой стороны, из-за трудности формализации ее проведения, отладка представляет весьма сложную и трудоемкую работу, которая тре- бует известного опыта и на выполнение которой нередко затрачива- ется не меньше времени, чем на разработку и составление програм- мы. В связи с этим многие считают, что показателем квалификации' программиста является не столько умение писать программы, сколь- ко умение их отлаживать. Для успешного ведения отладки программы (особенно достаточ- но сложной) важное значение имеет планомерность в выполнении этой работы. Точно так же, как перед написанием такой программы важно иметь ее схему, по которой проще проверить правильность общей структуры выбранного алгоритма, перед началом отладки важно иметь и ее «схему», т. е. план, который определяет отдельные этапы отладки и последовательность их выполнения. Наличие тако- го плана позволяет проверить полноту намечаемой отладки и упро- щает дальнейшее ее проведение. Этот план, и в частности — число выделяемых этапов, конечно, зависит и от сложности отлаживаемой программы, и от квалификации программиста. При этом приходит- ся думать и о сроках проведения отладки, и о затратах для этой цели машинного времени. Однако следует иметь в виду, что чем сложнее очередной выделенный этап отладки, тем труднее будет его реализо- вать. Поэтому при отсутствии достаточного опыта работы следует всю отладку разбивать на достаточно простые этапы, чтобы избе- жать трудностей в их реализации. Во всяком случае, не следует стремиться всю отладку провести за один сеанс работы на ЭВМ — такая попытка весьма редко увенчивается успехом; обычно это приводит к бесполезным затратам машинного времени и к появле1- нию у программиста растерянности в отношении того, что и как. делать дальше. . Реализация каждого из запланированных этапов отладки тре- бует одного или нескольких «выходов» на машину (т. е. сеансов ра- боты на ней). Как уже отмечалось, отладка программы с помощью ЭВМ состоит в том, чтобы заставить машину выполнить эту про- грамму и в некоторых точках программы выполнить вспомогатель- ные (отладочные) действия: вывести на печать промежуточные ре- зультаты, задать желаемые исходные данные для очередной части программы, изменить предусмотренный в программе порядок выпол- нения команд и т.д.
384 СИСТЕМЫ ПРОГРАММИРОВАНИЯ (Гл • Таким образом, перед каждым выходом на машину для отладки программист должен наметить: а) когда (по достижении каких точек программы) нужно выпол- нить те или иные вспомогательные действия, не предусмотренные в программе; б) что именно (какие вспомогательные действия) надо выпол- нить по достижении каждой из намеченных точек. После того, как очередной такой план отладки намечен, возни- кает задача его реализации. Один из возможных способов состоит в том, что в нужные места исходного текста программы вставляются дополнительные опера- торы, выполняющие необходимые выдачи на печать и другие вспо- могательные действия. Чтобы избежать слишком частых измене- ний текста программы от одного выхода на машину к другому, а следовательно и повторных ее трансляций, обычно такие отладоч- ные операторы стараются вставить сразу во все точки программы, где при каких-либо обстоятельствах могут понадобиться отладоч- ные действия. А для того, чтобы все эти действия не выполнялись каждый раз и, в частности, чтобы сократить объем получаемых вы- дач на печать, эти операторы снабжаются условиями применимости. Типичным приемом управления отладочными операторами яв- ляется введение в программу одной или нескольких вспомогатель- ных переменных, называемых ключами отладки, которые и исполь- зуются в условиях применимости. Придавая этим ключам различ- ные значения (например, с помощью операторов ввода с перфокарт), можно заставить выполняться разные группы отладочных операто- ров. Например, при отладке алгол-программы вспомогательный оператор вывода промежуточных результатов может иметь вид if W=1 then ВЫВОД (а, b, с) где целочисленная переменная Ы является ключом отладки. По этому оператору вывод будет произведен только в том случае, если ' ключу отладки было присвоено значение, равное единице. Включе- ние в текст программы отладочных операторов может производить- ся й с помощью специальных служебных программ — редакторов программных текстов. Такие программы, также входящие в состав систем программирования, будут рассмотрены более подробно в последующих разделах данной главы. У изложенного выше способа есть два существенных недостатка. Во-первых, практически невозможно заранее предусмотреть все те точки программы, в которых по ходу отладки может понадобить- ся выполнять те или иные вспомогательные действия, и какие имен- но действия понадобятся в этих точках, поскольку очередной этап отладки часто зависит от результатов предыдущего этапа. Поэтому *в процессе отладки, как правило, все же приходится неоднократно перетранслировать программу. Во-вторых, поскольку отладочные
«.41 ОТЛАДЧИКИ прЬграмм 385 операторы сами вносят некоторые изменения в процесс вычисле- ний, то после их изъятия (или отключения) по окончании отладки программа может получать уже другие результаты, т. е. стать не- правильной! Поэтому большой популярностью у программистов пользуется способ получения промежуточных выдач в контрольных точках без каких-либо изменений в тексте отлаживаемой програм- мы. Для этого в системы программирования обычно включаются специальные обслуживающие программы — отладчики. Для общения программиста с отладчиком создается специальный язык (язык отладки), на котором программист в удобном для него виде . формулирует свое задание на отладку, т. е. записывает своего рода «программу» отладки. Отладчик, который умеет понимать фразы этого языка, выполняет это задание с помощью ЭВМ. В следующем разделе мы рассмотрим принципы работы таких отладчиков. А еще с одним способом отладки без изменения текста программы, основан- ном на дополнительных возможностях аппаратуры ЭВМ, мы позна- комимся при рассмотрении режима разделения времени. 6.4.2. Реализация отладчика. Прокрутка. Итак, при пользовании отладчиком программист на языке от- ладки формулирует ему свое задание на отладку, в котором задают- ся контрольные точки в отлаживаемой программе и относящиеся к ним отладочные действия. Задача отладчика состоит в выполне- ~ нии этого задания на отладку. Для этого отладчик должен обеспе- чить выполнение машиной команд отлаживаемой программы, но при этом он должен следить за прохождением контрольных точек: если очередная выполненная (или подлежащая выполнению) ко- манда является одной из таких точек, то отладчик должен прервать выполнение отлаживаемой программы и выполнить вспомогатель- ную последовательность команд, с помощью которых реализуется отладочное действие, заданное для этой контрольной точки. Отладчик состоит из двух основных частей. Первая — это про- грамма, получившая название прокрутка. Эта программа по сути дела моделирует работу процессора машины — так, как это было формально описано в главе 4. Прокрутка по очереди выбирает и вы- полняет команды отлаживаемой программы, но после выполнения каждой из них осуществляет переход на другую часть отладчика — программу обработки. Эта программа выясняет, является ли вы- полненная команда отлаживаемой программы одной из контроль- ных точек, указанных в задании на отладку, и если да, то обеспечи- вает выполнение отладочного действия, заданного для этой точки, после чего осуществляет возврат на прокрутку. Прокрутка присту- пает к интерпретации следующей команды отлаживаемой програм- мы и т. д. Таким образом, назначение прокрутки состоит в последователь- ной интерпретации команд отлаживаемой программы, а назначение 13 Э. 3. Любинский др.
386 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл 6 обработки — в интерпретации (в случае необходимости) команд отладки, содержащихся в задании на отладку. Заметим, что про- грамма прокрутки — это в известном смысле универсальная ма- шина фон-Неймана (аналог универсальной машины Тьюринга), по- этому подробнее рассмотрение этой программы полезно и для бо- лее полного понимания логики работы самой ЭВМ. Чтобы не путать обозначений регистров реального процессора с обозначениями рабочих ячеек программы прокрутки, в которых будут храниться соответствующие значения для моделируемого процессора, и вместе с тем облегчить понимание программы про- крутки, мы дадим этим ячейкам те же имена, которые были даны ре- гистрам процессора, но приписав к ним в начале букву М. Посколь- ку разрядность регистров не всегда совпадает с разрядностью ячеек памяти, то в случае необходимости будем уточнять, в каком именно поле машинного слова будет храниться эта информация: МС: адрес очередной команды отлаживаемой программы (в поле Л2); МК: выполняемая команда отлаживаемой программы; MF: содержимое индексного регистра (в поле Л2); MW: значение признака со (значение true изображается циф- рой 1, а значение false — цифрой 0 в младшем разряде по- ля А2); МАХИ: первый исполнительный адрес (в поле Л2); МА2И: второй исполнительный адрес (в поле Л2); МАЗИ: третий исполнительный адрес (в поле Л2); MRX: первый аргумент операции; MR2: второй аргумент операции; MS: результат выполнения операции; MQ: код операции выполняемой операции (в поле КОП). Будем считать, что перечисленным здесь именам соответствуют ячейки памяти с идущими подряд адресами. Работа прокрутки в соответствии с тактом машины УМИРЗ- распадается на следующие этапы: 1°. Выбор в МК команды по адресу, содержащемуся в МС: МС: = Ж+1. 2°. Выделение кода операции выполняемой команды и его за- поминание в Мб, а также вычисление исполнительных адресов этой команды и их запоминание в ячейках МАХИ, МА2И, МАЗИ. 3°. Выбор аргументов в ячейки MRX и MR2. 4’. Формирование и выполнение команды вида (Мв MRX MR2 MS) с запоминанием выработанного значения ® в ячейке MW. 5*. Запись результата MS в ячейку с адресом, зафиксирован- ным в МАЗИ. 6°. Обращение к программе обработки с возвратом к п. 1°.
в.4] ОТЛАДЧИКИ ПРОГРАММ -387 Здесь описана схема работы прокрутки, соответствующая стан- дартному такту работы машины; при выполнении некоторых машин- ных операций эта схема несколько видоизменяется. Работа отладчика продолжается до тех пор, пока выполнение п. 4° не повлечет за собой прекращение выпрлнения отлаживаемой программы (выполнение команды останова или выработка признака <р=1 при выполнении очередной команды). Если среди отладочных действий допускается останов, то работа отладчика может быть пре- кращена и в программе обработки. Рис. 6.5. Разобьем все машинные операции на три типа: 1) Стандартные (7?1*/?2=>S, где * — операция с кодом 0). 2) Стандартные, но в которых первым аргументом является не- посредственно исполнительный адрес АХИ (а не содержимое ячей- ки с этим адресом, т. е. не величина ОП[АХИ]). 3) Нестандартные. Если обозначить через СТ, СТА и KQ блоки, предназначенные для выполнения соответственно стандартных операций первого типа, стандартных операций второго типа и каждой нестандартной опе- рации с кодом 0, то блок-схема прокрутки может иметь вид, при- веденный на рис. 6.5. Здесь используются следующие дополни- тельные фигуры: кружочки — для внутренних меток отдельных блоков, маленькие ромбики—для внешних меток, треугольники — для входных и выходных точек. Внутренние метки помогают также избежать длинных соединительных линий. 13»
388 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. в Получение в ячейках МА1И, МА2И и МАЗИ каждого из трех исполнительных адресов команды, выбранной в ячейку МК (что входит в задачу блока ИА), будем делать в цикле, по одному и тому же правилу, так что алгоритм этой части блока ИА можно не- формально описать следующим образом: 1°. 0; R:—MK (пересылка команды в рабочую ячейку). 2°. Сдвиг R вправо на 12 разрядов (поскольку каждый исполни- тельный адрес должен получаться в поле 42 соответствующей ячей- ки) и запись результата в ячейку МА1И+1. 3е. Если Л/+1=1, то прибавление значения MF к полю 42 содер- жимого ячейки М41Я~Н. 4°. Очистка всех полей ячейки Af41H+i кроме поля 42. 5°. Сдвиг R влево на 12 разрядов; 6°. При i<3 переход к п. 26. Для выделения признаков модификации в п. 3° запасем три кон- станты: Д+2: 400 0000 0000 0000; Д+3: 200 0000 0000 0000; Д+4: 100 0000 0000 0000; так что проверка признака nl+i будет реализоваться логическим ум- ножением содержимого ячеек МК и Д+2+i. Разветвление вычислительного процесса блоком ДО осуществим с помощью переключателя. Для его реализации запасем таблицу констант вида е оооо ме оооо где 0 — код операции интерпретируемой команды, а Мв— началь- ный адрес группы команд, обеспечивающей выполнение операции с кодом 0. В этой таблице констант будут фигурировать все коды опе- раций, за исключением операций типа СТ (порядок следования ко- дов операций в таблице безразличен). С использованием символичес- ких обозначений эта таблица запишется так: Д+ 6: СДМА — СТА — Д+ 7: СДСА - СТА — Д+ 8: ПБ - К56 — Д+ 9: ПЕ - К36 — Д+Ю: ПУ - К76 — Д+П: ПВ - К16 — Д+12: РА - К52 — Д+13: PC - К72 — Д+14: ПМ — Д12 — Д+15: ПН - Д32 — Будем считать, что строки этой таблицы запасены в программе про- крутки в качестве констант.
6.4] ОТЛАДЧИКИ ПРОГРАММ 389 После того, как в интерпретируемой команде будет вцделен код операции 9, в приведенной выше таблице осуществляется поиск строки, содержащей этот код в поле КОП. Если такая строка будёт найдена, то осуществляется переход по адресу, указанному в поле А2 этой строки; в противном случае осуществляется переход к бло- ку СТ. При составлении символической программы прокрутки будём исходить из того, что к началу ее работы в ячейке МС зафиксирует ван адрес первой команды отлаживаемой программы (которая вве- дена в память на отведенное для нее место) и что кроме указанных выше запасены еще следующие константы: Д : ООО 0000 0001 0000; Д +1 : 077 0000 0000 0000; Д+5 : 000 0000 7777 0000; Д+16: 000 MR\ MR2 MS', Д+17: 000 0000 MR2 MS; Д+18: ПВ 0000 0000 0000; Д+19: РА 0000 0000 0000; При этих предположениях программа прокрутки может быть за- писана так: ПР: PC — МС Выбор в МК очеред- П 0() — мк ; ной команды СМ МС д МС ; МС:=МС+\ ИА: И МК Д4-1 MQ ; Выделение К0П(МК) в MQ РА — — i:=0 П мк — R ; L: СДСА — 12 R МАШ(); Сдвиг R вправо на 12 разрядов И МК Д+2() ш:=л;+1=0 ПЕ — L1 • СМ МА1И( ) MF МА\И(); Получение МАШ, МА2И, МАЗИ И МАШ() Д+5 МА1И(); L1: СДСА 12 R R ; Сдвиг R на 12 раз- рядов влево ПМ 2 L Ц) ; Повторение цикла три раза ВА: PC — МАШ > Выбор первого аргу- П о() — МД1 ; мента PC МА2И » Выбор второго аргу- П 0() — MR2 ; мента
390 * СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. в ДО: РА — — — F:=0 L2: И Д+б() д+1 R • Н ме R ©: = КОЩД + 6 + F) =M0 ПУ —— L3 — PC — д+б() — Переход no ПБ 0() — Л2(Д + 6 + О L3: ПМ 9 L2 1() > Повторение цикла 10 раз СТ: ИЛИ МО Д +16 СТ 4-1 [СТ 4- 1]: = (М0 MRX MR2 MS) ООО — — Выполнение стандарт- ной операции ПЕ д ЗАП MW Запоминание значе- ПБ — ЗАП MW ния со в MW и пере- ход на ЗАП СТА: СДСА 12 МАХИ R ИЛИ Д+17 R R 7?:=(000 МАХИ MR2 MS) ИЛИ МО R CT+X [СТ+11:=(М0 МАХИ MR2 MS) ПБ — ст + х — Переход на СТ + 1 К.56: П МД1 — MS MS:=MRX ПБ МА2И ЗАП мс ♦ МС:=МА2И', пере- ход на ЗАП Л36: Н MW д — w.=MW = X ПУ MRX ЗАП MS > MS:—MRX: при = false переход на ЗАП ПБ МА2И ЗАП мс МС:—МА2И’, пере- ход на ЗАП Д76: Н MW Д — a-.=MW = X ПЕ MRX ЗАП MS > MS:—MRX\ при со = true переход на ЗАП ПБ МА2И ЗАП мс > МС:—МА2И: пере- ход на ЗАП Л16: ИЛИ Д+18 МАХИ MS MS: = (П В—МАХИ—) ПБ МА2И ЗАП мс t МС:=МА2И: пере- ход на ЗАП К52: ИЛИ Д + 19 МАХИ MS ♦ MS:= (РА—МАХИ — ) ПБ МА2И ЗАП MF ♦ МР:=МА2И\наЗАП
6.4] ОТЛАДЧИК^ ПРОГРАММ 391 К72: ИЛИ И ПБ Д+19 MR2 МАХИ Д + 5 ЗАП MS ; MF ; » MS:= (РА—МАХИ —) MF:=A2(MR2) Переход на ЗАП КХ2: ВМ MF МАХИ — &:=~\(MF"^MAXH) ПУ МАЗИ ОБР MF ; MF:=MA3H', при о» = false переход на ОБР ПБ МА2И ОБР МС ; МС:=МА2И; пере- ход на ОБР К32: ВМ MF МАХИ — = ~\(MF^MAXH) ПЕ МАЗИ ОБР MF ; MF: = МАЗИ-, при со == true на ОБ Р ПБ МА2И ОБР МС ; МС: = МА2И-, пере- ход на ОБР ЗАП- PC — МАЗИ — F: = MA3H П MS — 0() ; Запись результата по МАЗИ ПБ — ОБР » Переход на обработку Программа обработки по окончании своей работы осуществляет переход на начало прокрутки — на команду с меткой ПР. Проследим за выполнением прокрутки на конкретном примере. Пусть к моменту выполнения первой ее команды имеется следующее содержимое ее рабочих ячеек: МС: ООО 0000 2300 0000; Значение моделируемого счетчика команд MF: 000 0000 0002 0000; Значение моделируемого индексного регистра. Пусть по адресу 2300 хранится слово 2300: 154 0014 0100 0200; (команда сдвига слова по адресу) а по адресам 0014 и 0100 хранятся слова 0100: 305 1111 2222 3333; 0014:101 4000 0000 0000; В результате выполнения блока ПР будет получено МК: 154 0014 0100 0200; МС: 000 0000 2301 0000; Далее блок ИА сформирует следующее содержимое ячеек: /И0: 054 0000 0000 0000; ММИ: 000 0000 0014 0000;
392 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. в МА2И: 000 0000 0100 0000; МАЗИ'. ООО 0000 0202 0000; Выполняющийся затем блок ВА сформирует содержимое ячеек: Л4/?1: 101 4000 0000 0000; (этот аргумент использоваться не будет) MR2: 305 1111 2222 3333; Блок ДО в таблице констант, реализующих переключатель, найдет строку Д+7, в которой содержится код операции 54 (СДСА), и поэтому осуществит переход по метке СТА, указанной в этой стро- . ке. Блок СТА в ячейке СТ4-1 сформирует команду 054 12 MR2 MS', (команда сдвига слова по адресу) и обеспечит ее выполнение машиной, так что блок СТ, начиная ра- боту с команды СТ 4-1, сформирует в итоге содержимое ячеек: MS: 111 2222 3333 0000: (результат команды сдвига) MW: 000 0000 0000 0000; (значение ®=false) и осуществит переход на блок ЗАП. Этот блок в ячейку 0202, адрес которой зафиксирован в МАЗИ, запишет слово 0202: 111 2222 3333 0000; (результат выполнения интерпретируемой команды) и осуществит переход на обработку. Когда обработка, завершив свою работу, пе- рейдет на метку ПР, прокрутка начнет интерпретацию команды, адрес которой (2301) теперь зафиксирован в ячейке МС. Как видно, программа обработки имеет исчерпывающую ин- формацию о только что выполненной команде отлаживаемой про- граммы (эта информация хранится в рабочих ячейках прокрутки), поэтому при выполнении задания на отладку она может, в случае необходимости, выдать на печать любую информацию, относящую- ся к данной команде. В связи с тем, что программа обработки включается в работу пос- ле выполнения каждой команды отлаживаемой программы, имеется большая гибкость в задании контрольных точек в отлаживаемой программе, например: — адресом команды; — адресом, по которому выбирается один из аргументов (в этом случае фактически задается сразу некоторое множество контроль- ных точек); — адресом, по которому записывается результат; — текущим содержимым индексного регистра; — свойством, которым обладает тот или иной операнд и т. д. В каждом конкретном отладчике фиксируются как допустимые способы задания контрольных точек в отлаживаемой программе, так и допустимые отладочные действия, которые может реализовы-
6.5} РЕДАКТОРЫ ТЕКСТОВ ПРОГРАММ 393 вать программа обработки, а также синтаксис языка отладки, на котором формулируются все указания отладчику. Основным недостатком такого отладчика является существенное замедление выполнения отлаживаемой программы машиной (в де- сятки раз) в связи с тем, что выполнение каждой команды этой про- граммы сопровождается выполнением многих команд прокрутки и обработки. Кроме того, отладчик должен присутствовать в опе- ративной памяти наряду о отлаживаемой программой, что увеличи- вает объем требуемой памяти. С целью устранения этих недостатков делаются отладчики, прин- цип работы которых заключается в том, что они на основании ин- формации, содержащейся в задании на отладку, производят пред- варительную модификацию отлаживаемой программы, добавляя в нее команды, с помощью которых реализуются заданные отладоч- . ные действия. При таком способе замедления выполнения отлажи- ваемой программы фактически не происходит, но при этом сущест- венно ограничиваются возможности в задании контрольных точек. До сих пор мы имели в виду, что задание на отладку формули- руется главным образом в терминах адресов. Современные системы программирования позволяют для этих целей использовать те обо- значения (имена) объектов программы, которые фигурируют в мо- дуле пользователя, т. е. в исходном тексте программы на каком-либо языке программирования. Такая возможность обеспечивается За счет того, что трансляторы сохраняют исходные обозначения, так что вырабатываемые ими модули загрузки содержат в себе и ин- формацию об этих обозначениях, и о соответствии условных адре- сов этим обозначениям. Загрузчик также сохраняет эту информа- цию, которая и используется отладчиком для перехода от исходных обозначений к абсолютным адресам. Такая возможность существен- но упрощает работу программиста при ведении отладки. Заметим также, что отладчик, как правило, предварительно про- веряет правильность синтаксиса в задании на отладку. Задание, содержащее синтаксические ошибки, отладчик не выполняет, а ограничивается выдачей на печать соответствующих диагностичес- ких сообщений. 6.5. Редакторы текстов программ 6.5.1. Назначение редактора текстов. Отладка программы, как правило, связана с внесением в исход- ный ее текст, написанный на одном из языков программирования, различного рода изменений и исправлений. Конечно, эти изменения можно вносить непосредственно в колоду перфокарт, на которые на- несен текст программы: удалять из колоды отдельные перфокарты с ошибочными или ненужными частями текста и вставлять в нуж- ные места этой колоды другие перфокарты с исправленными или
.394 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. 6 пропущенными ранее частями текста. Однако такая работа с пер- фокартами очень неудобна, требует большого внимания и при ее выполнении нетрудно допустить ошибки. Современные системы программирования позволяют не вводить программу в память машины с перфокарт каждый раз, когда воз- никает необходимость работы с этой программой, а сделать это только однажды. Текст введенной программы записывается под заданным именем в архив системы, расположенный во внешней памяти машины, и при дальнейшей работе можно использовать этот текст: вносить в него изменения (т. е. производить его редакти- рование), использовать его для трансляции и т. д. Для упрощения и облегчения такого типичного вида работы с текстами программ, как их редактирование, в системе программи- рования обычно предусматривается специальная обслуживающая программа, называемая редактором программных текстов (или просто редактором). Этот редактор не следует путать с редактором межмодульных связей, о котором говорилось выше. Как и в случае с отладчиком, в системе программирования пре- дусматривается специальный язык общения пользователя с этим редактором. Этот язык дает возможность пользователю сформули- ровать свое задание на редактирование текста какой-либо програм- мы в удобном для него виде, а выполнение этого задания, т. е. не- посредственное редактирование текста, осуществляется машиной с помощью редактора. Редактирование текстов программ включает в себя: — определение объекта редактирования; — определение места редактирования; — собственное редактирование; — документирование процесса редактирования; — сохранение объекта редактирования. Определение объекта редактирования. Здесь речь идет о задании всего текста программы, подлежащего редактированию. Этот текст можно: а) Взять из архива. Как уже отмечалось, текст программы мож- но однажды записать в архив под определенным именем. Поэтому текст, подлежащий редактированию, можно задать путем указания его имени и, возможно, имени библиотеки, если система допускает наличие в архиве нескольких различных библиотек. б) Ввести с перфокарт. . в) Получить смешанным способом, при котором одни части тек- ста, подлежащего редактированию, берутся из архива, а другие вводятся с перфокарт. . Определение места редактирования. Здесь речь идет об указа- нии частей текста, подлежащих редактированию. На практике наи- более часто используются следующие способы указания места ре- дактирования.
6.5j РЕДАКТОРЫ ТЕКСТОВ ПРОГРАММ 395 а) Задание номера строки. Любой текст, предназначенный для ‘ ввода и хранения в машине, обычно разбивается на отдельные пор- ции, называемьй строками или записями. Строка обычно представ- ляет собой самостоятельную фразу языка (оператор, команда, кон- станта и т. д.), которая обычно и перфорируется на отдельной пер- фокарте, чтобы легче было устанавливать соответствие между стро- ками текста и перфокартами. Отдельные строки текста естественно перенумеровать, и тогда место редактирования удобно задавать с помощью номера строки. Поскольку при редактировании может меняться и число строк, и положение отдельных строк в тексте, тр по окончании своей работы редактор производит перенумерацию строк и выдает (по указанию пользователя) на печать отредакти- рованный текст с указанием новых номеров строк. При практическом использовании этого способа иногда возни- кают некоторые неудобства. Так, если нумерация строк сохраняется до конца редактирования, то нельзя вносить исправления в исправ- ления; если же нумерация строк меняется при каждой вставке (или удалении) строк, то трудно определять место последующих исправ- лений. Иногда этот способ приводит к громоздкому заданию на редактирование, если изменения затрагивают достаточно большое число строк. б) Контекстный способ. Задание номеров строк для указания мест редактирования достаточно удобно, если число таких мест достаточно мало и изменения затрагивают всю редактируемую стро- ку (например, замену этой строки на другую). Однако очевидно, что такой способ не очень удобен, если, например, во всей програм- ме нужно идентификатор х заменить на идентификатор xl, тогда как идентификатор х фигурирует во многих местах программы. В подобного рода случаях место редактирования удобнее задавать контекстами типа: первое вхождение х первая по порядку строка, начинающаяся с a[i] первая по порядку часть текста, находящаяся между последо- вательностями символов ai а2. .. а* и bi b2- . .Ьт и т. д. При этом могут допускаться модификации таких указаний, относящиеся ко всем вхождениям в программу данного контекста, так что указания на редактирование могут иметь вид: первое вхождение х заменить на у всюду в тексте d заменить на d 1 удалить первую по порядку строку, начинающуюся с М : Л:== все вхождения ЛЬ*] заменить на ЛИ,- /] и т. п. , ; в) Смешанный (позиционно-контекстный) способ. В этом случае место редактирования Грубо задается номером строки и уточняется контекстным способом, что позволяет достаточно удобно изменять
$96 v СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. в не всю строку, а только ее часть. Например, указания на редакти- рование могут иметь следующий смысл: в строке с номером 26 все вхождения А заменить на Л [Л все вхождения х между строками с номерами 17 и 25 заменить на t Собственное редактирование. Любое редактирование можно осу- ществить с использованием двух основных операций: ВСТАВИТЬ и УДАЛИТЬ, однако на практике часто используют и другие опера- ции, например ЗАМЕНИТЬ. В качестве параметров для этих опе- раций задается место редактирования, а к операциям ВСТАВИТЬ и ЗАМЕНИТЬ по определенным правилам задается дополнитель- ная информация о тех строках, которые надо вставить в указанное место или на которые надо заменить указанную часть текста, на- пример: УД 5 Удалить строку с номером 5 ЗАМ 22 Строку с номером 22 заменить X: = X 4- Н; А: = А 4- Р(Х); на следующие строки 'IF’XyB'THEN' ’GO TO'L\ 77777 Признак конца заменяющего текста ВСТ 29 После строки с номером 29 R'.=ABS{T)\ вставить следующие строки 'IF'R>QS)VTHEN"GO ТО'М-, 77777 Признак Конца вставляемого текста Для каждого конкретного редактора зафиксирован набор допу- стимых операций, которые могут использоваться для редактирова- ния, определен синтаксис и семантика языка, на котором формули- руется задание на редактирование, и правила указания любой до- полнительной информации, которая должна присутствовать в этом задании. Документирование. Мы уже говорили о том, что при изготовле- нии новой программы, особенно в процессе ее отладки, пользовате- лю, как правило, приходится неоднократно вносить изменения в ее текст. Поэтому пользователь постоянно должен знать текущее состояние текста своей программы, а в ряде случаев ему бывает важно вспомнить и динамику его изменения. В связи с этим редак- тор должен уметь выдавать пользователю отчет о проделанной ра- боте по редактированию текста. Обычно редактор выдает на пе- чать то задание на редактирование, которое он получил от пользо- вателя (что удобно для прослеживания динамики внесения измене- ний в программу), а также текст, полученный в результате очеред- ного редактирования, с новой нумерацией строк. Наличие такого документа облегчает дальнейшую работу пользователя с программой, а новая нумерация строк используется им при формулировке зада-
i.51 РЕДАКТОРЫ ТЕКСТОВ ПРОГРАММ 397 ния на очередное редактирование, если в этом возникает необхо- димость. Некоторые редакторы производят эту выдачу на печать всегда, а некоторые — по указаниям пользователя, содержащимся в зада- нии на редактирование. Кроме того, в наборе операций редактиро- вания обычно имеются операции, с помощью которых пользова- тель может в процессе редактирования вывести на печать любую интересующую его часть текста. Как и отладчик, редактор производит синтаксический контроль задания на редактирование и при обнаружении синтаксических ошибок выдает соответствующие диагностические сообщения. Сохранение объекта редактирования. Редактор должен уметь сохранять редактируемый текст для последующего его использова- ния (с целью его трансляции, последующего редактирования и т. д.). При этом пользователь имеет возможность давать редактору раз- личные указания о том, что делать с тем или иным текстом, на- пример: — записать отредактированный текст в архив под тем же име- нем, что и исходный текст (что означает удаление последнего из ар- хива); — записать отредактированный текст в архив под новым име- нем (что означает сохранение в архиве исходного текста); — ликвидировать в архиве текст с указанным именем; — образовать новый текст путем добавления одного-текста к другому с записью его в архив под заданным именем и т. д. 6.5.2. Пример работы с редактором текстов. Приведем пример работы с конкретным редактором текстов про- грамм на одной из отечественных машин — БЭСМ-6. Приводимое здесь задание на редактирование и распечатки редактора отличают- ся от реальных несущественными деталями—главным образом спо- собом представления некоторых символов на реальном печатающем устройстве. Использованный редактор в процессе редактирования текста сохраняет исходную нумерацию строк, а по окончании ре- дактирования производит их перенумерацию. Редактированию подвергается следующий текст алгол-програм- мы, который предварительно был записан под именем ТРАНС в библиотеку БИБОТЛ архива: 1) 'BEGIN* ’REAL* А, В, К, X, Р, У1, Y2; 2) 'PROCEDURE* F(X, Q; *VALUE'X\ ’REAL* X, С; 3) C:=COS X—КхХ; 4) Д:=0; В:=3.14; F(41, У1); F(B, У2); 5) Х:=0; 6) £1 : Х:=Х+0.1; *IF* /01.01 'THEN* 'GO Т(У М; 7) REP : Х:=(Д+В)/2; F(X, Р)-, 8) £: 'IF' Р=0 'THEN* 'GO ТО' FIN-,
398 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. Ь 9) 'IF' SIGN(Y1)=SIGN(P) 'THEN' 'BEGIN' Л:=Х; У1: = Р 'END' 10) 'ELSE' 'BEGIN' В.=Х\ Y2:=P 'END'- 11) 'IF' ABS(B—Л)>0.001 'THEN' 'GOTO' REP', Xt=(A+B)/2', 12) FIN: OUTPUT (1, X); OUTPUT (1, X); 'GOTO' LI; 13) M: 'END' В эту алгол-программу надо внести следующие изменения: — в строке 3) заменить COSX на COS (X) и после нее вставить строку: LI: INPUT (1, /Q; 'IF' /«0 'THEN' 'GO ТО' М- — в строке 4) заменить F(A 1, VI). на F(A, У1) — удалить строки с номерами 5) и 6). После указанного редактирования — с целью проверки внесе- ния изменений — надо вывести на печать первые шесть строк из- мененного текста программы, а измененную программу нужно за- писать под именем ТРАНС! в ту же библиотеку БИБОТЛ. Для выполнения этой работы составляется следующее задание для редактора (в использованном редакторе указывается число вставляемых строк, а не признак конца вставляемого текста): ЗАМ 3 2 C:=COS(X)—ХхХ; LI: INPUT (1, X); 'IF' К<0 'THEN' 'GO ТО' М-, (новые строки, на которые надо заменить строку с номером 3, ука- заны вслед за командой замены) ЗАМ 4 1 Л:=0; В: =3.14; F(A, У1); F(B, У2); ВЫ Б 5 2 (последняя команда означает: выбросить из текста две строки, на- чиная со строки с номером 5) ЗАП БИБОТЛ ТРАНС! (указание о том, что отредактированный текст надо записать в биб? лиотеку БИБОТЛ под именем ТРАНС!) ПЕЧ 1 6 (указание вывести на печать первые шесть строк текста) ВСЕ (признак конца действий, заданных редактору для исполнения; далее редактору даются указания, определяющие объект редакти- рования)/ ЧТЕ БИБОТЛ ТРАНС
61 СХЕМА СИСТЕМЫ ПРОГРАММИРОВАНИЯ 399 (подлежащий редактированию текст можно составить из несколь- ких частей: некоторые части можно взять из архива, а некоторые — задать непосредственно в задании на редактирование; здесь ска- зано, чтр из библиотеки БИБОТЛ надо прочесть текст с именем ТРАНС, а строка, состоящая из одной точки, является признаком конца формирования редактируемого текста; эта строка одновре- менно является и признаком конца задания на редактирование). По окончании своей работы редактор выдает на печать то зада- ние на редактирование, которое он выполнял: ЗАМ 3 2 C:=COS (Х) — КхХ-, LI: INPUT (1, Л); 'IF' К<0 'THEN’ 'GO ТО' М-, ЗАМ 4 1 Д:=0; В:=3.14; F(A, У1); F(B, У2); ВЫБ 5 2 ЗАП БИБОТЛ ТРАНС]. ПЕЧ 1 6 ВСЕ Кроме того, он выполняет указание о выводе на печать первых ше- сти строк отредактированного текста (с новой нумерацией строк): 0001 'BEGIN' 'REAL' А, В, К, X, Р, У1, У2; 0002 'PROCEDURE' F(X, С); 'VALUE'X; 'REAL' X, С; 0003 C:=COS(X) — КхХ; 0004 LI: INPUT (1, K); 'IF' K<0 'THEN' 'GO TO' M\ 0005 Л:=0; B:=3.14; F(A, У1); F(B, У2); 0006 REP-. Х:=(Л+В)/2; F(X, P)- КОЛИЧЕСТВО СТРОК В ТЕКСТЕ — 0012 Весь отредактированный текст будет в итоге записан под именем ТРАНС] в архив и может в дальнейшем вызываться оттуда для его дальнейшего использования. 6.6. Схема функционирования системы программирования В заключение приведем общую схему функционирования много- языковой системы модульного программирования (см. рис. 6.6). В многоязыковой системе модульного программирования поль- зователи могут писать свои модули на разных языках программиро- вания, выбирая для написания каждого модуля наиболее удобный язык. Как видно из схемы, каждый модуль пользователя может быть записан в библиотеку для последующего его использования. С по- мощью редактора текстов пользовательский модуль может подвер- гаться редактированию. При, этом подлежащий редактированию текст редактор может либо брать из библиотеки, либо получать его
400 СИСТЕМЫ ПРОГРАММИРОВАНИЯ [Гл. S непосредственно от пользователя путем ввода с перфокарт или с терминала, на котором пользователь работает с ЭВМ в режиме диа- лога, либо формировать этот текст из отдельных частей, одни из которых берутся из библиотеки, а другие — непосредственно от Пользователя. Отредактированный текст может быть записан в библиотеку и (или) выведен на печать. Ё Пользователи Модули пользователей (на входных языках) Рис. 6.6. Каждый модуль пользователя переводится в загрузочную форму с помощью транслятора Tpi с того входного языка, на котором на- писан данный модуль. Стрелка, идущая от библиотеки к модулям пользователя, означает, что подлежащие трансляции модули поль- зователей трансляторы могут брать из библиотеки. Вырабатывае- мые трансляторами загрузочные модули также могут записываться в библиотеку. Модули, подлежащие загрузке с целью получения машинной программы, берутся из библиотеки или поступают непосредственно от трансляторов. Сначала паспорта объединяемых в программу мо- дулей обрабатываются редактором связей, который заполняет их взаимными ссылками, после чего работает загрузчик, который остав-
6.В] СХЕМА СИСТЕМЫ ПРОГРАММИРОВАНИЯ 401 ляет в памяти машины готовую к выполнению машинную програм- му. Эта программа выполняется машиной либо непосредственно, либо под управлением отладчика, который обеспечивает при этом выполнение задания пользователя на отладку. При каждом сеансе использования ЭВМ пользователь может выполнять различные виды работ по изготовлению своих модулей или рабочей программы, пользуясь услугами соответствующих ком- понент системы программирования. Свое задание на выполнение нужной ему работы пользователь формулирует на специальном язы- ке общения человека с вычислительной системой, удобном для поль- зователя. Задание пользователя в общем случае состоит из последователь- ности шагов задания, каждый из которых определяет тот или иной вид работы, выполняемой с помощью одной из компонент системы программирования. Каждый шаг задания содержит информацию о том, какая компонента системы должна быть включена в работу, а также информацию о входных и выходных данных этой компонен- ты. Например, отдельные шаги в задании пользователя могут опре- делять такую последовательность работ*. — провести редактирование одного из модулей, хранящихся в библиотеке, и вывести на печать отредактированный текст модуля; — ввести с перфокарт текст другого модуля и записать его в библиотеку для последующего использования; — ввести с перфокарт текст третьего модуля, который является главным в некоторой программе, оттранслировать его, загрузить этот и все другие модули, образующие программу, и провести оче- редной этап отладки полученной машинной программы. В составе математического обеспечения ЭВМ имеется специаль- ная управляющая программа, которая понимает тот язык, на ко- тором пользователь формулирует свое задание. Назначение этой программы и состоит в том, чтобы выполнить задание пользователя путем последовательного вызова в работу соответствующих компо- нент системы программирования. Таким образом, функции пользователя сводятся к форму- лированию (на удобном для него языке) задания на вы- полнение тех или иных видов работ, возникающих в процессе изго- товления программы; фактическое же выполнение этих ра- бот обеспечивается системой программирования.
Глава 7 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН С В настоящее время при решении задач вычислительного харак- тера широкое применение находит язык программирования фортран (FORTRAN). Исторически фортран появился раньше алгола-60 — в серединё 50-х годов. Первоначально под этим назва- нием понималась вспомогательная программа, предназначавшаяся для перевода (трансляции) последовательности формул в машинную программу (сам термин FORTRAN является сокращением слов FORmula TRANslation — трансляция формул). Поскольку эта про- грамма, естественно, предъявляла определенные требования к запи- си формул, подлежащих трансляции, то под термином фортран одновременно понимался и достаточно простой язык програм- мирования, ориентированный на решение задач соответствующего класса. Впоследствии этот язык продолжал развиваться путем вклю- чения в него все новых и новых возможностей, и в настоящее время существует уже несколько версий этого языка с достаточно широ- кими возможностями, близкими к алголу-60. Одной из наиболее развитых и широко распространенных является версия фортран- IV. С начала 1979 г. в нашей стране вступил в силу государствен- ный стандарт на этот язык программирования. Несмотря на то, что алгол-60 появился позднее и явился в из- вестном смысле обобщением и усовершенствованием существовавших до него языков программирования, в том числе и фортрана, послед- ний все же оказался вполне конкурентноспособным по, отношению к алголу-60 языком, и даже сейчас он имеет большее распростране- ние и применение, чем алгол-60. Эта жизнеспособность фортрана обусловлена тремя основными факторами. Во-первых, модульностью этого языка. Фортран — в отличие от алгола-60 — заранее предполагает, что в общем случае программа, предназначенная для решения той или иной конкретной задачи, будет получаться путем объединения нескольких модулей. Поэтому и назначение языка состоит в записи модулей пользователя, и основной единицей языка является не программа в целом, а мо- дуль, причем отдельные модули могут составляться и трансли- роваться на язык загрузки независимо друг от друга. О пре- имуществах модульного программирования, особенно при реше- нии сложных задач, мы уже говорили ранее.
Г.1] ОБЩАЯ ХАРАКТЕРИСТИКА ФОРТРАН-ПРОГРАММЫ 403 Во-вторых, огромным запасом готовых модулей, который.был накоплен за время существования фортрана, что существенно упрощает изготовление программ для решения новых задач за счет широкого использования уже имеющихся модулей. Этот фактор является следствием первого, т. е. модульности языка, но сейчас — при наличии разнообразных языков программирования, в том числе и модульных — он приобретает решающее значение. В-третьих, фортран, будучи удобным для человека языком про- граммирования, все же сохранил и определенную машинную ориен- тацию, т. е. в известной мере в нем учтена и общая специфика ЭВМ, на языки которых в конечном счете должна быть переведена любая программа, подлежащая выполнению. Благодаря этой особенности фортрана и трансляция на язык машины осуществляется проще и быстрее, и вырабатываемая машинная программа получается более эффективной (в частности, более «быстродействующей»), чем в слу- чае алгола, что имеет немаловажное значение при решении практи- ческих задач, особенно с большими объемами вычислений, а ведь для решения именно таких задач прежде всего и служат ЭВМ. Указанные выше факторы и обеспечивают широкое использова- ние фортрана и в настоящее время, хотя ряд других языков програм- мирования (в том числе и алгол-60) во многих отношениях являются • более развитыми языками. Заметим также, что целый ряд особен- ностей фортрана (по сравнению с алголом-60), и особенно его модуль- ность, находят свое отражение и в более поздних языках, напри- мер в ПЛ/1. Рассмотрим теперь общую структуру и основные особенности языка фортран, знакомство с которыми поможет — в случае необ- ходимости — перейти к более детальному его изучению. 7.1. Общая характеристика фортран-программы Программа, которая представляет собой описание некоторого вполне законченного вычислительного алгоритма и которая может быть выполнена соответствующим процессором, в фортране назы- вается выполнимой программой (мы будем называть ее фортран- программой или, ради краткости, просто программой). В отличие от алгола здесь программа должна содержать исчерпывающую ин- формацию не только об алгоритме переработки исходных данных с целью получения искомых результатов и о вводимых в употреб- ление объектах, но также и о форме записи исходных данных, и о форме представления окончательных результатов, выводимых из машины. Программа состоит из одного или нескольких программных мо- дулей, один (и только один) из которых является главным — с его выполнения и начинается выполнение машиной всей программы.
404 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл 7 Как мы уже знаем, в алголе программа может использовать толь- ко внутренние процедуры, т. е. процедуры, описанные в ней самой. Модульность фортрана и заключается в том, что в любом программном модуле могут использоваться внешние по отно- шению к нему процедуры, т. е. такие процедуры, которые описаны пне данного модуля. Для описания таких внешних процедур сред- ствами фортрана служат модули-процедуры, которые должны вхо- дить в, состав общей программы. При этом внешняя процедура мо- жет быть либо внешней функцией, либо внешней подпрограммой {аналог процедур-функций и процедур-операторов в алголе). Любая внешняя процедура может иметь как динамические, так я статические параметры. Для обращения к внешним процедурам служат указатели функций и операторы вызова подпрограммы, в которых задаются только динамические параметры этих процедур; для задания статических параметров используется знакомый нам аппарат внешних и общих имен, и настройка модуля на статические параметры производится во время загрузки модулей. Каждый модуль состоит из предложений и комментариев. Как обычно, комментарии используются для повышения наглядности программы, не влияя на характер ее выполнения. Предложение является законченной фразой языка. Все предло- жения фортрана делятся на два основных класса: выполняемые {или операторы) и невыполняемые (или объявления). Операторы опре- деляют действия в программе, тогда как объявления описывают ха- рактеристики тех или иных Объектов, способ редактирования дан- ных при их выводе, и т. п. ' Заметим, что все языки программирования можно разделить на бланковые и безбланковые. В первом случае программа записывается ла специальных бланках — заранее разграфленных листах бумаги, на которых горизонтальными линиями выделены строки, в которых л должен записываться текст программы, а каждая строка разделе- на на позиции, в которых должны записываться отдельные символы, образующие текст. При этом отдельные позиции в строках могут группироваться в поля, каждое из которых обычно имеет опреде- ленное назначение, т. е. служит для записи определенной компонен- ты фразы данного языка (например, метки оператора). Кроме того, на бланке могут быть заранее отпечатаны в соответствующих пози- циях строк некоторые символы, характерные для данного языка •{например, двоеточия, отделяющие метки от операторов). Бланковость языка обычно является проявлением его машинной ориентации, а именно — зависимости от используемых на ЭВМ внешних носителей данных и устройств их подготовки. Так, если л качестве внешних носителей используются перфокарты, то при вы- боре формата бланка обычно исходят из того, что каждая строка -текста на бланке будет перфорироваться на отдельной перфокарте. .Поскольку в качестве стандарта на ЭВМ приняты 80-колонные пер-1
7.11 ОБЩАЯ ХАРАКТЕРИСТИКА ФОРТРАН-ПРОГРАММЫ 405 фокарты и поколонная перфорация (т. е. код каждого символа пер* форируется в определенной колонке перфокарты), то в каждой стро- ке бланка обычно выделяется не более 80 позиций. В безбланковых языках текст программы пишется либо на обыч- ных листах бумаги, либо на простейших бланках, на которых ли- ниями выделены только строки, без выделения позиций и без уста- новления каких-либо правил расположения символов в строке и правил перехода с одной строки на другую, причем и выделение строк делается лишь с целью стандартизации записи текста, облег- чающей его последующую перфорацию. Типичным представителем безбланковых языков является ал- гол-60. Фортран же представляет собой бланковый язык. В каждой строке бланка фортрана выделены 72 позиции, перенумерованные слева направо от 1 до 72, в каждой из которых может быть записан один символ. При этом заметим, что в бланковых языках пробел Л5Й Л/ПМк нетж? Л । । МА *1 7| f । \15\ f । t _ iZ? iirr f 1 мо Л\ £*1111 *1 1 1 1 I 1 PJa^a^x • i I I I I f । । 17\0 1 T\~\P\li i i i i i i i (III 1 Hw|7|e I I 1 1 f 1 +|7|2| •|7|2|^i'9|*|7’i*i*i4| । । 1 |2|0| _1 । । । । । । Рис. 7.1. обычно является значащим символом. Позиции объединяются в по- ля, каждое из которых имеет свое назначение. Например, наличие буквы С в позиции 1 какой-либо строки служит признаком того, что остальной текст, записанный в этой строке, является коммента- рием. Позиции 1—5 в записи предложения образуют поле метки. В качестве меток в фортране используются только целые без зна- ка, и если какое-либо предложение в программе снабжается мет- кой, то последовательность (от одной до пяти) цифр, образующих метку, записывается в этом поле, начиная с любой ее позиции. Само помечаемое (или непомеченное) предложение записывается в поле, которое образуют позиции 7—72. Поскольку текст предложения может не уместиться в одной стро- ке бланка, то язык предполагает, что каждое предложение состоит
406 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН 1.Гл. 7 из одной или более частей, называемых также строками — кажда я из них записывается в отдельной строке бланка и перфорируется на отдельной перфокарте. Первая строка предложения называется на- чальной строкой, а остальные (если они есть) — строками-продол- жениями. Поле бланка, состоящее из одной позиции 6, использует- ся для указания того, что данная строка является строкой-продол- жением: наличие в этой позиции любого символа, отличного от про- бела и цифры 0, является признаком строки-продолжения. Каждый комментарий представляется одной строкой, содержа- щей букву С в позиции 1. При этом в комментариях обычно разре- шается использовать не только символы, образующие алфавит форт- рана, но и любые другие символы, имеющиеся на устройствах подготовки перфокарт и алфавитно-цифровом печатающем устрой- стве (АЦПУ). На рис. 7.1 приведен пример записи фортранного текста на бланке. 7.2. Основные понятия 7.2.1. Алфавит. Алфавит фортрана образуют три группы символов: цифры, бук- вы и специальные символы. Цифры — как и в алголе — это араб- ские цифры от 0 до 9, а в качестве букв используются только 26 заглавных букв латинского алфавита (от А до Z). Буква или цифра называется буквенно-цифровым символом. Специальный символ — это один из следующих десяти символов: Символ Название Символ Название 8 + 1 * пробел равно плюс минус звездочка / ( ) » наклонная черта открывающая круглая скобка закрывающая круглая скобка запятая десятичная точка Как видно, алфавит фортрана гораздо беднее, чем в алголе. С одной стороны, это несколько снижает наглядность записи про- грамм, но зато предъявляются меньшие требования к устройствам подготовки данных (это тоже одно из проявлений машинной ориен- тации языка). Недостающие символы представляются определен- ными последовательностями из имеющихся символов. Например, символ » используется для обозначения операции умножения, а операция возведения в степень обозначается через **; аналогом основных символов' алгола, являющихся подчеркнутыми словами.
7.21 ОСНОВНЫЕ ПОНЯТИЯ 407 в фортране являются зарезервированные для этих целей последо- вательности букв, называемые ключевыми словами, например FOR, DO и т. д. Следует избегать использования ключевых слов в каче- стве имен (или начал имен) объектов, особенно в тех местах пред- ложений, где по синтаксису могут встречаться ключевые слова, ибо это может привести к неоднозначности понимания сделанной записи. 7.2.2. Типы данных. В фортране различаются данные шести типов: целые, веществен- ные, двойной точности, комплексные, логические и текстовые. Каждый тип может иметь свое особое внутреннее представление в машине, поэтому интерпретация операций над данными существен- но зависит от типов этих данных. С данными типа целое, вещественное и логическое мы уже зна- комы по алголу. Данное двойной точности отличается от вещественного только повышенной точностью представления в машине: если вещественное значение представляется одним машинным словом, то значение двой- ной точности обычно представляется парой машинных слов. Комплексное данное — это машинное приближение'комплекс- ного значения: оно представляется в виде упорядоченной пары ве- щественных данных, первое из которых представляет действитель- ную, а второе — мнимую часть комплексного числа. Текстовое данное — это последовательность символов (аналог понятия (строка) в алголе). В текстовом данном пробел является значащим символом. Заметим, что увеличение числа допустимых типов данных яв- ляется характерной тенденцией в развитии алгоритмических язы- ков, так как это расширяет круг задач, при решении которых может удобно использоваться данный язык. 7.2.3. Константы. Для каждого типа данных в языке имеются свои правила записи констант. Целые и вещественные числа записываются так же, как и в алго- ле — с той лишь разницей, что вместо символа 10 используется бук- ва Е, ив смешанной дроби может отсутствовать дробная часть. Кро- ме того, не допускаются числа, состоящие только из десятичной экс- поненты, например Е+2. Примеры записи вещественных чисел: 3.14 +.02 5. . —2Е—4 39. Е5 Число двойной точности записывается как и вещественное число, только обязательно должна присутствовать десятичная экспонен- та, и в ней вместо буквы Е используется буква D (что и является признаком числа двойной точности), например: 0.05 D0 —5 D—6 5. D —7
408 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН Гл. it Комплексное число задается в виде упорядоченной пары вещест- венных чисел (каждое из которых может быть либо вещественным без знака, либо вещественным со знаком), разделенных запятой и заключенных в скобки: первое из этих чисел представляет действи- тельную, а второе — мнимую часть комплексного числа. Напри- мер, комплексное число Z=2+3t можно записать в виде (2.0, 3.0), а число Z——0.2» — в виде (0.0, —2.Е —1). Текстовая константа записывается в виде п Н ht ht... hn где п — целое без знака (п>0), а каждое h: — некоторый символ. Последовательность из п символов, которая следует за буквой Н, и образует собственно текстовое данное — константу. Заметим, что после буквы Н могут записываться любые символы, представление которых допустимо в машине (не обязательно из алфавита фортра- на), например: 16НРЕШЕНИЕ СИСТЕМЫ: Текстовая константа может встречаться только в списке фактичес- ких параметров оператора вызова подпрограммы и в объявлении начальных данных. Чаще всего текстовые константы используются в качестве заготовок заголовков, выводимых на печать. Заметим, что при рассмотрении текстовой константы мы по су- ществу впервые встретились с синтаксическим определением в фортране, и, как видно, это определение дано иначе, чем в алголе. Конечно, определение текстовой константы (как и других понятий фортрана) можно было бы дать на языке БНФ, например: (текстовая константа) ::=(целое без знака) Н (символ)} (текстовая константа) (символ) сделав при этом оговорку, что целое без знака должно быть отлич- но от нуля и что количество литер, следующих за буквой Н, долж- но равняться значению этого целого без знака. Однако в фортране синтаксические определения даются в ином виде — просто приво- дится вид той конструкции, которая образует определяемое поня- тие, причем отдельные компоненты этой конструкции, не являющие- ся буквами алфавита, указываются с помощью их условных обозна- чений. Конечно, такие определения страдают недостатком строгости и точности, поэтому они обычно сопровождаются дополнительными пояснениями описательного характера (что влечет за собой те же самые недостатки). Зато такой способ обеспечивает лучшую нагляд- ность определяемой структуры, что является его преимуществом по сравнению с языком металингвистических формул, который к то- му же не был еще известен к моменту появления фортрана. В даль- нейшем изложении мы не будем отходить от принятого в фортране способа описания синтаксиса, тем более что он является общепри- нятым во всех материалах, относящихся к этому языку.
7.2] ОСНОВНЫЕ ПОНЯТИЙ 409 Логические константы, представляющие логические значения «истина» и «ложь», записываются как .TRUE, и .FALSE, соответст- венно. 7.2.4. Переменные. Как и в алголе, для ссылок на отдельные (переменные) значения используются простые переменные, а на элементы массива — пере- менные с индексами. В отличие от алгола, в фортране размерность массива не может быть больше трех, а нумерация по каждому изме- рению всегда начинается с единицы. При этом в качестве индексных выражений допускаются только арифметические выражения вида C*v+k, C*v—k, C*V, V-\-k, V—k, V, k, где с и k — целые без знака, а о — целочисленная простая перемен- ная. Эти ограничения на допустимые индексные выражения, на чис- ло измерений и на нумерацию по каждому измерению вполне прием- лемы при решении большинства практических задач, но они упро- щают трансляцию и позволяют получать более качественные про- граммы. Поскольку в алфавите фортрана нет квадратных скобок, то спи- - сок индексных выражений заключается в круглые скобки. Примеры переменных с индексами: А(3) X(2«I) MATR (К—2, 5). Что касается типа значений, принимаемых простыми перемен- ными или элементами массива, то он — как и в алголе — может быть указан при помощи объявления типа (вместо термина «описа- ние» в фортране используется термин «объявление»). Однако такое объявление не является обязательным — при его отсутствии с име- нем переменной и именем массива связывается тип целый, если пер- вая буква этого имени есть ,1, J, К, L, М или N; в противном случае связывается тип вещественный (аналогично тому, что если в опи- сании массива в алголе отсутствует указание тцпа, то этому мас- сиву предписывается тип real). Этот «принцип умолчания» в некото- рых языках используется весьма широко. 7.2.5. Выражения. В фортране для задания правил вычисления числовых и логи- ческих значений также служат арифметические и логические вы- ражения, причем в фортране отсутствуют условные выражения. Арифметическое выражение может определять значение типа це- лый, вещественный, двойной точности или комплексный. Синтак- сическое определение арифметического выражения здесь аналогич- но определению простого арифметического выражения в алголе — с той разницей, что в качестве множителя используется только кон- струкция (первичное выражение)»* (первичное выражение)
410 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. 7 (т. е. отсутствует рекурсивность в определении этого понятия). Как и в алголе, формулируются правила определения типа результата операции в зависимости от типов аргументов, но накладываются несколько другие ограничения на допустимые типы аргументов. Например, выражение (№“+ 2-fo?)siny на фортране запишется в виде (X**(2*»N)+A(I,2)*B(I)**2) «SIN (2*X/3) Логическое выражение определяется аналогично простому логи- ческому выражению алгола, однако используются только три ос- новные логические операции: отрицание (.NOT.), логическое умно- жение (.AND.) и логическое сложение (.OR.). В отношениях на- кладываются определенные требования на типы входящих в него арифметических выражений и используются другие обозначения операций отношения: .LT. (О, ,LE. (</), .EQ. (=), .NE. (=#), .GT. (>), .GE. О). Впрочем, в конкретных представлениях язы- ка используемые знаки выбираются в зависимости от возможностей имеющихся устройств подготовки данных (как и для алгола). Так, логическое выражение x<Zy/\ 1 а на фортране запишется следующим образом: X.LT.Y.AND. .NOT.A 7.3. Операторы Как уже отмечалось, все предложения фортрана делятся на операторы и объявления, причем операторы делятся на три группы: операторы присваивания, операторы управления и операторы ввода/ вывода. 7.3.1. Операторы присваивания. Существуют три типа операторов присваивания: арифметичес- кий, логический и оператор предписания. Арифметический оператор присваивания имеет вид v=e где v — числовая переменная (простая или с индексами), е — ариф- метическое выражение. Смысл такого оператора тот же самый, что и в алголе, а символ — выполняет роль символа :=. При этом формулируются ограни- чения на допустимые типы v и е (например, если е — типа комплекс- ный, то и о может быть только типа комплексный), а также правила, по которым должно преобразовываться вычисленное значение е перед присваиванием этого значения переменной и в зависимости от (допустимых) типов е и V.
7.31 ОПЕРАТОРЫ 411 Примеры арифметических операторов присваивания: 1=5 М=М+2 D(I, К)=А(1)**2+В(К)**2 Логический оператор присваивания имеет вид и=е где v — логическая переменная (простая или с индексами), е — ло- гическое выражение. Этот оператор также имеет обычный смысл, как и в алголе. Пример логического оператора присваивания: A=X.NE.Y Оператор предписания имеет вид ASSIGN k ТО i где k — метка оператора (целое без знака), i — целочисленная про- стая переменная. Этот оператор служит для того, чтобы переменной i предписать значение k. которое будет использоваться в соответствующем опера- торе перехода по предписанию. Пример оператора предписания: ASSIGN 25 ТО ММ 7.3.2. Операторы управления. В языке различается 8 типов операторов управления: — операторы перехода, — условный арифметический оператор, — условный логический оператор, — оператор продолжения, — оператор цикла, — оператор вызова подпрограммы, — оператор возврата, — операторы управления программой. 7.3.2.1. Операторы перехода делятся на три группы: безуслов- ный, по предписанию и вычисляемый. Безусловный оператор перехода имеет вид GO ТО k где k — метка оператора (целое без знака), и означает переход к оператору, помеченному этой меткой k. Пример безусловного оператора перехода: GO ТО 345 Оператор перехода по предписанию имеет вид GO ТО Z, (ku . ., kn) где i — целочисленная простая переменная, а каждое kj — метка оператора. К моменту выполнения данного оператора переменной i должно быть предписано (присвоено) текущее значение предшествующим выполнением рассмотренного выше оператора предписания — этим значением должна быть одна из меток списка, заключенного в скоб-
412 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. 7 ки. Результат выполнения такого оператора перехода и состоит в переходе к оператору, помеченному этой меткой. Если переменной i не было предписано значение или такого значения нет в указанном списке, то действие этого оператора перехода неопределено. Пример оператора перехода по предписанию: GO ТО ММ, (15, 25, 10, 16) Если к моменту выполнения этого оператора целочисленной пере- менной ММ было предписано значение 25 выполненным ранее опе- ратором предписания, то выполнение данного оператора перехода означает переход к оператору, помеченному меткой 25; если же пе- ременной ММ было предписано, например, значение 40, то действие этого оператора перехода неопределено. _ Вычисляемый оператор перехода имеет вид GO ТО (ki, kt......kn), i где i — целочисленная простая переменная, а каждое kj — метка оператора. Здесь в скобках по сути дела содержится переключательный спи- сок, позиции которого перенумерованы от 1 до п. Выполнение дан- ного оператора означает переход по метке, указанной в той позиции переключательного списка, номер которой равен значению перемен- ной i; при iCO и i>n действие этого оператора неопределено. Так, выполнение вычисляемого оператора перехода GO ТО (7, 26, 425), К означает переход по метке 7 при текущем значении К=1, по метке 26 при К=2 и по метке 425 при К=3; при любом другом значении К результат выполнения данного оператора перехода неопределен. 7.3.2.2. Условный арифметический оператор имеет вид 1F (<?) klt kt, где е — арифметическое выражение типа целый, вещественный или двойной точности, ku kt и k3 — метки операторов. Этот оператор служит для разветвления вычислительного про- цесса в зависимости от знака значения е: его выполнение означает переход по метке k, при е<0, по метке kt при е=0 и по метке k» при е>0 (на алголе такое действие выполняется оператором пере- хода вида go to if е<0 then кг else if e=0 then kt else ks). Например, алгоритм вычисления о=8г2-Ьг+0.5, где (хг + у* при х<у, (1— х—у при можно записать с использованием условного арифметического опе- ратора следующим образом: IF(X—Y)10, 20, 20 10 Z=X**2+Y**2 GO ТО 30
7.3] ОПЕРАТОРЫ 413 20 Z=l. —X—Y 30 V=8*Z**2+Z+.5 7,3.2.3. Условный логический оператор, имеющий вид IF (е) s где е — логическое выражение, s — любой оператор, кроме опера- тора цикла и условного логического оператора, эквивалентен опе- ратору «если» алгола вида if е then s Например, значение t®=min (и, о) можно получить так: W=U IF (V.LT.U) W=V 7.3.2.4. Оператор продолжения имеет вид CONTINUE Этот оператор является эквивалентом пустого оператора алгола: в результате его выполнения просто продолжается обычный поря- док выполнения операторов. Как правило, оператор продолжения используется для того, чтобы с его помощью пометить нужную точ- ку в программе. Так, значение t»=min («, о) можно получить и по алгоритму: W=U IF (V—U) 5, 6, 6 5 W=V 6 CONTINUE 7.3.2.5. Оператор цикла. В фортране оператором цикла но сути дела называется заголовок цикла. Это запись вида DO п i—mi, т3, т3 или DO п i—mlt т3 где п — метка оператора, i — целочисленная простая переменная (параметр цикла), а каждое из ти /п« и /п, — целое без знака или целочисленная простая переменная. Назначение каждого из этих операторов эквивалентно назначе- нию заголовков цикла алгола вида for i:=mi step т3 until т3 do и for ii=mi step 1 until m3 do соответственно (шаг задается значением т3, а не значением т3). Телом цикла, которое выполняется при последовательных зна- чениях параметра цикла t, является последовательность операто- ров, следующих за оператором цикла. Поскольку в фортране нет операторов, аналогичных блоку или составному оператору алгола, то последний оператор, которым заканчивается тело цикла, дол- жен быть помечен — метка п этого оператора указывается в опера- торе цикла, и тем самым определяется область его действия, т. е. тело цикла. Этот оператор, помеченный меткой п, называется тер- минальным оператором цикла. Терминальный оператор не может
414 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. быть оператором перехода, возврата, останова, цикла, условным арифметическим оператором, а также условным логическим опера- тором, содержащим в себе какой-либо из указанных здесь операто- ров (в связи с этим в таких случаях обычно и приходится вставлять в программу помеченный оператор продолжения). К началу выполнения оператора цикла значения ть т2 и тл должны быть строго больше нуля. Значение mt не должно превы- шать значения mt, так что тело цикла должно выполняться хотя бы один раз, причем в теле цикла значения i, гщ, т2 и т» не дол- жны изменяться. Вопросы входа в цикл и выхода из него, а также вопрос о зна- чении параметра цикла после выхода из цикла решаются примерно так же, как и в алголе. Для иллюстрации использования оператора цикла рассмотрим следующий пример: у вектора х (xt, х2, . . ., хп) значения всех отрицательных компонент с нечетными номерами надо заменить их квадратами. Алгоритм решения этой задачи можно записать в виде: DO 5 1 = 1, N, 2 IF(X(I).LT.O)X(I) = X(l)**2 5 CONTINUE Если же указанному преобразованию подлежат все отрицательные компоненты, то явное задание шага изменения значений параметра цикла можно опустить, поскольку этот шаг равен единице: DO 5 1 = 1, N 1 F(X( I).LT.O)X(I) = Х(1 )**2 5 CONTINUE Использование в операторах цикла фортрана по сути дела только элемента списка цикла типа арифметической прогрессии, а также использование в качестве параметра цикла только целочисленной переменной, несколько ограничивает возможности этих операторов по сравнению с алголом. Так, знакомый нам алгоритм вычисле- ния у=У х с точностью до 8>0 методом Ньютона, который на алголе записывается весьма компактно: у:=1; for v.—(x]y—у)]2 while abs (v)^eps do y:=y+v на фортране с использованием оператора цикла можно задать сле- дующим образом (сделав предположение, что для достижения тре- буемой точности заведомо не понадобится больше ста итераций): Y = 1.0 DO 20 1 = 1, 100 V = (X/Y —Y)/2 IF(ABS(V).LT.EPS) GO TO 30 20 Y = Y-f-V 30 CONTINUE
7.3) ОПЕРАТОРЫ 415 Как и в алголе, допускается использование вложенных циклов. Если тело одного оператора цикла содержит другой оператор цик- ла, то тело этого другого оператора цикла должно быть подмноже- ством тела первого оператора цикла. Например, алгоритм умножения квадратных матриц А и В по- рядка п можно записать так: DO 40 1 = 1, N DO 40 J = l, N R = 0 DO 20 K = l, N 20 R = R + A(I, K)*B(K, J) 40 C(I, J) = R 7.3.2.6. Оператор вызова подпрограммы имеет вид CALL s(an а2, ..., а„) или CALL s где s — имя подпрограммы, а каждое aj — фактический параметр. Как уже говорилось, в фортране имеются два типа внешних про- цедур: процедуры-функции и процедуры-подпрограммы. Обращение к любым функциям производится как и в алголе, т. е. с помощью указателей функций. Для обращения же к процедурам-подпрограм- мам как раз и служит оператор вызова подпрограммы, так что этот оператор является аналогом оператора процедуры в алголе. Каждая внешняя процедура, определяемая отдельным моду- лем, может иметь как статические, так и динамические параметры. В операторе вызова подпрограммы задаются только те фактические параметры, которые соответствуют динамическим параметрам под- программы, а настройка подпрограммы на ее статические парамет- ры производится во время загрузки. Фактическим параметром при ссылке на внешнюю подпрограмму может быть текстовая констан- та, переменная (простая или с индексами), имя массива, имя внешней процедуры, любое арифметическое или логическое выра- жение .(не состоящее только из одной переменной). Выполнение оператора вызова подпрограммы производится пу- тем обращения к модулю-подпрограмме, имя которого указано в операторе. Связь между заданными в операторе фактическими пара- метрами и формальными параметрами модуля-подпрограммы уста- навливается по правилам, аналогичным соответствующим правилам в алголе. Поскольку в фортране нет понятия, аналогичного списку значений алгола, то характер вызова фактических параметров (зна- чением или по имени) определяется самим фактическим параметром, а именно — значением вызывается последняя из перечисленных вы- ше категорий фактических параметров (любое выражение); осталь- ные категории фактических параметров вызываются по имени. . Примеры операторов вызова подпрограммы: CALL AINT (O,B+O.5,F2,.1E—4, Y) CALL PRINT (9НМАССИВ А:, А)
416 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. 7 (первым фактическим параметром в последнем примере является текстовая константа). 7.3.2.7. Оператор возврата имеет вид RETURN и используется только в модуля-процедурах (функциях и подпро- граммах) для того, чтобы отмечать их логический конец, т. е. ме- сто, в котором должно заканчиваться выполнение данного модуля- процедуры. В алголе таким логическим концом всегда является последний оператор тела процедуры — только после его завершения процеду- ра считается законченной и осуществляется возврат в основную программу. В фортране выполнение процедуры может быть закон- чено в любом месте модуля-процедуры, как только в процессе его выполнения встретится оператор возврата. 7.3.2.8. Операторы управления программой. В языке предусмот- рены два типа таких операторов: оператор паузы и оператор оста- нова. Оператор паузы имеет вид PAUSE п или PAUSE где п — последовательность от одной до пяти восьмеричных цифр. Выполнение этого оператора состоит из двух этапов. В результате выполнения первого из них происходит приостановка выполнения программы, и на время этой приостановки становится доступной последовательность цифр (значение) п, заданное в операторе. Для ? возобновления выполнения программы необходимы действия, внеш- ние по отношению к ней. Если выполнение возобновляется без ка- ких-либо изменений состояния процессора, то выполняется второй этап оператора паузы, в результате которого продолжается обыч- ный порядок выполнения операторов. Этот оператор удобен, например, при работе пользователя с ЭВМ в режиме диалога (этот режим будет рассмотрен более подроб- но в главе 10): во время паузы, возникающей при выполнении дан- ного оператора, пользователь, работающий за специальным устрой- ством связи с ЭВМ — терминалом, может выполнить некоторые вспомогательные действия, не предусмотренные в программе (на- пример, при ее отладке). Внешними действиями, которые влекут за собой продолжение выполнения программы, как раз и являются соответствующие действия пользователя за терминалом. Оператор останова имеет вид STOP п или STOP где п—последовательность от одной до пяти восьмеричных цифр. В результате выполнения этого оператора завершается выполне- ние программы. Таким образом, в отличие от алгола, здесь вы- полнение программы может завершаться в разных ее местах,
7.4] ОБЪЯВЛЕНИЯ 417 по разным причинам, а значение и, которое указано в операторе останова и которое также становится доступным в момент окон- чания выполнения программы, может использоваться для уста- новления того, по какой причине было прекращено выполнение программы. Операторы ввода/вывода мы рассмотрим в отдельном разделе вместе с объявлениями формата, с которыми тесно связано вы- полнение этих операторов. 7.4. Объявления В фортране предусмотрены следующие объявления (которые яв- ляются аналогом описаний в алголе): объявления спецификаций, объявление начальных данных, объявление формата, объявление внутренней функции и заголовок модуля (заголовок функции или заголовок подпрограммы). В целом роль объявлений в фортране аналогична роли описаний в алголе, однако имеются и определенные различия. В алголе, на- пример, любой используемый в программе объект должен быть вве- ден в употребление с помощью соответствующего описания (за иск- лючением стандартных процедур). Это требование обусловлено, в частности, блочной структурой алгол-программы и связанным с этим принципом локализации. В фортране это требование не является обязательным. Напри- мер, для простых переменных и массивов можно вообще не давать их объявлений с целью ввести в употребление эти объекты — счи- тается, что сам факт их использования в программном модуле сви- детельствует о наличии этих объектов, а отсутствие блочной струк- туры фортран-программы снимает вопрос об области существова- ния упомянутых объектов — таковой всегда является тот программ- ный модуль, в котором используются эти объекты. Основное назначение объявлений в фортране — это указать ха- рактеристики тех или иных объектов. Но если в алголе вся инфор- мация об объекте должна содержаться в одном описании (с помощью которого этот объект вводится в употребление), то в фортране эта информация может содержаться в разных объявлениях. Например, информация о структуре массива может быть задана в объявлении массивов или в объявлении общих объектов, а информация о типе значений его компонент — в объявлении типа. А благодаря широ- кому использванию принципа умолчания, имена некоторых объ- ектов вообще могут не фигурировать в каких-либо объявлениях. Так, для простой переменной задается только одна ее характеристи- ка — тип принимаемых ею значений, и если имя этой переменной таково, что по нему в соответствии с принципом умолчания тип зна- чений определяется правильно, то нет необходимости включать имя этой переменной в объявления типа. 14 э. 3. Любимскнй и др.
418 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл, 7 В данном разделе мы рассмотрим объявления спецификаций и начальных данных; остальные объявления будут рассмотрены в по- следующих разделах. 7.4.1. Объявления спецификаций. Различается пять типов объявлений спецификаций: — объявление массивов; — объявление типа; — объявление эквивалентности; — объявление внешних имен; — объявление общих объектов. Объявление массивов имеет вид DIMENSION оД^), о2(/2), • • •» un(Q где каждое о/О) есть описание массива, в котором V; — имя мас- сива, a ij — список границ. Описание массива указывает имя массива, число измерений (од- но, два или три) и размер по каждому измерению. Поскольку ниж- няя граница по каждому измерению в фортране считается равной единице, то список границ по сути дела является списком верх- них границ. При этом в качестве верхней границы может фигури- ровать только целое без знака или целочисленная простая пере- менная. Если список границ не содержит ни одной переменной, то он называется постоянным списком границ. Если в качестве хотя бы одной из границ фигурирует переменная, то описываемый массив называется массивом с регулируемыми размерами, а имена этих переменных — регулируемыми размерами. В связи с тем, что в фортране отсутствует блочная структура программы, такие массивы допустимы только в модуле-процедуре, и в этом случае имя массива и имена переменных, представляющих регулируемые размеры, должны входить в список формальных параметров заголовка этого модуля. Наличие описания массива в каком-либо объявлении служит для задания информации о том, что имя, которым начинается это опи- сание, является именем массива и что этот массив имеет указанную (с помощью списка границ) структуру, т. е. размерность и размеры по каждому измерению. Объявление массивов, в которое входит одно или несколько опи- саний массивов, содержит информацию только о структуре масси- вов. Что касается типа значений каждого из массивов, то он обычно определяется по первой букве имени массива. Однако тип значения массива может быть указан и явно, с помощью объявления типа. Заметим, что информация о структуре массива задается не обя- зательно о помощью объявления массивов — ее можно задать и с помощью объявлений типа или общих объектов.
7.4] ОБЪЯВЛЕНИЯ 419 Пример объявления массивов: DIMENSION Х(10), LY(3, 4) Это объявление говорит о том, что в данном программном модуле имя X является именем вектора, состоящего из 10 компонент, а имя LY — именем матрицы, состоящей из трех строк и четырех столбцов. Если имена X и LY не фигурируют в объявлениях типа, то вектору X предписывается тип вещественный, а матрице LY — тип целый (поскольку имя матрицы начинается буквой L). В фортране определен способ отображения любого массива на одномерный массив, т. е. фактически на линейный массив ячеек памяти машины, с помощью «функции линеаризации», которая определяет соответствие между элементами отображаемого массива и компонентами упомянутого одномерного массива. Это отображе- ние размещает матрицу, например, в памяти по столбцам. Объявление типа. Для определения типа значений, которые мо- жет принимать тот или иной объект программы (простая перемен- ная, массив или функция), в фортране широко применяется прин- цип «умолчания»: если имя объекта начинается одной из букв: I, J, К, L, М или N, то этому объекту предписывается тип целый; в противном случае ему предписывается тип вещественный (тип константы определяется ее изображением). Объявление типа ис- пользуется не для того, чтобы ввести в употребление новый объект, а для того, чтобы изменить или подтвердить неявное указание типа, а также для предписания типа двойной точности, комплексный или логический тем или иным объектам. Как уже отмечалось, объявле- ние типа может также содержать информацию о структуре массива, так что — в отличие от алгола — один массив может быть опреде- лен с помощью разных объявлений, и даже более того: один и тот же массив может быть определен в два приема — с помощью объяв- ления массивов (где задается информация о структуре массива) и с помощью объявления типа. Объявление типа имеет вид t »1( vit . . ., где t — указатель типа (INTEGER, REAL, DOUBLE PRECISION, COMPLEX или LOGICAL), а каждое Vt — простая переменная, имя массива, описание массива или имя функции. Примеры объявлений типа: REAL X, МАХ INTEGER В, MATR (20, 10), Р (35) DOUBLE PRECISION К, A, F Первое из этих объявлений подтверждает вещественный тип зна- чений объекта с именем X и предписывает этот тип объекту с име- нем МАХ (каждое из этих имен может быть именем простой перемен- ной, массива или внутренней функции). Второе объявление не толь- .14*
420 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН (Гл. 7 ко предписывает объектам с именами В и Р и подтверждает для объекта MATR тип целый, но и содержит информацию о массивах MATR и Р, так что эти имена могут и не фигурировать в объявле- ниях массивов. Третье объявление предписывает объектам с имена- ми К, А и F тип двойной точности (неявное указание типа может использоваться только для типов целый и вещественный); если ка- кое-либо из этих имен является именем массива, то оно должно встретиться в каком-либо другом объявлении (в составе описания массива) для указания структуры этого массива. Объявление эквивалентности имеет вид EQUIVALENCE (^), (kt)......(kn) где каждое kt — список вида аи а*,. . ., ат{ (т{^2), элементами ко- торого могут быть простые переменные и (или) переменные с по- стоянными индексами. Все переменные, образующие какой-либо список kt в объявле- нии эквивалентности', размещаются в памяти машины, начиная с одной и той же единицы памяти (такой единицей может быть ячей- ка), так что в результате трансляции и загрузки все эти элементы будут иметь один и тот же истинный адрес. Пример объявления эквивалентности: EQUIVALENCE (X, Y, А (3, 4), Т), (Р2, Р5) Наличие такого объявления приведет к тому, что значения простых переменных X, Y и Т будут размещены в той же ячейке памяти, что и элемент А (3,4) матрицы А; значения простых переменных Р2 и Р5 также будут размещены в одной и той же ячейке памяти. Объявление внешних имен. В фортране взаимодействие модулей по переменным величинам осуществляется через общие объекты, а взаимодействие по управлению обеспечивается с помощью внешних имен: если в каком-либо программном модуле используются внеш- ние по отношению к нему процедуры, то все имена этих процедур должны быть объявлены в качестве внешних имен. Для этой цели и предназначено объявление внешних имен, которое имеет вид EXTERNAL vt, . . ., vn где каждое Uj — имя внешней процедуры. Заметим, что в фортране должны использоваться реальные имена этих процедур, так что при составлении каждого модуля, вообще говоря, надо знать, какие имена были (или будут) даны используе- мым в нем внешним- процедурам. Это требование несколько затруд- няет независимое составление модулей и ограничивает свободу в выборе обозначений, что особенно неудобно при использовании библиотеки модулей, поскольку каждому из библиотечных модулей уже дано какое-то имя. Возникающие здесь трудности преодоле- ваются за счет использования редактора связей, при обращении .к которому можно задать информацию о том, какое конкретное имя процедуры надо поставить в соответствие тому или иному внешнему
7.4] ОБЪЯВЛЕНИЯ 421 имени, использованному в данном модуле для ссылки на эту внеш- нюю процедуру, если это имя не совпадает с реальным именем ис- пользуемой процедуры. Пример объявления внешних имен: EXTERNAL FUN, SUM, INT Объявление общих объектов используется для обеспечения вза- имодействия модулей по переменным величинам. Такое взаимодействие означает, что в разных модулях исполь- зуются одни и те же переменные величины (отдельные значения или массивы значений). Для ссылок на эти величины в каждом из моду- лей используются некоторые имена, вообще говоря, не совпадающие между собой. А поскольку в каждом модуле локализованы все ис- пользованные в нем имена, то даже одно и то же имя в разных моду- лях обозначает, вообще говоря, разные объекты. В связи с этим нужен некоторый аппарат, который позволял бы согласовывать между собой имена, использованные в разных модулях — так, что- бы в случае необходимости с помощью этих имен можно было ссы- латься на одно и то же значение. В основе такого аппарата в фортране также лежат общие блоки данных, с которыми мы уже познакомились в главе 6. Напомним, что общий блок данных представляет собой упорядоченный набор (одномерный массив) значений, при размещении которого в памяти машины выделяется массив ячеек памяти с последовательными адре- сами. Общие блоки данных не принадлежат какому-либо программ- ному модулю, но одинаково доступны для любого из них — поэтому они и называются общими. Каждый программный модуль может по своему усмотрению размещать используемые в нем величины в том или ином общем блоке — для этого и служат объявления общих объектов. Объявление общих объектов в простейшем случае имеет вид , COMMON/х/ bu b.........Ьп где х — имя общего блока, а каждое bt — простая переменная, имя массива или описание массива. Список объектов, содержащийся в таком объявлении, и говорит о том, какие именно объекты размещаются в общем блоке с именем х, причем эти объекты размещаются в общем блоке в том порядке, в котором они перечислены в объявлении. Пусть, например, в модуле с именем MODI дано объявление об- щих объектов: COMMON/BLOCK/A, В(3), X, Р где Р — имя квадратной матрицы второго порядка, а А и X — про- стые переменные. Это объявление говорит о том, что в последователь- ных позициях общего блока BLOCK размещаются следующие
422 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. 7 значения: A, Bi, В2, Вз, X, Pi,i, P2,i Pi, г> Р2>2 Для согласования двух модулей по переменным величинам надо в каждом из этих модулей (с помощью объявлений общих объектов) эти величины разместить в одном и том же общем блоке — так, что- бы эти величины оказались в одних и тех же его позициях (для до- стижения этой цели иногда приходится вводить в употребление прос- тые переменные или массивы, которые нигде больше в модуле не используются). В результате трансляции и загрузки имена вели- чин, размещенных в одной и той же позиции какого-либо общего блока, будут заменены на один и тот же абсолютный адрес. Таким образом, размещение величин в общих блоках позволяет отождест- влять значения, обозначаемые в разных блоках по разному. Так, если в модуле M0D2 дано объявление общих объектов COMMON/BLOCK/T, U, V, W, А где Т, U, V, W и А — простые переменные, то они в этом модуле будут обозначать те же самые значения, что и переменные А, В(1), В(2), В(3) и X соответственно в модуле MODI. Фортран разрешает использовать в программе несколько общих блоков данных, причем один из них может быть непомеченным (т. е. без имени). Поскольку во многих реальных программах достаточно иметь один общий блок, то такая возможность весьма удобна с практической точки зрения. 7.4.2. Объявления начальных данных. В фортране имеется возможность придать начальные значения некоторым переменным или элементам массивов без использования для этих целей каких-либо операторов (присваивания, ввода и т. д.). Такая возможность обеспечивается объявлением начальных данных, имеющим вид DATA kM kM .. ., kn/dn/ где каждое kt — список, элементами которого могут быть простые переменные и переменные с индексами, а каждое di — список кон- стант. В каждой паре списков ktldt] должно существовать взаимно-од- нозначное соответствие между элементами списка kt и списка dt — элементам из списка k, и придаются значения соответствующих эле- ментов из списка dt. Заметим, что в списках dt могут встречаться и текстовые константы; В языке предусмотрена возможность более компактного зада- ния списка констант dr. если перед какой-либо из констант этого списка записана конструкция п*, в которой п есть отличное от нуля целое без знака, то это эквивалентно записи данной константы п раз подряд через запятую.
7.5] ПРОЦЕДУРЫ И МОДУЛИ 423 Имеются определенные ограничения на те простые переменные и переменные с индексами, которые могут фигурировать в объявле- ниях начальных данных: эти объекты не должны входить в непоме- ченный общий блок, а если они входят в помеченный общий блок, то начальные значения им могут быть приданы только в специаль- ном модуле, который называется модуль-блок данных. Пример объявления начальных данных: DATA Al, А2, LVECT(l), Х/2*0.1,5,(0.5,2.)/ Наличие такого объявления влечет за собой придание (к началу выполнения программы) переменным А1 и А2 одного и того же на- чального значения, равного 0.1, первой компоненте массива LVECT (независимо от числа измерений этого массива) — значения 5, а переменной X, которой с помощью объявления типа должен быть предписан тип комплексный,— значения 0.5+2.0i. 7.5. Процедуры и модули В фортране различаются четыре категории процедур: встроенные функции, внутренние функции, внешние функции и внешние под- программы. Первые три категории процедур относятся к функциям (или процедурам-функциям'), а последняя категория — к подпро- граммам (или процедурам-подпрограммам}. Обращение к любой функции производится с помощью указателя функции как составной части арифметического или логического выражения. Назначение процедуры-функции состоит в том, чтобы выработать значение (числовое или логическое), которое принимает- ся в качестве значения указателя функции и можёт использоваться при вычислении выражения, в которое входит этот указатель функции. Обращение к внешней подпрограмме производится с помощью оператора вызова подпрограммы, который, как и всякий оператор, представляет самостоятельный этап вычислений. 7.5.1. Встроенные функции. В языке имеется фиксированный набор функций, имена которых считаются известными процессору, так же как и определяемые ими функциональные зависимости. К их числу относятся такие простей- шие функции, как вычисление абсолютной величины числа, вычис- ление остатка от деления двух чисел, получение вещественной или мнимой части комплексного аргумента, преобразование числовых значений из одного типа в другой и т. д. Для каждой из этих функ- ций в языке дано ее определение, присвоено ей имя, указано число аргументов и допустимые типы их значений, а также указан тип значения функции. Встроенные функции не требуют объявления в программе и мо- гут свободно использоваться в любом программном модуле.
424 (ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. 7 7.5.2. Внутренние функции. В каждом программном модуле можно ввести в употребление функции, являющиеся локальными для данного модуля. Каждая такая функция определяется при помощи объявления внутренней функции, которое имеет вид f(au at, . . йп)=е где f — имя определяемой функции, е — выражение, а каждое at есть формальный параметр. Тип значения функции определяется либо неявно, по имени функции (по тем же правилам, что и в случае простых переменных или массивов), либо задается явно с помощью объявления типа. Соответствие между типом f и типом е должно удовлетворять тем же правилам, что и в случае оператора присваивания. Формальные параметры используются в объявлении внутрен- ней функции лишь для указания типа, числа и порядка параметров функции, и поэтому могут совпадать с именами переменных того же типа, встречающимися где-либо еще в данном программном мо- дуле (но вне объявления этой функции). В фортране нет специ- фикаций формальных параметров, так что тип значений, которые они представляют, определяется по общим для фортрана правилам. Выражение е кроме формальных параметров может содержать только нетекстовые константы, простые переменные, указатели встроенных функций, указатели внутренних функций определен- ных ранее в данном модуле, и указатели внешних функций. В каждом программном модуле все определения внутренних функций должны предшествовать первому оператору этого модуля и следовать за объявлениями спецификаций, если таковые имеются. В указателе внутренней функции фактическим параметром может быть любое выражение того же типа, что и соответствующий ему формальный параметр. Пример объявления внутренней функции: KFUN(LX, Y)=(Y**LX+0.2)*(SIN (Y)+ALOG(Y**2)) Если имя этой функции не встречается в объявлениях типа, то по принципу умолчания (имя функции начинается буквой К) этой функции предписывается тип целый; из объявления видно, что это функция от двух параметров (аргументов) и что первый фактиче- ский параметр при обращении к ней должен задавать значение типа целый, а второй — вещественный; в выражении, записанном в пра- вой части, кроме формальных параметров и констант, фигурируют имена основных внешних функций SIN и ALOG (внешние функции будут рассмотрены в следующем разделе). 7.5.3. Внешние функции. Из синтаксиса объявления внутренней функции видно, что с его помощью можно задать только простейшие функциональные за-
7.5] ПРОЦЕДУРЫ И МОДУЛИ 425 висимости — такие, которые могут быть заданы в виде одного выра- жения (арифметического или логического). Более сложные функцио- нальные зависимости в фортране задаются с помощью внешних процедур-функций. Каждая внешняя функция определяется отдельным программ- ным модулем, который в этом случае называется модуль-функция. Этот модуль начинается с заголовка функции, за которым следует тело модуля. Заголовок функции, являющийся одним из типов объявлений, имеет вид t FUNCTION f(au а2„ . ,,ап) где t — пусто или один из спецификаторов типа значения функции, f — имя определяемой функции, а каждое az — динамический фор- мальный параметр. В теле модуля каждый из формальных параметров должен ис- пользоваться в качестве простой переменной, либо имени массива, либо имени внешней (по отношению к данному модулю) функции. Модуль-функция может содержать любые предложения, за ис- ключением заголовка блока данных, заголовка подпрограммы, дру- гого заголовка функции, а также любого предложения, которое явно или неявно ссылается на определяемую функцию (т. ё, запре- щаются рекурсивные обращения к функциям). Модуль-функция должен содержать по крайней мере один опе- ратор возврата, выполнение которого завершает выполнение дан- ного модуля. Как и в случае процедур-функций алгола, имя опрёделяемой функции должно встречаться в этом модуле в качестве имени пере- менной; в процессе выполнения модуля этой переменной должна быть присвоено значение, причем впоследствии это значение может как использоваться, так и изменяться (т. е. допустима, например, запись F=F+A, где F — имя определяемой функции). Значение этой переменной в момент выполнения оператора возврата и при- нимается в качестве значения указателя функции, с помощью кото- рого было произведено обращение к данной внешней функ- ции. Модуль-функция при своем выполнении может изменять значе- ния своих фактических параметров, что — наряду с вычислением значения функции — также является результатом выполнения мо- дуля-функции (т. е. допускается «побочный эффект» при вычисле- нии функций). Пример модуля-функции, с помощью которого определяется функция с именем NMAX, значение которой равно числу компонент некоторого вектора, превышающих значение R (заметим, что каж- дый модуль должен заканчиваться строкой, состоящей из букв Е, N и D):
426 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. 7 INTEGER FUNCTION NMAX(A, N) COMMON R DIMENSION A(N) NMAX = 0 DO 5 1 = 1, N 5 IF(A(I).GT.R) NMAX = NMAX +1 RETURN END Этот модуль-функция имеет два динамических параметра: А — имя вектора, и N — число его компонент. Параметр R является стати- ческим: с помощью этой переменной задается значение, используе- мое при выполнении данного модуля. Поскольку связь модулей по переменным производится с помощью общих объектов, то эта пере- менная размещена в первой позиции непомеченного общего блока данных. Тот программный модуль, который будет использовать мо- дуль-функцию NMAX, должен использовать какую-либо перемен- ную для обозначения этого значения, разместить (с помощью объяв- ления общих объектов) эту переменную также в первой позиции непомеченного общего блока и перед использованием функции NMAX присвоить этой переменной конкретное значение. В фортране зафиксирован набор внешних функций, называе- мых основными внешними функциями, который по своему составу весьма близок к набору стандартных функций алгола. Считается, что набор соответствующих модулей-функций имеется в системе про- граммирования, которая обеспечивает включение используемых мо- дулей в любую фортран-программу. 7.5.4. Внешние подпрограммы. Внешняя процедура-подпрограмма определяет некоторый само- стоятельный этап вычислений. Каждая процедура-подпрограмма .также определяется отдельным модулем — модулем-подпрограммой. Этот модуль начинается с заголовка подпрограммы, за которым сле- дует тело модуля. Заголовок подпрограммы, также являющийся одним из типов объявлений, имеет вид SUBROUTINE $ (аъ а2,. . ., ап) .или вид SUBROUTINE s где s — имя определяемой процедуры-подпрограммы, а каждое at — динамический формальный параметр. В теле модуля каждый из формальных параметров должен ис- пользоваться в качестве простой переменной, либо имени массива, либо имени внешней процедуры.
7.5J ПРОЦЕДУРЫ И МОДУЛИ 427 Имя. определяемой процедуры (в отличие от модуля-функции) не должно встречаться ни в одном предложении этого модуля, за ис- ключением заголовка подпрограммы. Тело! модуля-подпрограммы, за исключением отмеченной выше особенности, аналогично телу модуля-функции. В частности, ре- курсивные обращения к подпрограммам также запрещены. В теле этого модуля также должен содержаться хотя бы один оператор возврата — выполнением любого из них завершается выполнение модуля-подпрограммы. Для ссылки на подпрограмму используется оператор вызова подпрограммы (CALL). Фактические параметры, образующие спи- сок фактических параметров этого оператора, должны согласовы-, ваться по порядку, числу и типу с соответствующими формальными параметрами модуля-подпрограммы. Пример процедуры-подпрограммы: SUBROUTINE SUM (A, N) DIMENSION A(N) COMMON P(2), S S = 0 DO 20 1 = 1, N 20 S = S + A(I) RETURN END Здесь предполагается, что переменная, которой нужно присвоить значение вычисляемой суммы, при организации взаимодействия mq- дулей размещена в третьей позиции непомеченного общего блока. Чтобы переменную S разместить именно в этой позиции, исполь- зуется вспомогательный вектор Р, состоящий из двух компонент. 7.5.5. Модуль-блок данных. В отличие от рассмотренных выше модулей-процедур, этот мо- дуль относится к категории модулей-спецификаций. Назначение этого модуля состоит в придании начальных значений элементам по- меченных общих блоков данных. Этот специальный модуль может содержать только объявления типа, эквивалентности, общих объек- тов и начальных данных — именно с помощью объявлений началь- ных данных и производится придание начальных значений тем или иным объектам, входящим в общие блоки. Первым предложением этого модуля является заголовок модуля- спецификации общих блоков, имеющий вид: BLOCK DATA В одном таком модуле начальные значения могут быть приданы эле- ментам как одного, так и нескольких общих блоков данных. Поскольку действия над общими блоками данных являются весьма ответственными, ибо они касаются согласования модулей, об-
428 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН Тл. 7 разующих фортран-программу, к модулю-блоку данных предъяв- ляются требования, для выполнения которых автор этого модуля должен внимательно проследить использование общих блоков в каждом из модулей. / / 7.6. Ввод/вывод Фортран существенно учитывает тот факт, что исходные данные для решения какой-либо задачи на ЭВМ обычно предварительно на- носятся на внешние носители данных (например, на перфокарты), а в процессе выполнения программы они должны вводиться в па- мять машины, и что получаемые результаты должны выводиться из машины (на перфокарты, на печать и т. д.) для их последующего использования. При этом формы представления информации на внешних носи- телях и в машине могут существенно отличаться друг от друга. Так, числовые значения на внешних носителях обычно представ- ляются в десятичной (точнее, в двоично-десятичной) системе, при- чем разные числа могут иметь различное количество цифр в дроб- ной части. В машине‘же числа представляются, как правило, в двоичной системе, в стандартной форме. Кроме того, результаты ре- шения своей задачи пользователю желательно получить в наиболее удобном для него виде: в виде таблиц, оформленных по обычным из- дательским правилам, в виде готовых к использованию платежных ведомостей, накладных и т. д. Таким образом, как при вводе, так и при выводе требуется выполнение некоторых преобразований дан- ных при переходе от внешней формы представления данных к внут- ренней и наоборот. Для достижения всех этих целей в фортране имеются весьма ши- рокие возможности, которые обеспечиваются с помощью опера- торов ввода/вывода и объявлений формата. В дальнейшем изложении мы более подробно остановимся на одном частном, но практически наиболее широко используемом случае ввода/вывода — форматном выводе на печать. При форматном выводе приходится решать два вопроса: что именно (т. е. значения каких переменных или массивов) надо вы- водить и к а к надо выводить эти значения, т. е. какой формат должна иметь выдача на печать. Дело в том, что фазные числовые значения бывает удобно пе- чатать по-разному: с разным количеством цифр в дробной части, в форме с фиксированной или с плавающей точкой.' Кроме того, для улучшения наглядности выдаваемых результатов печатаемые числа можно соответствующим образом располагать на бумаге, снабжать их некоторыми заголовками или комментариями, разби- вать строки, печатаемые на рулоне бумаги, на отдельные порции, соответствующие обычным страницам и т. п. Другими словами, каж-
7.6] ввод/вывод 429 да я выдача может иметь свой формат, для чего необходимо провести . соответствующее редактирование выдаваемых на печать числовых значений. В свЬзи с этим каждый вывод на печать производится в резуль- тате выполнения оператора вывода во взаимодействии с определен- ным объявлением формата; при этом оператор вывода содержит в себе информацию о том, что надо выводить, а объявление формата — информаций) о том, как надо выводить, т. е. о формате, который должна иметь эта выдача. Заметим,! что в некоторых случаях (при решении небольших задач с небольшим количеством результатов) формат выдачи не имеет существенного значения. Чтобы упростить в таких случаях работу по составлению программы, в фортране предусмотрен и бес- форматный ввод и вывод; при бесформатном выводе на печать все выводимые числа представляются в некоторой стандартной форме и на рулоне бумаги располагаются также стандартным образом. При использовании такого стандартного формата в программе не требуетсяа'&о явного объявления. 7.6.1. Оператор форматного вывода. Оператор форматного вывода имеет вид WRITE (и, f) k или WRITE (u, /) где и — целое без знака или целочисленная простая переменная, / — метка объявления формата, k — список вывода. Значение и задает номер устройства, на которое должен произ- водиться вывод. Метка f определяет то объявление формата, во взаимодействии с которым должен производиться вывод. Список вывода k определяет те простые переменные и элементы массивов, значения которых подлежат выводу. Как видно из определения оператора вывода, список вывода может и отсутствовать. Однако отсутствие списка вывода вовсе не означает, что в результате выполнения такого оператора вывода на печать не будет выдано никаких символов. Как мы уже говорили, наряду с изображениями чисел на печать может выводиться и раз- личного рода информация редакционного характера (заголовки, комментарии и т. д.), которая задается в объявлении формата. Так что при отсутствии списка вывода на печать будет выдана совокуп- ность символов, определяемая объявлением формата. Для обеспечения большей гибкости и компактности задания пе- ременных (простых или с индексами), значения которых подлежат выводу, в языке допускаются довольно сложные структуры списка вывода. Список вывода — это либо простой список, либо простой список, взятый в круглые скобки, либо список с циклом, либо два списка,
430 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН 1Гл 7 разделенные запятой (как видно, определение списка вывода яв- ляется рекурсивным и допускает много возможных конструкций}. Простой список есть либо простая переменная, либо переменная с индексами, либо имя массива, либо два простых списка? разде- ленных запятой, например: / X, А(2), Y, В < где X и Y — простые переменные, а А и В — имена ( массивов. Если элементом списка является имя массива, то выводу подлежат все его компоненты, которые выводятся в порядке, определяемом функцией линеаризации. Как видно, с помощью простого списка, можно достаточно удобно задать вывод отдельных значений или всех компонент какого-либо массива, причем компоненты массива выводятся в фиксированном порядке. Список с циклом позволяет задать вывод части массива (напри- мер, одну строку матрицы), а также иной порядок вывода компо- нент массива по сравнению с порядком, определяемом простым списком. Список с циклом — это взятая в круглые скобки последователь- ность, состоящая из списка вывода и спецификации цикла, разде- ленных запятой. Спецификация цикла имеет вид i=fni, т2, т3 или i=mi, mt где i, mi, т2 и т9 определяются так же, как и в операторе цикла, и имеют тот же смысл. Область действия спецификации цикла — это список, входящий в состав списка с циклом. Например, список с циклом (A(2,J), J = 1,20,1) задает вывод первых двадцати компонент второй строки матрицы А, а список ((A(I,J),J = 1,N), 1=1, М) задает вывод элементов матрицы А (состоящей из т строк и п столб- цов) по строкам, тогда как в случае простого списка его элемент А задавал бы вывод этой матрицы по столбцам. Элементы списка считаются упорядоченными в соответствии о их вхождением в список при его просмотре слева направо. Это упо- рядочение элементов в списке с циклом имеет место для каждого очередного повторения цикла. 7.6.2. Объявление формата. Список вывода в операторе вывода определяет только значения, подлежащие выводу, ничего не говоря о том, как должна быть оформлена сама эта выдача, т. е. получаемый печатный документ. Для задания дополнительной информации, определяющей формат этой выдачи, и служит объявление формата.
7.61 ВВОД/ВЫВОД 431 Iiie на печать последовательности символов объединя- ли, каждая из которых печатается на одной строке ру- . В связи с этим длина записи не должна превышать ий в строке рулона на используемом печатающем уст- ачестве стандарта для печатающих устройств принята и в 128 позиций). эжет представлять одну или несколько логических еди- ации, являющихся элементами этой записи. Такими являются отдельное числовое или логическое значение, екст и т. д. В связи с этим запись делится на поля, в сражаются отдельные элементы записи. Объявление еделяет длину каждой выводимой записи, число и раз- мер полей в ртой записи, ту форму, в которой изображается каж- дый элемент записи в соответствующем поле, а также расположение печатаемых записей по строкам рулона бумаги: Объявление формата имеет вид: FORMAT (qt 4 Zi tt z2. . . t^z^t^) где a q2 — серия наклонных черт или пусто, каждое tt — описа- тель поля или группа описателей полей, а каждое Zt — разделитель полей. Конструкция, следующая за ключевым словом FORMAT, назы- вается спецификацией формата. Каждое объявление формата долж- но быть помечено. Разделители полей служат для разделения друг от друга опи- сателей полей и (или) групп описателей полей — такими раздели- телями являются наклонная черта и запятая; серия наклонных черт также является разделителем полей. Каждая наклонная черта , в объявлении формата всегда является и разделителем записей, о ко- торых говорилось выше. Описатели полей служат для указания ширины поля для каж- дого из элементов записи, а также определяют расположение по позициям этого поля символов, изображающих данный элемент за- писи; если этот элемент представляет выводимое значение, то опи- сатель поля определяет способ преобразования этого значения при переходе от его внутреннего представления в машине к последо- вательности символов в записи. 7.6.2.1. Описатели полей и их действие. Имеется девять типов описателей полей, число которых определяется, в частности, раз- нообразием типов данных, допустимых в языке. Мы рассмотрим наиболее часто используемые основные описатели полей: F w.d Е w.d I w п X п Н h2 h2. . . hn
432 , ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН (Гл. 7 где w. d и п — отличные от нуля целые без знака, каждое /— не- который символ, а буквы F, Е, I, X и Н называются кодамшпреоб- разований*. они указывают способ преобразования и редактирования при переходе от внутреннего представления выводимой информации к внешней последовательности символов (буква F означает «фик- сированная точка», Е — «плавающая точка», I — «целое», X — «пробел», Н — «символ»). / Первые три типа описателей полей используются при выводе ве- щественных и целочисленных значений. Каждому из этих описа- телей полей в объявлении формата должен соответствовать элемент списка вывода в операторе вывода. f Последние два типа описателей используются дл$у вывода тек- стовой информации, которая содержится в самих этирс описателях, так что им не ставятся в соответствие какие-либо элементы списка вывода. Каждый описатель задает ширину поля в выводимой записи, при- чем последовательность символов, получаемая при переходе от внутреннего представления информации к внешнему в соответствии с действием данного описателя, всегда размещается в правых по- зициях этого поля, а в остающиеся незанятыми левые позиции вставляются пробелы. Описатель поля Fw.d указывает, что в очередное поле записи, состоящее из w позиций, будет помещена последовательность сим- волов, изображающая вещественное число в форме с фиксирован- ной точкой, причем в дробной части числа оставляется d цифр с округлением последней из них. Знак плюс перед числом может быть заменен пробелом. Этому описателю поля должен соответствовать элемент списка вывода, который должен иметь значение типа ве- щественный. Если, например, описателю поля F 9.4 в списке вывода соответ- ствует переменная, значение которой к моменту вывода равно зна- чению л«3.1415926, то в правых позициях очередного поля в за- писи, состоящего из 9 позиций, будет размещена одна из последо- вательностей символов: +3.1416 или 3.1416, а в остальные (левые) позиции прля будут вставлены пробелы. Описатель поля Fw.d указывает, что в очередном поле записи, состоящем из w позиций, будет помещена последовательность сим- волов, изображающая вещественное число в форме с плавающей точкой. Стандартная форма изображения числа в этом случае имеет вид g Q. Xi х2. . .xrfY где означает знак числа (знак плюс может отсутствовать), хт х2 • . . xd — старшие d цифр мантиссы округленного выводимого значения, Y — десятичная экспонента, которая может иметь вид, E±yi У2 или вид у2 у3, где у^ у2 и у3— цифры, причем в пер-
7.6] ввод/вывод 433 вом из\ этих видов экспоненты знак плюс может быть заменен пробелок. Значение соответствующего элемента списка вывода для данно- го описателя поля также должно иметь тип вещественный. Так, если описателю поля Е12.4 в списке вывода соответствует элемент, значение которого равно значению л, то в правых пози- циях поля, содержащего 12 позиций, будет помещена одна из сле- дующих последовательностей символов: 0.3142Ё+01 0.3142В 01 0.3142 + 001 01 001 (перед каждрй^из них может быть помещен знак плюс). Описатель поля указывает, что в очередном поле записи, со- стоящем из w позиций, будет помещена последовательность симво- лов, изображающая целое число, причем знак плюс у числа может быть опущен; Значение соответствующего элемента списка вывода должно иметь тип целый. Например, если описателю поля 16 в списке вывода соответст- вует элемент, значение которого типа целый равно числу дней в обычном году, то в правых позициях поля, содержащего 6 пози- ций, будет размещена одна из двух последовательностей символов: +365 или 365. Описатель поля nH/ij. . . hn указывает, что в очередное поле формируемой записи, состоящее из п позиций, заносятся п симво- лов (включая пробелы), которые указаны в самом описателе поля вслед за буквой Н. Этому описателю поля не ставится в соответствие какой-либо элемент из списка вывода — он сам задает текстовую информацию, подлежащую выводу. Описатель поля пН чаще всего используется для печати заголов- ков или комментариев к печатаемым результатам, для управления расположением печатаемых чисел по рулону бумаги и т. д. Например, для размещения в очередном поле записи, которая затем будет выведена на печать, заголовка МАССИВ X: в объявлении формата можно задать описатель поля 9НМАССИВ X: (между буквами В и X помещен один пробел, который здесь яв- ляется значащим и учитывается при задании числа п, указываемого в начале описателя поля). Описатель поля пХ -означает занесение во все п позиций оче- редного поля записи пробелов, так что этот описатель эквивалентен описателю пН, у которого вслед за буквой Н содержатся п пробе- лов. Описатель пХ используется также для целей редактирования — главным образом для управления размещением символов в строке.
434 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. 7 Например, три последовательные описателя полей / 6НМАССИВ, 5Х, 2НХ: / задают вывод на печать двух последовательностей символов МАССИВ я X: с пятью пробелами между ними. / В конкретных реализациях фортрана обычно предусматриваются дополнительные удобства для редактирования. Например, в каче- стве стандарта предполагается, что каждая очередная/запись при выводе на печать будет печататься в очередной строке рулона бу- маги. Чтобы оставить на бумаге несколько чистых строк, надо пре- дусмотреть вывод соответствующего числа пустых записей. В неко- торых реализациях принимается соглашение о том, что первый символ каждой записи не печатается, а управляет продвижением ру- лона бумаги. При этом выделяется набор символов, которые могут использоваться для этих целей, и каждому из них придается опре- деленный смысл: один символ может означать отмену продвижения рулона, и тогда данная запись будет отпечатана в той же строке, что и предыдущая запись (т. е. печать записей с наложением); дру- гой символ может означать продвижение рулона на одну строку; третий — продвижение на начало следующей страницы. В послед- нем случае фиксируется определенное число строк, образующих такую «страницу» на рулоне бумаги, а в процессе вывода на печать ведется учет числа израсходованных строк в текущей странице. Такая возможность особенно удобна при выводе на печать большого объема информации, когда для последующего использования полу- ченных результатов этот рулон удобно разрезать на стандартные части (страницы), которые можно, например, затем сброшюровать. 7.6.2.2. Спецификации повторений. Иногда какой-либо описа- тель поля или группа описателей полей должны применяться по- следовательно несколько раз. Если, например, список вывода со- стоит из четырех переменных типа вещественный, значения которых надо отпечатать последовательно в одной строке рулона бумаги в форме с плавающей точкой с пятью цифрами в дробной части, то для этого четыре раза надо использовать, например, описатель поля Е20.5 (заданная здесь ширина поля обеспечивает и отделение друг от друга изображений чисел на бумаге, поскольку каждое, из них занимает 12 правых позиций поля, содержащего 20 позиций). Что- бы избежать громоздкой записи объявления формата из-за боль- -шого количества содержащихся в нем описателей полей, язык пре- дусматривает возможность более компактного задания нужных опи- сателей полей с помощью спецификации повторений. Так, перед каждым из описателей полей типа Е, F и I может быть записано отличное от нуля целое без знака г, называемое счетчиком повторений. Этот счетчик указывает, сколько раз дол- жен быть применен следующий за ним описатель поля. Например, запись 315 эквивалентна записи 15, 15, 15 (этот способ непосредст-
7.6] \ ВВОД/ВЫВОД 435. венно неприменим к описателям типа X и Н, поскольку запись каж- дого из них начинается целым без знака п). Повторение какого-либо фрагмента спецификации формата, в. который могут входить как описатели, так и разделители полей,, достигается путем заключения этого фрагмента в скобки, причем перед левой скобкой можно указать счетчик повторений (если счет- чик повторений не задан, то число повторений принимается рав- ным единивд). Такая конструкция, при которой в скобках фигури- руют толькд( описатели и разделители полей, называется основной группой повторений, например: 3 (FlO.sj 5 (F6.2.E12.3/) 6 (ЗХ) Более сложные группы повторений можно образовывать заключе- нием в скобки описателей полей, разделителей полей или основных групп. Для каждой такой группы также можно задать счетчик по- вторений, например 20(9НВАРИАНТ N, I2/5(F10.5)/). Внешние скобки спецификации формата не рассматриваются как скобки, ограничивающие группу. 7.6.3. Взаимодействие форматного управления со списком вы- вода. Начало выполнения оператора форматного вывода инициирует форматное управление. Каждое действие форматного управления определяется как очередным элементом списка вывода, если та- ковой имеется, так и очередным описателем поля в спецификации формата. Если в операторе имеется список вывода, то в специфи- кации формата должен существовать по крайней мере один описа- тель поля, отличный от пН и пХ. Процесс форматного вывода как бы распадается на два этапа,, которые повторяются циклически: формирование текущей записи- и печать сформированной записи. Под воздействием форматного управления вывод записи на пе- чать происходит каждый раз, когда спецификация формата требует перехода к новой записи. Завершение форматного управления также- влечет за собой вывод текущей записи. Интерпретация формата производится слева направо. Каждому основному описателю типа F, Е и I соответствует один элемент-- списка вывода: как только при интерпретации формата встречается такой описатель, проверяется, имеется ли соответствующий эле- мент, задаваемый списком вывода. Если такой элемент существует, то форматное управление передает преобразованную соответствую- щим образом информацию от элемента в формируемую запись и про- должает свою работу. Если соответствующего элемента нет, то фор- матное управление завершается. Каждому основному описателю.
436 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН /[Гл. 7 типа Н или X не соответствует никакой элемент, задаваемый спис- ком вывода — в этом случае форматное управление передает в фор- мируемую запись информацию, содержащуюся в самом таком опи- сателе. [ Если при интерпретации формата встречается наклонная черта, то это означает переход к новой записи; если к этому времени была сформирована запись, то она выводится на печать. Перед выводом каждой записи рулон бумаги продвигается на одну строку. Если форматное управление дойдет до внешней закрывающей скобки спецификации формата, то проверяется, остались ли необ- работанные элементы в списке вывода. Если таких элементов нет, то форматное управление завершается; в противном случае осуще- ствляется переход к следующей записи и управление возвращается к той спецификации повторений группы, которая заканчивается последней предшествующей правой скобкой, а при ее отсутствии — к внешней левой скобке спецификации формата. Процесс форматного вывода проиллюстрируем на примере опе- ратора WRITE (1,5) X, Y, Z, U (считая, что печатающее устройство имеет номер 1) при следующем объявлении формата, помеченного меткой 5: 5 FORMAT (10НРЕЗУЛЬТАТ:/3(5Х, F10.4/)) Форматное управление, которое инициируется началом выполне- ния оператора вывода, начинает интерпретировать спецификацию формата в объявлении. При просмотре спецификации формата сна- чала выявляется описатель поля типа Н. Его интерпретация не тре- бует обращения к списку вывода — в первое поле первой формируе- мой записи просто передается последовательность из 10 символов РЕЗУЛЬТАТ:. Далее в спецификации формата выявляется разде- литель поля /. Поскольку этот разделитель поля одновременно яв- ляется и разделителем записей, то он означает конец очередной (пер- вой) записи и переход к следующей (второй) записи. При этом сфор- мированная первая запись выдается на печать с предварительным продвижением рулона бумаги на одну строку. При дальнейшей интерпретации выявляется группа со счетчиком повторений, равным трем. Эго значит, что заключенный в скобки фрагмент 5Х, F10.4/ следует проинтерпретировать три раза. При первой интерпретации этого фрагмента формируется запись, в начало которой по описате- лю поля 5Х помещается пять пробелов; интерпретация следующего описателя поля F10.4 требует очередного (в данном случае первого) элемента списка вывода — таким элементом является (веществен- ная) переменная X: ее значение преобразуется по правилам, зада- ваемым описателем типа F, и последовательность символов, изоб-
7.7] СТРУКТУРА ПРОГРАММЫ И МОДУЛЕЙ 437 ражаюшая это значение, помещается в очередное поле формируемой записи. \Далее в рассматриваемом фрагменте встречается раздели* тель/, означающий конец записи и переход к следующей, поэтому сформированная запись, которая содержит изображение значения X, печатается в следующей строке рулона бумаги. Вторая и третья интерпретации рассматриваемого фрагмента производятся аналогичным образом, только при второй интерпрета- ции описателя поля F10.4 из списка вывода выбирается в качестве очередного его элемента переменная Y, а при третьей — перемен- ная Z. В итоге будут сформированы и отпечатаны в двух следующих строках записи, содержащие изображения значений Y и Z. Затем форматное управление дойдет до внешней закрывающей скобки. В этот момент выясняется, что в списке вывода остался не- обработанный элемент — переменная U, поэтому форматное управ- ление требует начать новую запись (а поскольку в текущую запись ничего не передавалось, то она на печать не выводится), и управле- ние возвращается на начало группы со счетчиком повторений 3. Теперь при первой интерпретации фрагмента, заключенного в скоб- ки, формируется и печатается запись, содержащая изображение значения U, а при второй интерпретации для описателя поля F10.4 в списке вывода не оказывается соответствующего элемента, поэтому форматное управление (а тем самым и выполнение оператора выво- да) завершается. 7.7. Структура программы и модулей Программа на фортране состоит из одного главного модуля и произвольного числа других модулей, среди которых могут быть модули-функции, модули-подпрограммы и модули-блоки данных. Выполнение программы начинается с выполнения главного модуля. Главный модуль состоит из тела модуля, за которым следует за- ключительная строка. Тело модуля может содержать объявления спецификаций, внут- ренних функций, начальных данных и форматов, а также операто- ры, причем в теле модуля должен присутствовать по крайней мере один оператор. При этом все объявления спецификаций должны предшествовать объявлениям внутренних функций, а эти последние должны предшествовать операторам.' Объявления форматов и на- чальных данных могут располагаться в теле модуля где угодно, в частности — между операторами. Заключительная строка — это строка особого вида, которая в позициях 1—6 содержит пробелы, а в позициях 7 — 72 — пробелы и буквы Е, N и D, причем эти буквы в указанном порядке могут размещаться в любых из этих позиций. Модуль-функция состоит из заголовка функции, за которым сле- дует тело модуля с последующей заключительной строкой.
438 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН /[Гл. 7 Модуль-подпрограмма отличается от .модуля-функции /только тем, что он начинается заголовком подпрограммы. / Модуль-блок данных представляет собой последовательность,.об- разованную заголовком спецификации блока данных, набором со- ответствующих спецификаций для каждого из помеченных общих блоков, элементам которых придаются начальные значения, набо- ром объявлений начальных данных и заключительной строкой, рас- положенных в указанном порядке. 7.8. Пример программирования на фортране В качестве иллюстрации программирования на фортране рас- смотрим следующую несложную задачу. Пусть даны вещественные а, b и целое п. Нужно в точках х,= a+ih (i=0,l,. . ., п), где h=(b—а)/п, вычислить значения функций fi(x)—x2, f2(x)=sin х, /з(х)=Кх2+1—1. Результаты вычислений нужно получить в виде отпечатанной таблицы следующего формата: ТАБЛИЦА ФУНКЦИЙ X Х**2 SIN(X) SQRT(X**2+1)—1 fl (*о) fг (хо) f з (*e) fl (*1) h (*1) Х„ AW fM СОСТАВИЛ: (автору Заголовки колонок и саму таблицу чисел нужно заключить в рамку. При решении этой задачи мы не будем использовать основную внешнюю функцию SQRT (х), а для иллюстрации возможностей фортрана извлечение корня с задаваемой точностью е>0 оформим в виде внешней процедуры-функции с именем КОР, которая имеет один динамический параметр (значение аргумента) и один статичес- кий параметр (задаваемая точность). Непосредственную печать таблицы по имеющимся векторам, компоненты которых и соответствуют печатаемым колонкам чисел, выделим в отдельную процедуру, которую определим с помощью отдельного модуля-подпрограммы с именем TABL. Будем считать, что все параметры этой процедуры являются статическими. При наличии этих процедур задача главного модуля будет за- ключаться в задании исходных значений соответствующим перемен- ным, получении одномерных массивов выводимых на печать значе- ний и окончание вычислений. Вовремя своего выполнения главный модуль будет обращаться к модулю КОР для вычисления значения квадратного корня и к модулю TABL — для печати таблицы.
7.81 \ ПРИМЕР ПРОГРАММИРОВАНИЯ НА ФОРТРАНЕ 439 Главный модуль. Сначала составим часть модуля (без объявле- ния спецификаций), а затем дополним его необходимыми объявле- ниями.! В данном модуле мы могли бы' обойтись без внутренних функ- ций, нос целью иллюстрации этой возможности языка мы введем в употребление две внутренние функции, дав следующие их объяв- ления: F1(X)=X*«2 F3(X)=KOP (Х*«2+1.0)—1. Здесь в арифметическом выражении второго объявления использо- ван указатель внешней функции с именем КОР. Далее запишем последовательность операторов, с помощью ко- торых получаются векторы АХ, AF, AG и АН, компоненты которых и образуют соответствующие столбцы нашей таблицы. При этом следует заметить, что компоненты векторов логично было бы пере- нумеровать от 0 до п, однако в фортране нумерация компонент всегда должна начинаться с единицы, так что их придется перенуме- ровать от 1 до п+1. Компоненты векторов естественно получать в цикле, параметр которого должен изменяться от 1 до п+1, однако в записи вида 1=/П1, /П2, ГПз которая фигурирует в операторе цикла, в качестве mlt т2 и т3 мо- гут использоваться только целые без знака или простые переменные. Поэтому придется ввести в употребление вспомогательную перемен- ную М, которой и присвоим значение п+1, а исходное значение п обозначим через ММ. Тогда последовательность операторов этого модуля можно записать так: Н = (В—А)/ММ М=ММ-)-1 Х = А DO 5 1 = 1, N, 1 АХ(1) = Х AF(I) = F1(X) AG(I)=SIN(X) • AH(I) = F3(X) 5 Х=Х+Н После того как вычисление векторов закончено, обратимся к проце- дуре-подпрограмме TABL (которая не имеет динамических парамет- ров) с помощью оператора вызова подпрограммы, затем запишем оператор останова и заключительную строку модуля: CALL TABL STOP END
440 w ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН уТл. 7 Читатель, вероятно, заметил, что в тексте модуля допущена опис- ка: в операторе цикла вместо М записано N. Для ее устранения мож- но было бы переписать оператор цикла, но если бы в модул|е имя № встречалось много раз, то такой способ устранения описк^ был бы неудобен. Поэтому проще воспользоваться объявлением эквива- лентности EQUIVALENCE (М, N) которое надо добавить в начале модуля, благодаря которому имена М и N будут обозначать одно и то же значение. В составленной последовательности операторов еще не преду- смотрено присваивание исходных значений переменным А, В, ММ и EPS. Вообще говоря, это можно было бы сделать с помощью операторов ввода, но бывает удобнее для этих целей использовать объявления начальных данных: если мы хотим решить нашу за- дачу для а=0, 6=1, п=20 и е= 10“5, то эти конкретные значения можно придать соответствующим переменным с помощью объяв- ления: DATA А, В, ММ, EPS/0.0,1.0,20,.00001/ Теперь надо еще задать информацию об используемых масси- вах и обеспечить взаимодействие модулей. Здесь следует учесть, что в фортране нельзя объявлять массивы с переменными грани- цами (если только имена массивов и их границы не являются фор- мальными параметрами), поэтому приходится фиксировать размер каждого массива применительно к конкретному значению п. Ин- формацию о массивах можно задать с помощью объявлений мас- сивов: DIMENSION АХ(21), AF(21), AG(21), АН(21) Для связи главного модуля с внешними процедурами КОР и TABL надо прежде всего эти имена объявить в качестве внешних имен: EXTERNAL КОР, TABL Связь модулей по переменным величинам осуществляется с по- мощью общих блоков данных. Модуль КОР должен использовать значение е, задаваемое в главном модуле, но переменную EPS нельзя включать в непомеченный общий блок (а именно такой блок удобнее всего использовать в данной задаче), поскольку этой пере- менной было придано начальное значение с помощью объявления начальных данных (фортран запрещает использование в объявле-
7.8] ’ ПРИМЕР ПРОГРАММИРОВАНИЯ НА ФОРТРАНЕ 441 нии начальных данных имен объектов, включенных в непомечен- ный общий блок). В связи с этим в общий блок включим вспомога- тельную переменную Р, которой надо присвоить значение EPS с помощью оператора присваивания. Модуль TABL должен ис- пользовать значение М и полученные в главном модуле массивы. Итак, с помощью объявления общих объектов введем в употребле- ние непомеченный общий блок, задав определенное размещение в нем указанных объектов, например: COMMON Р, М, АХ, AF, AG, АН Вообще говоря, объявление общих блоков удобнее писать, когда все модули уже составлены — в противном случае осталь- ные модули должны спривязываться» к данному объявлению, что не всегда удобно, хотя для нашей задачи это вполне приемлемо. В данном модуле можно обойтись без объявлений типа, по- скольку использованные в нем имена объектов таковы, что принцип умолчания обеспечивает предписывание всем использованным объек- там нужных типов их значений. Итак, окончательно главный модуль будет иметь вид: EXTERNAL КОР, TABL COMMON Р, М, АХ, AF, AG, АН DIMENSION АХ (21), AF (21), AG (21), АН (21) EQUIVALENCE (М, N) DATA А, В, ММ, EPS./0.0,1.0,20,.00001/ Fl (Х)=Х**2 F 3 (Х)=КОР (X*» 2+1.0)—1. Н=(В—А)/ММ Х=А P=EPS DO 5 1 = 1, N, 1 АХ (I)=X AF (I)=F1 (X) AG (I)=SIN (X) AH (I)=F 3 (X) 5 X=X+H CALL TABL STOP END Модуль-функция КОР задает процедуру вычисления квадрат- ного корня у=Ух с задаваемой точностью е. В основу этой проце- дуры положим итерационный метод Ньютона: уп+1=Уп+(х/уп— —Уп№, причем в качестве начального приближения мы всегда будем брать у9—1.
442 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. 7 Модуль-функция должен начинаться с заголовка функции. Поскольку имя функции начинается буквой К, а значение функции должно иметь тип вещественный, то в заголовке функции должен быть указан ее тип: REAL FUNCTION КОР (X) Значение аргумента является динамическим параметром этого модуля, а значение заданной точности (обозначим его через LAM- BDA) — статическим параметром. Поскольку переменная LAMBDA должна представлять то же значение, что и переменная Р в глав- ном модуле, включим ее в общий блок, разместив ее в той же по- зиции, что и переменную Р. Поскольку имя этой переменной начи- нается буквой L, то ей в явном виде надо предписать тип вещест- венный с помощью объявления типа. Напомним, что модуль-про- цедура обязательно должен содержать в себе оператор возврата. Итак, модуль КОР можно записать в виде: REAL FUNCTION КОР (X) REAL LAMBDA COMMON LAMBDA KOP=1.0 10 V==(X/KOP—KOP)/2 IF (ABS (V).LT.LAMBDA) GO TO 20 KOP=KOP+V GO TO 10 20 RETURN END Модуль-подпрограмма TABL. Этот модуль предназначен для печати в виде таблицы значений, заданных компонентами четырех векторов — обозначим их через MX, MF, MG и МН — которые получаются в главном модуле. Здесь будет использоваться и зна- чение, равное числу компонент у векторов — обозначим его че- рез N. По этим объектам модуль TABL должен взаимодействовать с главным модулем, поэтому их надо соответствующим образом разместить в общем блоке данных. Для этого в первой позиции общего блока разместим некоторую переменную Р, которая нужна только для того, чтобы занять первую позицию общего блока, и никак в этом модуле не используется. Поскольку имена всех мас- сивов здесь начинаются буквой М, в объявлении типа предпишем им тип вещественный. Печать таблицы произведем в три этапа: сначала отпечатаем заголовок таблицы и заголовки столбцов, затем — числа, входя- щие в таблицу и, наконец, завершим ее оформление. В данном мо- дуле будут использоваться строки-продолжения, однако при их
7.81 ПРИМЕР ПРОГРАММИРОВАНИЯ НА ФОРТРАНЕ 443 использовании в объявлении формата необходимо учитывать, что в описателе поля иН пробелы являются значащими, поэтому внутри этого описателя поля опасно переходить на следующую строку. Это связано с тем, что обычно строка текста на бланке не занимает все 72 позиции, так что на перфокарте последние позиции будут содержать пробелы, а при трансляции учитываются все позиции перфокарты, по 72-ю включительно. При записи текста модуля будем иметь в виду, что объявления форматов могут находиться в любом месте модуля (между его за- головком и заключительной строкой). Заметим еще, что в исполь- зуемой здесь реализации фортрана выделен частный случай опера- тора вывода, вывод на печать. Этот оператор имеет вид PRINT /, к где f — метка объявления формата, а к — список вывода (что эквивалентно оператору вывода WRITE (1, f) к если устройство с номером 1 есть устройство печати). В этой реализации языка первый символ каждой выводимой записи является управляющим — сам он на печать не выводится, а управляет продвижением рулона бумаги непосредственно перед печатью выводимой записи. В частности, символ пробел означает продвижение рулона на одну строку. При печати начала таблицы сначала отпечатаем ее общий за- головок, начиная с 23-й позиции строки (позиции строк будем ну- меровать с единицы). Для этого запишем следующий фрагмент в объявлении формата для формирования и вывода соответствую- щей записи: 23Х.15НТАБЛИЦА ФУНКЦИЙ/ Эта запись будет состоять из двух полей: первое из них будет со- держать 23 пробела, а второе — заголовок таблицы. Наклонная черта в конце этого фрагмента означает конец формирования записи и вывод ее на печать. Первый пробел в этой записи, как всегда, яв- ляется управляющим — он обеспечит продвижение рулона на одну строку, после этого в очередной строке рулона будет отпечатан заголовок, начиная с 23-й позиции строки. В следующей строке рулона отпечатаем символ * в позициях с 10-й по 53-ю включительно, для чего запишем в объявлении фор- мата фрагмент 10Х.44 (1Н*)/ Далее отпечатаем строку, содержащую символ * в позициях 10 и 53, а между ними — заголовки столбцов таблицы: X (в пози- ции 14), Х**2 (в позициях 21—24), SIN (X) (в позициях 29—34)
444 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. 7 и SQRT(X**2+1)—1 (в позициях 38—51). Печать такой строки обеспечивается фрагментом формата 10Х,1 Н*,ЗХ, 1 HX,6X,4HX**2,4X,6HSIN(X),3X, 14HSQRT (Х**24-1)—1,1Х,1Н*/ Заголовок таблицы завершим строкой, содержащей символ » во всех позициях с 10-й по 53-ю включительно. Печать такой строки обеспечивается уже знакомым нам фрагментом 10Х.44 (1Н«) Итак, печать заголовка таблицы произведем по оператору вы- вода (с пустым списком вывода, поскольку здесь числовые значе- ния на печать не выводятся) PRINT 10 при объявлении формата с меткой 10: FORMAT (23Х, 15НТАБЛИЦА ФУНКЦИЙ/ 10Х,44 (1Н*)/10Х, 1Н*. ЗХ, 1НХ, 6Х, 4НХ**2, 4Х, 6HS1N (X), ЗХ, 14HSQRT (Х**2+1)—1, IX, 1Н*/ 10Х, 44 (1Н*)) Основную часть таблицы будем печатать построчно в цикле. При каждом выполнении тела цикла будем печатать очередную строку, которая содержит символ * в позициях 10 и 53, а между ними — очередное значение аргумента X (в позициях 11—16) и соответствующие ему значения функций (в позициях 17—25, 26—35 и 36—50 соответственно). При этом значение X будем печатать в форме с фиксированной точкой с двумя цифрами в дробной части, значение х2 — в форме с фиксированной точкой с четырьмя циф- рами в дробной части, значение sin (х) — с фиксированной точкой и пятью цифрами в дробной части, а значение Кх2 +1 — 1 —в форме с плавающей точкой с пятью цифрами в мантиссе. Напомним, что изображение каждого числа будет печататься всегда в правых позициях соответствующего поля — мы используем это обстоя- тельство для формирования колонок чисел в таблице). Таким образом, печать i-й строки таблицы можно осуществить с помощью оператора PRINT 20, MX (I), MF (I), MG (I), MH (I) при объявлении формата с меткой 20: FORMAT (10Х.1Н*. F6.2, F9.4, F10.5, Е15.5, 2Х, 1Н*) Поскольку здесь формируется и выводится только одна запись, то задавать ее окончание с помощью наклонной черты нет необходи- мости.
?.8J ПРИМЕР ПРОГРАММИРОВАНИЯ НА ФОРТРАНЕ 445 Печать таблицы завершим печатью строки, содержащей сим- вол * во всех позициях с 10-й по 53-ю включительно, и строки, содержащей подпись автора, например СОСТАВИЛ: КОТОВ Е. А., в поле, начинающемся с позиции 30. Поскольку значения пере- менных здесь также не выводятся, то эту печать можно сделать с помощью оператора с пустым списком вывода PRINT 40 при объявлении формата с меткой 40: FORMAT (10Х,44(1 Н*)/30Х,20НСОСТАВИЛ: КОТОВ Е.А.> Для иллюстрации возможностей фортрана в этом модуле мы не будем давать объявлений массивов, а необходимую информацию об используемых массивах зададим в объявлении общего блока данных и объявлении типа. Теперь приведем текст модуля TABL: SUBROUTINE TABL С ЭТО МОДУЛЬ ПЕЧАТИ ТАБЛИЦЫ COMMON Р, N, МХ(21), MF(21), MG(21), 1 МН (21) REAL MX, MF, MG, MH PRINT 10 с ПЕЧАТЬ ЗАГОЛОВКА 10 FORMAT(23X, 15НТАБЛИЦА ФУНКЦИЙ/ 1 10X, 44(1Н*)/10Х, 1H«, ЗХ, 1HX, 6X, 2 4HX**2, 4X, 6HSIN(X), 3X, 3 14HSQRT(X*»2+1)—1, IX, 1H*/ 4 10X, 44(1 H*)) с ДАЛЕЕ ПЕЧАТАЮТСЯ ЧИСЛА DO 30 1 = 1, N PRINT20, MX(I), MF(I), MG(I), MH(I) 20 FORMAT(10X, 1H*. F6.2, F9.4, F10.5, 1 E15.5, 2X, 1Н») 30 CONTINUE PRINT 40 С ЗАВЕРШЕНИЕ ОФОРМЛЕНИЯ ТАБЛИЦЫ 40 FORMAT (10X, 44(1 H*)/ 1 30X, 20НСОСТАВИЛ: КОТОВ E.A.) RETURN END Ниже приводится типографская копия выдачи на печать, полу- ченной в результате выполнения на реальной ЭВМ составленной нами фортран-программы:
446 ЯЗЫК ПРОГРАММИРОВАНИЯ ФОРТРАН [Гл. 7 Таблица 7.1 ТАБЛИЦА ФУНКЦИЙ **************************************************** * X Х**2 SIN (X) SQRT (Х»»2+1)- 1* **************************************************** * 00 0.0 0.0 0.0 * * 005 0.0025 0.04998 0 10000Е 01 * * 0.10 0.0100 0.09983 0 20000Е 01 * ♦ 0.15 0.0225 0.14944 0 30000Е 01 * * 0.20 0.0400 0.19867 0.40000Е 01 * * 0.25 0.0625 0.24740 0.50000Е 01 * * 0.30 0.0900 0.29552 0.60000Е 01 * * 0.35 0.1225 0.34290 0.70000Е 01 * - * 0.40 0.1600 0.38942 0.80000Е 01 ♦ * 0.45 0.2025 0.43497 0.90000Е01 * * 0.50 0.2500 0.47943 О.ЮОООЕ 02 * ♦ 0.55 0.3025 0.52269 О.ИОООЕ 02 % * 0.60 0.3600 0.56464 0.12000E 02 * * 0.65 0.4225 0.60519 0.13000E 02 * * 0.70 0.4900 0.64422 0 14000E 02 * * 0.75 0.5625 0 68164 015000E 02 * * 0.80 0.6400 0.71736 0 16000E02 * * 0.85 0.7225 0.75128 0.17000E 02 * * 0.90 0.8100 0 78333 018000E 02 * * 0.95 0.9025 0.81342 0 19000E02 * * 1.00 1.0000 ‘ 0.84147 0.20000E 02 * *************** ******** *********************** ****** СОСТАВИЛ: КОТОВ Е. А.
Глава 8 АВТОКОДЫ 8.1. Общие сведения Автокоды — это машинно-ориентированные языки программи- рования: они предназначены для того, чтобы в наиболее удобнойг для человека форме писать программы для конкретных машин. Каждый из автокодов представляет собой язык символического программирования для соответствующей машины, т. е. основан на ее системе команд, и, кроме того, включает в себя ряд дополни- тельных удобств для составления, изменения и оформления про- грамм. Алгоритмические языки высокого уровня (такие, например, как алгол или фортран), конечно, более удобны для человека, так что появление этих языков и соответствующих трансляторов су- щественно сократило сферу применения машинных языков, од- нако не ликвидировало ее полностью. Необходимость (или целе- сообразность) использования в ряде случаев машинного языка обусловливается двумя основными факторами. Первый фактор — эффективность получаемой машинной про- граммы. Как уже отмечалось, при решении разных задач к про- грамме могут предъявляться различные требования в отношении ее быстродействия, объема требуемой памяти и т. д., причем эти требования могут быть различными даже по отношению к отдель- ным частям одной и той же программы. Транслятор же переводит запись с какого-либо алгоритмического языка на язык машины по определенным, заложенным в него правилам (а усложнение этих правил к тому же повлекло бы за собой усложнение самого транс- лятора и замедлению его работы). По этой причине транслятор не в состоянии учитывать специфику каждой конкретной задачи и пожелания автора программы по отношению к характеристикам получаемой на машинном языке программы. Точно так же трудно построить транслятор, который мог бы с максимальной эффектив- ностью использовать все особенности имеющихся машинных опе- раций в разнообразных ситуациях, которые могут встретиться в конкретных программах. Если же программа составляется на языке машины, то ее автор имеет возможность держать под своим контролем каждую команду и каждую ячейку памяти, использовать все возможности имею-
448 АВТОКОДЫ [Гл. 8 щихся машинных операций и тем самым получать эффективную программу с нужными ему характеристиками с учетом специфики решаемой задачи. Вопрос эффективности получаемой программы особенно важен при решении задач, требующих большого коли- чества машинного времени, а также при создании программ, ко- торые в дальнейшем будут использоваться многократно (напри- мер, библиотечные подпрограммы). Реальный же эффект от ис- пользования машинного языка зависит, естественно, от квалифи- кации программиста. Второй фактор — это универсальность машинного языка: на нем можно запрограммировать любую задачу, которая вообще мо- жет быть решена на данной машине. А поскольку круг задач, ре- шаемых на ЭВМ, постоянно расширяется, то при решении таких задач, для которых еще нет достаточно удобного алгоритмического языка высокого уровня (или соответствующий транслятор на дан- ной машине отсутствует), использование машинного языка стано- вится подчас и вынужденной необходимостью. Заметим также, что иногда желаемые характеристики про- граммы или ее отдельных составных частей трудно сформулировать заранее — они могут выясняться и уточняться уже в процессе ис- пользования полученной программы (это относится главным об- разом к большим и сложным, многократно используемым програм- мам). В связи с этим возникает необходимость анализировать от- дельные части программы в процессе ее эксплуатации и вносить в них локальные изменения. Вся эта работа может значительно усложняться, если программа была получена с помощью трансля- тора — как за счет того, что понимание чужой программы (в дан- ном случае — программы, составленной транслятором) вообще весьма затруднительно, так и за счет того, что желаемые локаль- ные изменения бывает трудно или даже невозможно выразить в тер- минах языка высокого уровня (например, изменения, связанные с использованием индексных регистров). Поэтому при составлении подобного рода программ бывает целесообразнее использовать язык машины, а не язык высокого уровня. Машинная ориентированность автокода выражается в следующем: — автокодная программа состоит из последовательности авто- кодных предложений, большинство из которых является не чем иным, как символической записью соответствующих машинных команд; — в автокодной программе могут использоваться любые ма- шинные операции, благодаря чему автокод сохраняет все возмож- ности машинного языка; — автокодные команды имеют формат, аналогичный формату машинных команд. Таким образом, в основном речь идет о том, чтобы предоставить программисту возможность записывать машинные команды в более
8.2] ОСНОВНЫЕ ПОНЯТИЯ 449 V удобном для него виде. Наряду с этим автокод предоставляет про- граммисту и некоторые дополнительные удобства. Чтобы символическое программирование давало должный эф- фект, необходимо автоматизировать процесс перехода от символи- ческой программы к машинной (ее кодировку), передав выполне- ние этой работы самой ЭВМ по специальной программе. Эта про- грамма по сути дела является простейшим транслятором, который принято называть «автоматическим кодировщиком» или, короче, «автокодиром»; от этих же слов происходит и название языка «авто- код». В литературе для обозначения такого транслятора часто ис- пользуется также термин ассемблер, а входной язык для него назы- . вается языком ассемблера. Как и любой язык программирования, автокод имеет свой ал- фавит, синтаксис и семантику. Мы уже говорили, что под терми- ном «автокод» понимается целый класс языков, каждый из которых ориентирован на определенную ЭВМ. Даже для одной и той же машины нередко создается несколько различных автокодов, отли- чающихся друг от друга по способу изображения команд и по тем дополнительным возможностям, которые они предоставляют про- граммистам. Из множества автокодов принято выделять автокоды типа 1:1 — к этой группе относят такие автокоды, каждое предло- жение которых порождает в машинной программе не более одного машинного слова. В дальнейшем мы рассмотрим не какой-то конкретный, а некий учебный автокод, отражающий наиболее типичные черты и возмож- ности различных реальных автокодов. В частности, рассматри- ваемый нами автокод предусматривает взаимодействие модулей и по внешним, и по общим величинам. При этом мы будем исходить из того, что автокод ориентирован на нашу учебную машину УМИР-3. 8.2, Основные понятия 8.2,1. Алфавит. При выборе алфавита конкретного автокода приходится иметь в виду, что для работы ассемблера автокодную программу необ- ходимо вводить в память машины, предварительно нанеся текст программы на внешние носители данных (например, на перфо- карты). Поэтому приходится считаться с возможностями и особен- ностями имеющихся устройств подготовки данных. Мы будем счи- тать, что в алфавит автокода входят следующие основные сим- волы: а) буквы — заглавные буквы латинского алфавита и не совпа- дающие с ними по начертанию заглавные буквы русского алфа- вита; б) цифры — арабские цифры от 0 до 9 включительно; 15 э, з. Любныскиб и др.
450 АВТОКОДЫ [Гл. в) знаки операций'. (знак операции): := + | — г) разделители'. (разделитель) : (|)|=|.|,|:|;1%|« 1‘1’Ufe Заметим, что этот набор символов принадлежит соответствующему ГОСТу. 8.2.2. Идентификаторы. Идентификатор определяется так же, как и в алголе (любая последовательность букв и цифр, начинающаяся с буквы), с огра- ничением на его длину: идентификатор должен содержать не более пяти символов. Это ограничение связано с желанием представлять любой идентификатор одним машинным словом: обычно для изоб- ражения одного символа в памяти машины отводится один байт (восьмерка двоичных разрядов), так что 45-разрядное машинное слово позволяет представлять 5 символов (будем считать, что для этих целей используются 40 младших разрядов слова). Идентификаторы используются в автокоде в качестве имен объектов программы (команд, констант, рабочих ячеек или групп ячеек памяти), а также для построения мнемонических кодов опе- раций. 8.2.3. Формат предложений. Текст автокодной программы состоит из последовательности предложений, каждое из которых является законченной фразой автокода. В частности, каждая машинная команда пред- ставляется одним предложением. В связи с этим автокодные пред- ложения обычно имеют формат, аналогичный формату машинной' команды (квадратные скобки означают, что указанная в них ком- понента может отсутствовать): [(метка):](КОП) (поле адресов) [(комментарий)] причем поле адресов состоит из подполей, число которых соответст- вует числу адресов в команде. Комментарий, как и в алголе, не влияет на вырабатываемую программу, однако он также перфорируется и вводится в память машины (например, с целью получения на машине печатной копии исходного текста автокодной программы). По форме записи текста программы автокоды делятся на блан- ковые и безбланковые. В первом случае программа пишется на специальном бланке» т. е. заранее разграфленном листе бумаги. На бланке горизонталь- ными линиями выделены строки, в которых должен записываться автокодный текст, а каждая строка разделена на позиции (колон-
8.21 основные понятия 451 ки), в которых должны записываться отдельные символы. Обычно каждая строка текста перфорируется на отдельной карте. По- скольку в качестве стандарта на ЭВМ приняты 80-колонные перфо- карты, то строка содержит не более 80 позиций. Для некоторых компонент автокодного предложения на бланке выделяется опре- деленная группа позиций (поле), см., например, рис. 8.1. Если автокодное предложение не помещается на одной строке бланка, то обычно предусматривается возможность продолжения Метка с коп А1 А2 АЗ Комментарий W' м W,- , !••• I- WA- 1 1 1 V.V,- /?,/,.... Wi- 1111”* L- Рис. 8.1. его записи в следующих строках; для указания того факта, что данная строка является продолжением предыдущей, на бланке отводится специальная колонка (поле С), в которой записывается признак продолжения, например буква'С; в остальных строках эта колонка должна содержать пробел. Расположение символов в каж- дом из полей может определяться дополнительными правилами. Бданк обеспечивает достаточную простоту чтения автокодной программы, поскольку все поля каждой из команд находятся на одной вертикали, хотя он и не позволяет использовать такое сред- ство наглядного изображения структуры программы, как соответст- вующее расположение отдельных ее частей на листе бумаги. Кроме того, использование бланка очень удобно для трансляции, по- скольку транслятору заранее известно, в какой группе позиций строки находится та или иная компонента предложения, что пре- дельно упрощает их синтаксический анализ. Основное неудобство бланка заключается в фиксированной длине полей, что может за- труднить использование удобных обозначений. Кроме того, может быть затруднена и перфорация: если очередное поле было занято не полностью, то необходимы дополнительные действия для пере- хода к началу следующего поля. В безбланковых автокодах текст программы пишется либо на обычных листах бумаги, либо на простейших бланках, на которых выделены только строки, без фиксации позиций и тем более без фиксации групп позиций для размещения каждой из компонент автокодного предложения (выделение позиций здесь может быть полезным для стандартизации записи предложений и для упроще- ния контроля за допустимым числом символов в строке). Для вы- деления отдельных полей в предложении и для отделения предло- 15»
452 АВТОКОДЫ [Гл. 8 - жений друг от друга используются определенные разделители. Например, можно принять соглашение о том, что метка должна отделяться от остальной части предложения двоеточием, осталь- ные поля должны отделяться друг от друга запятой, а предложе- ния разделяются точкой с запятой, например: РА, 0, 0, 0; П, А, 0, X; ЦИКЛ-. С, X, ШАГ, Х-, Нули в каком-либо поле обычно разрешается опускать, так же как и последние пустые адреса, например: РЛ; П, А, , X; ЦИКЛ-. С, X, ШАГ, Х-, При этом способе можно в одной строке записывать несколько автокодных предложений, свободно осуществлять переход на сле- дующую строку и т. д., однако для достижения хорошей нагляд- ности программы и удобства дальнейшей работы с нею обычно каж- дое предложение размещают в отдельной строке, которая как пра- вило перфорируется на отдельной карте. Для сохранения преимуществ каждой из этих двух форм записи автокодного текста часто используется смешанный способ: напри- мер, метка и КОП должны записываться в определенных позициях, а остальная часть предложения записывается в безбланковой форме. Мы в дальнейшем будем придерживаться безбланковой формы. При этом условимся считать, что каждое предложение должно на- чинаться с новой строки и что поле адресов должно отделяться от комментария (возможно, пустого) точкой с запятой. Таким обра- зом, в качестве комментария будем считать последовательность символов, следующих в строке за точкой с запятой и до конца этой строки (или до конца последней из строк-продолжений, непосредст- венно следующих за строкой, содержащей точку с запятой). 8.2.4. Имена. Главное преимущество автокода по сравнению с машинным языком состоит в том, что для ссылок на те или иные объекты про- граммы (команды, константы, рабочие ячейки и т. д.) исполь- зуются не адреса, а главным образом имена этих объектов, в качестве которых могут использоваться произвольные идентифи- каторы. Именно благодаря использованию имен для ссылок на нужные объекты автокодная программа обладает хорошей нагляд- ностью и не зависит от конкретного распределения памяти. В каж- дом модуле могут использоваться имена внутренних, внешних и общих объектов. Все предложения автокода можно разбить на два класса. Одни предложения определяют те объекты, из которых состоит тело дан-
8.2) ОСНОВНЫЕ ПОНЯТИЯ 453 кого модуля — команды, константы, его рабочие ячейки и т. д. Другие предложения таких объектов не определяют — они служат для задания различного рода дополнительной информации (на- пример, информации о том, что данное имя используется для ссылки на некоторый внешний объект). Как видно из приведенного выше формата, каждое предложе- ние может быть снабжено меткой. Появление идентификатора в поле метки предложения, определяющего какой-либо объект, означает, что этот идентификатор является именем данного внутреннего объекта — это имя и используется для ссылок на данный объект. Для ссылок на внешние и общие объекты в каждом модуле также используются имена, которые могут выбираться произвольно — с соблюдением естественного требования, чтобы в одном и том же модуле одинаковые идентификаторы не использовались в качестве имен разных объектов. При переходе от автокодной программы к машинной каждое имя заменяется абсолютным адресом соответствующего объекта — этот адрес мы будем называть значением имени данного объекта. При трансляции автокодной программы предполагается, что внутренние объекты модуля будут размещаться в памяти в по- рядке следования определяющих их предложений в тексте про- граммы. Так что если очередной объект программы представляется в абсолютном модуле строкой с относительным номером к (к=0, 1,...), а модуль будет размещен в памяти с ячейки а„, то абсолютный адрес этого объекта равен значению ан+«. Если объект представ- ляется несколькими строками (например, число с удвоенной точ- ностью, изображаемое парой машинных слов), то абсолютным адре- сом такого объекта является адрес первой из этих строк. Таким образом, появление внутреннего имени в поле метки предложения определяет и значение этого имени, если известно истинное начало данного модуля в памяти машины. Что касается внешних и общих имен, то для определения их значений нужна дополнительная информация о том, на какие именно внешние и общие объекты ссылаются эти имена, а также информация об общем распределении памяти между модулями и общими объектами. На самом деле ассемблер обычно вырабатывает не абсолютный, а загрузочный модуль, поэтому каждое имя он заменяет не абсо- лютным, а некоторым условным адресом. Переработка условных адресов в абсолютные осуществляется впоследствии загрузчиком. В качестве условного адреса для каждого внутреннего объекта (назовем такие адреса внутренними} удобно взять как раз относи- тельный номер к строки, начиная с которой в модуле размещается этот внутренний объект. В этом случае переработка внутренних адресов в абсолютные будет производиться по чрезвычайно простому правилу: каждый внутренний адрес, равный к, надо заменить на абсолютный адрес, равный значению йв+к. Выбор условных адре-
454 АВТОКОДЫ [Гл. 8 сов, на которые заменяются внешние и общие имена, в разных ассемблерах делается по-разному, и их переработка в абсолютные адреса производится по более сложным правилам. 8.2.5. Счетчик размещения. Для определения значений внутренних имен ассемблер в про- цессе своей работы использует счетчик размещения-, к началу об- работки каждого автокодного предложения, определяющего оче- редной объект программы, текущее значение этого счетчика пола- гается равным адресу, начиная с которого следует разместить в па- мяти данный объект. Если'это предложение снабжено меткой, то в качестве значения этой метки принимается текущее значение счетчика размещения. В начале своей работы ассемблер присваивает счетчику разме- щения некоторое начальное значение. Если бы к моменту трансля- ции было известно истинное начало а* данного модуля в памяти, то адрес аи и следовало бы принять в качестве начального значе- ния счетчика размещения. Но если ассемблер вырабатывает не аб- солютный, а загрузочный модуль, истинное начало которого в па- мяти еще неизвестно, то в качестве начального значения счетчика размещения можно принять некоторый условный начальный ад- рес С, например С=0000. Другими словами, ассемблер вырабаты- вает загрузочный модуль в предположении, что соответствующий ему абсолютный модуль будет размещен в памяти, начиная с ячей- ки С. Если С—0000, то текущее значение счетчика размещения равно относительному номеру строки в модуле, начиная с кото- рой размещается очередной объект программы (счет строк начи- нается с нуля). Таким образом, значения внутренних имен опреде- ляются ассемблером с точностью до одного и того же слагаемого, значение которого определяется истинным началом этого модуля в памяти машины. При составлении автокодной программы удобно иметь возмож- ность ссылаться на текущее значение счетчика размещения. Для этого резервируется символ * (звездочка): этот символ может ис- пользоваться в адресных полях команд в любых позициях, где могут использоваться имена. Например, автокодная команда РА, 0, * , 0; определяет занесение в индексный регистр адреса размещения этой команды (т. е. она эквивалентна команде L: РА, 0, L, 0;), а команда перехода к какой-либо подпрограмме Р с возвратом (по рассмот- ренным ранее правилам) может быть записана в виде ПВ, * +1, Р, /?1; вместо команды L: ПВ, L+l, Р, /?1;
6.2] ОСНОВНЫЕ ПОНЯТИЯ 455 Как видно, использование ссылки на счетчик размещения при составлении программы позволяет избежать появления в ней лиш-, них меток и улучшает наглядность программы. 8.2.6. Самоопределенные величины. Мы знаем, что значения внутренних, внешних и общих имен зависят от расположения в памяти машины модулей и общих объек- тов. Но мы знаем также, что в программах встречаются адреса, которые не зависят от распределения памяти — например адреса стандартных рабочих ячеек, адреса, являющиеся непосредствен- ными операндами в некоторых командах сдвига (адреса нулевого ранга) и т. д. Такие адреса в автокоде задаются самоопределенными величинами. Частным случаем самоопределенной величины является целое без знака. При этом автокод обычно допускает запись целых без знака в различных системах счисления и содержит правила для ука- зания используемой системы счисления. Можно, например, осно- вание системы (в его десятичной записи), взятое в скобки, записы- вать перед целым без знака: (10)26, (8)0062 и т. д. В нашем автокоде разрешается использовать десятичную и восьмеричную системы счисления, причем восьмеричная запись целого без знака должна заключаться в строчные кавычки. Примеры целых без знака: ‘0012’, 80, ‘35’, 26. Другим примером самоопределенной величины является вы- ражение М — Р, где М и Р — внутренние имена в данном модуле, Очевидно, что значение выражения М — Р не зависит от распреде- ления памяти. Если метка Р предшествует метке М, то это значение равно числу строк в оттранслированной программе, начиная со строки, помеченной меткой Р, и кончая строкой, непосредственно предшествующей строке с меткой М. В ряде случаев использование таких самоопределенных величин может оказаться весьма удобным. 4 Например, в приведенной в гл. 6 программе прокрутки блок Д0 осуществляет просмотр таблицы констант от Д+6 до Д+15. Этот просмотр осуществляется в цикле. Управление числом его повторений производится с использованием индексного регистра: перед входом в цикл в этот регистр заносится нуль, а последняя команда цикла записана В виде L3: ПМ, 9, 12, 1( ); она и обеспечивает выполнение цикла 10 раз, по числу констант в таблице. Если бы мы захотели изменить число нестандартных операций, интерпретируемых прокруткой, в эту таблицу надо было бы добавить (или удалить из нее) некоторое число соответст- вующих констант, а это привело бы к необходимости изменять и приведенную выше команду. Между тем можно избежать необходи- мости изменения этой команды при изменении длины таблицы:
456 АВТОКОДЫ 1Гл. 8 для этого достаточно первую и последнюю константы таблицы по- метить, например, метками Р и Q соответственно, а приведенную выше команду записать в виде L3: ЛМ, Q — Р, L2, 1 ( ); Очевидно, что выражение Q—Р является самоопределенной вели- чиной, значение которой всегда равно п—1 при любом числе п строк в таблице, так что изменение длины последней теперь не требует изменения команды с меткой L3. 8.2.7. Адресные выражения. Для большего удобства написания программ автокоды разре- шают записывать в адресных полях автокодных предложений не только имена соответствующих объектов, но и целые формулы для вычисления их адресов. Поскольку каждая такая формула опреде- ляет некоторый адрес, то эти формулы называют адресными выражениями. В общем случае они строятся из операндов, знаков арифметических операций и круглых скобок. Операндами адрес- ных выражений являются внутренние, внешние и общие имена, в том числе и имя счетчика размещения (*) как специфичный слу- чай внутреннего имени, а также константы. Разные автокоды допускают различные способы задания адрес- ных выражений и различную степень их сложности. Однако сле- дует иметь в виду, что чаще всего адресные выражения исполь- зуются о целью уменьшения числа вводимых в употребление имен, так как обилие различных используемых имен затрудняет их подбор, понимание программы и увеличивает вероятность ошибок в ней — например, придания одного и того же имени разным объектам. Если, например, в программе запасаются константы, то можно дать свое имя каждой из них и использовать его для ссылки на данную константу. Между тем, если эти константы запасти в про- грамме единым массивом (в результате чего они будут размещены в ячейках памяти с последовательными номерами), то достаточно дать имя (например, С) только первой из них, а для ссылок на по- следовательные константы этого массива использовать адресные выражения С, С-Н, С+2 и т. д. На практике сложные структуры адресных выражений исполь- зуются весьма редко. В то же время их допущение влечет за собой неоправданное усложнение ассемблера. В связи с этим многие реальные автокоды допускают лишь простейшие структуры адрес- ных выражений. К этому следует добавить, что не всякие и допус- тимые по синтаксису адресные выражения имеют смысл. Так, если Р и Q — внутренние имена, то выражение Q—Р, если оно до- пустимо по синтаксису, имеет вполне определенный смысл, о кото- ром говорилось выше, тогда как выражению Q+P трудно придать какой-либо реальный смысл. В любом случае значение адресного
аз) ЗАДАНИЕ КОМАНД 457 выражения должно свестись либо к значению имени (внутреннего, внешнего или общего), либо к значению самоопределенной величи- ны, либо к конструкции вида (значение имени )± (значение самоопределенной величины) Мы в адресных выражениях будем допускать использование только операций сложения и вычитания, т. е. алгебраическую сумму опе- рандов. Из предыдущего требования вытекает, что в этой сумме может присутствовать любое число констант и пар вида (внутреннее имя > — (внутреннее имя) и, кроме того, не более одного любого непарного имени, обяза- тельно со знаком плюс. Примеры адресных выражений: 5 ЦИКЛ С+6 А+Х—У+4 (где X и У — внутренние имена, а А — внешнее имя). 8.3. Задание команд Предложение автокода, определяющее команду, в общем слу- чае имеет вид: [(метка):] {КОП}, (адрес 1)[( )], (адрес 2)[( )], (адрес 3 >f( )]; [ (комментарий)] Код операции (КОП). Эта компонента автокодной команды слу- жит для задания требуемой машинной операции. Как правило, автокод позволяет задавать в этом поле как машинный (цифровой), так и символический код операции. Если программист достаточно долго работает на данной машине, то он обычно помнит цифровые коды операций (если не все, то по крайней мере наиболее употребительные), поэтому ему может быть удобнее задавать цифровой код операций. Поскольку цифровой код операции обычно является восьмеричным, то в этом поле раз- решается записывать только восьмеричные цифры, без заключения их в строчные кавычки. Если в данном поле указаны три восьме- ричные цифры, то первая из них задает признаки модификации ад- ресов по индексному регистру. Обычно же для задания машинной операции в автокоде исполь- зуется ее символическое обозначение. При выборе таких обозна- чений используют некоторые мнемонические правила, облегчаю- щие запоминание введенных обозначений, поэтому символическое обозначение часто называют мнемоническим кодом операции (или, короче, мнемокодом). Наиболее часто используются два способа
458 АВТОКОДЫ [Гл. 8 выбора мнемокодов. Первый способ состоит В обозначении опера- ции одной или несколькими буквами, взятыми от названия этой операции или отражающими ее содержание, например: С (сложе- ние), СДСА (сдвиг слова адресом), СТОП (останов) и т. д. Конечно, вместо букв можно использовать и другие символы — например, ддя основных арифметических операций можно использовать их обычные обозначения (+, —, / и X). Однако при выборе этих обозначений также учитываются реальные устройства подготовки данных (перфорирующие устройства) и их специфика. Если, на- пример, имеющийся на них набор символов разбит на несколько регистров, то использование символов из разных регистров при- ведет к необходимости дополнительных действий по переключению регистров во время перфорации, что увеличивает время перфора- ции и вероятность ошибок. Поэтому при выборе допустимых сим- волов стремятся свести к минимуму необходимость этих допол- нительных действий. Другой способ состоит в обозначении операций (по крайней мере большинства из них) такой комбинацией символов, которая характеризует как саму операцию, так и используемые в ней опе- ранды. Этот способ особенно удобен на машинах, в которых одна и та же операция присутствует в разных модификациях (что осо- бенно характерно для одно- и двухадресных машин). При этом фиксируется некоторое (обычно сравнительно небольшое) число обозначений как для классов возможных операндов, например: S (сумматор), X (слово, выбираемое из памяти), А (адрес в команде), / (индексный регистр) и т. д., так и для выполняемых над ними операций, например Т (пересылка, от английского слова trans- fer),— (вычитание), J (переход — jump) и т. п. Мнемонический код операции образуется из этих условных обозначений, взятых в оп- ределенном порядке. Для одноадресной машины, например, можно взять такие мнемонические коды операций: XTS — пересылка слова из памяти в сумматор, STX — пересылка содержимого сумматора в память, ATI — пересылка адреса в команде в индексный регистр, ITX s— пересылка содержимого индексного регистра в память, ITI — пересылка содержимого одного индексного регистра в другой, S—X — вычитание из числа в сумматоре числа, выбираемого из памяти, X—S — вычитание из числа, выбираемого из памяти, числа в сумматоре, I—А — вычитание из содержимого индексного регистра адреса в команде, J —безусловный переход, JT — условный переход при costrue и т. д.
8.3] ЗАДАНИЕ КОМАНД 453 Аналогичный прием удобен и для машин с несколькими возмож- ными форматами команд. Довольно часто формат команды опреде- ляется кодом операции — в этом случае характерной является си- туация, когда одна и та же операция (например, сложение) имеет несколько различных кодов операций в зависимости от требуемого формата команды. Так, на двухадресной машине, имеющей специ- альную быструю память небольшого объема, возможны, например, форматы команд с адресностью 0.25+0.25, 0.25+1, 1 + 1, и тогда многие операции будут иметь по три различных кода операций, что затрудняет их запоминание. На такой машине удобно для каждого из возможных форматов ввести свое обозначение, напри- мер буквы К («короткие» адреса), С («смешанные» адреса), Д («длин- ные» адреса), и тогда мнемонические коды операций можно состав- лять из двух частей: первая определяет саму операцию, а вторая -г- нужный формат команды. При таком соглашении мнемонические коды операций умножения, например, могут иметь, легко запоми- нающийся вид: УК, УС и УД. Конечно, при довольно большом числе разнообразных машинных операций бывает трудно подобрать одинаково удачные мнемони- ческие коды для всех операций, однако для наиболее часто употреб- ляемых операций это обычно, удается сделать. Мы будем использо- вать первый из указанных выше способов, приняв в качестве мне- монических кодов введенные ранее символические обозначения операций. При этом условимся считать, что в случае использования мнемонического кода операции признаки модификации адресов должны задаваться в адресных полях автокодной команды. Адреса в команде. Каждый из трех адресов в автокодной команде является адресным выражением. Если какой-либо из адресов в команде должен модифицироваться по индексному регистру,' а в коде операции этой команды не заданы признаки модификации, что может быть сделано только в случае цифрового кода операции, то вслед за соответствующим адресным выражением записываются последовательно открывающая и закрывающая круглые скобки. В общем случае, когда в машине имеется несколько индексных регистров и каждый адрес в команде может модифицироваться по любому из них, обычно в круглых скобках указывается номер ин- дексного регистра, по которому должен модифицироваться данный адрес. Если же в-машине имеется только один такой регистр, то запись его номера в круглых скобках опускается; Примеры (отдельных) автокодных команд: С, X, Н, Х-, М: СДСА, 44’, А, Р + 2; ПМ, P — Q, *—4, 1( ); 501, А, 2, А; 01, А( ). 2, Л( );
460 АВТОКОДЫ [Гл В Заметим, что последние две автокодные команды определяют одну и ту же машинную команду. При трансляции автокодной команды ассемблер: — заменяет мнемонический код операции на соответствующий ему цифровой код, используя таблицу соответствия между мнемо* ническими и цифровыми кодами операций; — дополняет слева полученный цифровой код операции восьме- ричной цифрой п, задающей признаки модификации адресов; эта цифра формируется с учетом круглых скобок, указанных в полях адресов; — записывает в каждом поле адреса вырабатываемой машин- ной команды адрес (абсолютный или условный), являющийся зна- чением соответствующего адресного выражения в автокодной команда. 8.4. Задание констант Практически в любой программе приходится запасать те или иные константы: вещественные или целые числа, выделители частей машинного слова, части текстов и т. д. Автокод позволяет задавать константы в удобной для человека форме, а их перевод в машинное слово берет на себя ассемблер. Предложение автокода, определяющее константу, имеет вид [(метка):) КОНСТ, (константа); Наличие мнемокода КОНСТ в предложении и говорит о том, что это предложение определяет некоторую константу, запасаемую в программе. Автокод может допускать задание констант разных типов. Вещественное число. Если запасаемая константа является ве- щественным числом, то оно задается, например, так же, как и в алголе. Примеры задания констант, являющихся вещественными чис- лами: ПИ: КОНСТ, 3.141592; СТО: КОНСТ, 10+2; КОНСТ; —25.5; При трансляции такого предложения ассемблер переведет за- данное число в двоичную систему счисления, представит его в форме, принятой на данной машине для представления вещественных чи- сел, и полученное машинное слово разместит в очередной ячейке памяти в соответствии с текущим значением счетчика размещения. Если константа помечена, то, как обычно, в качестве значения ее метки принимается тот адрес, по которому размещенаэта константа. Если в машине целые и вещественные числа представляются по-разному, а также если существуют разные форматы представ-
ЗАДАНИЕ КОНСТАНТ 461 S.4] ления вещественных чисел (длинный, короткий), то требуемый фор* мат указывается в автокодном предложении, определяющем кон* станту — например, вслед за записью самого числа в круглых скобках записывается условное обозначение нужного формата: / — целое, Е — вещественное короткое или D — вещественное длинное, например 25 (£). Слово машины. Для удобства задания констант, являющихся произвольными машинными словами, каждому полю слова (при- няв, например, за основу формат команды) присваивается некоторое фиксированное имя. См. например, рис. 8.2. Тогда для опреде- ления нужной константы-слова указываются имена полей (в про- извольном порядке), а вслед за каждым таким именем в круглых скобках задается то (целочисленное) значение, которое должно быть представлено в виде числа единиц младшего разряда этого поля. При этом каждое поле должно быть определено не более одно- го раза, а если имя какого-либо поля не указано, то это значит, что данное поле должно содержать нули во всех его разрядах. Значение, представляемое в каждом поле, в простейшем случае задается целым без знака (в десятичной иди восьмеричной записи). Ассемблер в процессе трансляции переведет эти числа в двоичную систему и сформирует соответствующее машинное слово. Например, предложения автокода Cl: КОНСТ, Al (1) АЗ (2); С2: КОНСТ, К (‘52’) А2 (5); СЗ: КОНСТ, С (29); определяют константы, представляемые соответственно машинными словами: С1: ООО 0001 0000 0002 С2: 052 0000 0005 0000 СЗ: 000 0000 0000 0035. Значение, представляемое в том или ином адресном поле, мо- жет быть задано и адресным выражением. Например, предложение М: КОНСТ, К (‘505’) Al (X) А2 (Р) ЛЗ (X); эквивалентно автокодной команде М: 505, X, Р, X;
/ 462 АВТОКОДЫ > у/ [Гл. в Вместо имени поля можно указывать номер^разряда в машинном слове, с которого надо разместить задаваемое в круглых скобках значение (например, влево от этого разряда). Номер N разряда можно условиться задавать в виде Р (N)/ В этом случае запись Р (0) эквивалентна имени поля ЛЗ, запись Р (12) эквивалентна имени поля Л2 и т. д. При этих соглашениях константа С2, напри- мер, задавалась бы предложением С2: КОНСТ, Р (36) (‘52’) Р (12) (5). Текстовые константы. В автокодах, в которых принят опреде- ленный внутренний язык представления символов, могут допус- каться и текстовые константы, т. е. машинные слова, представ- ляющие собой коды некоторой последовательности символов. Для задания таких констант служат автокодньщ предложения вида [(метка):] КОНСТ, 7 ((текст»; где (текст) и задает последовательность, состоящую не более чем из пяти символов; при меньшем числе заданных символов счи- тается, что справа добавляется недостающее число пробелов. Пример текстовой константы: TEXT: КОНСТ, Т (ГДЕ:); Более удобен способ, не ограничивающий длину текста. Для этого может быть использовано другое автокодное предложение вида [(метка):] ТЕКСТ, ‘(текст)’; например: загл: текст, ‘ТАблица^меток’-, При трансляции такой текст будет размещен в трех последова- тельных ячейках памяти по 5 символов в каждой из них (последняя ячейка будет дополнена двумя пробелами справа). Текстовые кон- станты обычно используются как заготовки заголовков, выводимых на печать перед получаемыми результатами, диагностических со- общений и т. д. С помощью одного автокодного предложения можно задать и несколько констант, перечислив их через запятую в поле адресов этого предложения. Например, по предложению автокода М: КОНСТ,Ie+2, Al (1) Л2 (1), Т (ГДЕ:); ассемблером в трех последовательных ячейках памяти будут за- пасены упоминавшиеся ранее константы СТО, С1 и TEXT. Значе- нием метки Af будет являться адрес первой из этих ячеек, так что теперь для ссылок на эти константы должны использоваться ад- ресные выражения М, М-Н и М+2 соответственно. Заметим, что указанные возможности нарушают принцип 1:1, поскольку одно
ЗАДАНИЕ КОНСТАНТ 463 «Л] автокодное предложение порождает в программе несколько ма- шинных слов. \ Следует отметить/ .что между автокодными командами и кон- стантами нет принципиальной разницы — это просто разные спо- собы задания очередного машинного слова в окончательной про- грамме. Например, машинную команду - 052 0000 0005 0000 означающую занесение в индексный регистр числа 5,' на автокоде можно записать как в виде автокодной команды РА, , 5, ; так и в виде константы КОНСТ, К (‘52’) Л2(5); ибо эффект трансляции будет один и тот же. Точно так же и кон- станту можно задать с помощью автокодной команды (что, впрочем, находится в соответствии с тем, что по виду машинного слова не- возможно определить, что оно представляет собой на самом деле — число или команду). Таким образом, автор автокодной программы может выбирать тот способ записи на автокоде каждого машин- ного слова, который ему кажется наиболее удобным. По этой при- чине ассемблер обычно размещает константы именно в тех местах программы, где они написаны,, не производит их экономии и, ко- нечно, не вставляет в вырабатываемую программу команд обхода констант — все эти вопросы должны решаться автором программы. Буквальные константы (литералы). Рассмотренный выше спо- соб задания и использования констант в автокоде не всегда доста- точно удобен: программист должен выбрать подходящее место в программе, где можно разместить нужную константу, написать в этом месте соответствующее предложение автокода, снабдить его индивидуальной меткой и использовать эту метку для ссылок на данную константу. Так, при программировании оператора при- сваивания х:=х+0.5 надо записать команду С, X, Cl, X; и где-то в программе записать предложение С1: КОНСТ, 0.5; Между тем в ряде случаев программисту бывает безразлично, где именно будет размещена константа. Кроме того, если данная кон- станта используется только в одном месте программы, то хотелось бы избежать необходимости выбирать для нее какое-то имя. В таких случаях было бы удобнее поручить ассемблеру работу по размеще- нию константы и организации ссылки на нее. Поэтому подавляющее большинство автокодов допускает другой, более удобный способ размещения и использования констант, а именно — с помощью литералов (или буквальных констант}. При этом способе требуемая константа указывается в явном виде в соответствующем поле адреса
464 АВТОКОДЫ [Гл. 8 команды, точно по тем же правилам, по которым она задается в предложениях КОНСТ, а чтобы устранить ^однозначность по- нимания, эта константа предваряется знаком —. При этом способе оператор х:=х+0.5 можно запрограммировать автокодной командой С, X, =0.5, X; (где запись =0.5 и является литералом), не записывая отдельного предложения, определяющего константу. При трансляции такой команды ассемблер по знаку = обнару- жит, что здесь речь идет о буквальной константе, сам запасет эту константу 0.5 в виде машинного слова в подходящем месте про- граммы (как правило, все такие константы объединяются в единую группу и помещаются в конце программы), и заменит литерал в команде на соответствующий адрес. Таким образом, с помощью литерала мы передаем ассемблеру работу и по размещению нужной константы в памяти, и по организации правильной ссылки на нее. Поскольку в этом случае у нас нет возможности сослаться на эту же константу в других предложениях, то ассемблер может не только размещать буквальные константы в памяти по своему усмот- рению, но и производить их экономию: если такая константа уже была запасена ранее, то литерал просто заменяется ее адресом. Заметим, что в литералах могут использоваться любые кон- станты, допустимые в предложениях КОНСТ, например: И, А, = Л2(‘7777’), £1; • СМ, Р, = Л 1(1)42(1), Р; С, М, =2, М; 8.5. Команды транслятору Каждое из рассмотренных выше предложений автокода опре- деляет либо машинную команду, либо константу (или группу констант), либо и то и другое (в случае использования в команде буквальных констант). Таким образом, обработка каждого из таких предложений автокода ассемблером влечет за собой включение в вырабатываемую машинную программу одного или нескольких машинных слов. В автокоде есть предложения и другого типа: их особенность состоит в том, что такие предложения не влекут за собой появления каких-либо слов в вырабатываемой ассемблером машинной про- грамме — они служат для управления работой ассемблера и обеспе- чивают дополнительные удобства пользователю. Предложения этого типа принято называть командами ассемблеру. Набор таких команд может быть весьма обширен, и этот набор может сущест- венно варьироваться от одного конкретного автокода к другому. Мы рассмотрим не все возможные, а достаточно типичные команды ассемблеру, хотя некоторые из них могут и отсутствовать в том или ином автокоде.
8.5] КОМАНДЫ ТРАНСЛЯТОРУ 465, 8.5.1. Команды организационного Характера. Эти команды ассемблеру служат для фиксации начала и конца , автокодного текста, подлежащего трансляции. В этих командах может задаваться и некоторая дополнительная информация о дан- ном модуле, например, ёго имя: обычно вырабатываемый ассембле- ром загрузочный модуль помещается на хранение в библиотеку модулей, а при дальнейшей работе с библиотекой ссылка на нуж- ные модули производится по их именам. Иногда выработанный ассемблером модуль нужно сразу загрузить в память и выполнить, и тогда адрес загрузки также можно задать в качестве дополни- тельной информации. Команды ассемблеру такого типа могут иметь следующий формат: [ {метка 1 ):] НАЧАЛО [, (имя модуля Я [. (адрес загрузки >1; [(метка 2)-} КОНЕЦ-, Первой из этих команд должна начинаться, а второй — завер- шаться каждая автокодная программа, подлежащая трансляции. При этом значением первой метки является адрес ячейки, начиная с которой этот модуль будет размещен в памяти машины, а значе- нием второй метки — адрес последней из ячеек, отведенных для размещения данного модуля. Если дополнительная информация о модуле не задана, то она определяется ассемблером по фикси- рованным правилам. Например, если не указано имя модуля, то ему присваивается имя, состоящее из одних пробелов, а если не указан адрес загрузки, то в качестве такового принимается адрес 0000. 8.5.2. Резервирование памяти. Команда вида [(метка):! ПАМ, (самоопределенная величина); означает, что в данном месте программы нужно зарезервировать группу ячеек памяти, число которых / равно значению самоопреде- ленной величины. Содержимое этих ячеек не определяется (иначе вместо данной- команды ассемблеру были бы записаны предложе- ния, определяющие соответствующие константы), так что трансля- ция данного предложения сводится просто к продвижению счет- чика размещения на I единиц. Значением метки является адрес первой ячейки этой группы. Если 1=0, то эта команда аналогична пустому оператору алгола и может использоваться для того, чтобы пометить следующее за ней предложение (например, второй мет- кой): А12: ПАМ, 0; М'. С, X, Н, X; В этом случае метки 2И2 и М получат одно и то же значение, по-
466 АВТОКОДЫ / [Гл. 8- этому на команду сложения можно ссылаться с пбмощью любой из этих меток. / Данная команда ассемблеру в основном используется с целью резервирования памяти для локальных переменных величин дан- ного модуля. Поскольку характер использования резервируемых ячеек ассемблеру неизвестен, то он (как и в случае констант) не записывает в вырабатываемую программу команд обхода резерви- руемых ячеек — за этим должен следить автор программы. Во из- бежание возможных ошибок и для большей наглядности про- граммы целесообразно все локальные переменные величины разме- щать в конце модуля (после всех команд и констант). Заметим, что ассемблер обычно не заводит в программе резер- вируемых ячеек (чтобы не удлинять загрузочный модуль), а остав- ляет соответствующую ин4юрмацию загрузчику, который уже фактически и выделяет зарезервированное место в памяти. 8.5.3. Объявление эквивалентности. Данное предложение, имеющее вид I(метка):] ЭК.В, (адресное выражение); предписывает имени, использованному в качестве метки, значение заданного адресного выражения. При этом часто накладывается требование, чтобы все имена, входящие в это выражение, были определены в тексте ранее. Напомним, что одним из способов определения значения имени является его использование в качестве метки какого-либо предло- жения, связанного с порождением одного из объектов программы — команды, константы, рабочей ячейки (или группы рабочих ячеек). Объявление эквивалентности также определяет значение имени, но другим способом. Так, предложение М: ЭКВ, Р-, означает, что имя М объявляется эквивалентным имени Р (т. е. имени М предписывается значение имени Р), так что объявления эквивалентности часто используются для отождествления двух или более различных имен. Такую возможность можно, например, использовать для разде- ления работ по написанию команд вычисления по каким-либо фор- мулам и экономии рабочих ячеек. Так, при программировании опе- ратора присваивания x:=aXb-\-c—d+$ можно записать команды, не думая об экономии рабочих ячеек У, А, В, Р1; С, Pl, С, Р2; В, Р2, D, РЗ-, С, РЗ, Е, X;
8.5] КОМАНДЫ ТРАНСЛЯТОРУ 467 а для их экономии в программе можно записать следующие пред- ложения: \ Р1: ПАМ, 1Д Р2: ЭКВ, Р1; РЗ: ЭКВ, Р1; (или РЗ: ЭКВ, Р2-,) В результате всем трем именам Pl, Р2 и РЗ будет поставлена в соот- ветствие одна и та же ячейка памяти, отведенная для Р1. При этом, конечно, предполагается, что ячейки Р2 и РЗ сами по себе- не используются в других местах, так как в результате объявления эквивалентности они отождествляются с ячейкой Р1 во всем- модуле. Путем объявления эквивалентности можно задать значение некоторого параметра программы. Например, для вычисления у=хп (л>0— целое) можно написать команды: П, =1.0, , У; РА, , 1; У, Y, X, У; ПМ, N, *—1, 1(); Чтобы к началу трансляции зафиксировать конкретное значение п, например п=19, достаточно в автокодную программу вставить^ предложение N: ЭКВ, 19; 8.5.4. Управление размещением литералов. Как уже отмечалось, константы, вводимые в употребление с помощью литералов, размещаются в памяти ассемблером по его усмотрению — обычно все такие константы ассемблер собирает в единую группу и помещает их в конце программы. Однако такой способ размещения литералов может вызвать определенные трудности при дальнейшей работе с оттранслирован- ной программой (например, при желании изучить эту программу или ее отдельные части), поскольку большое удаление констант от тех команд, в которых они используются, затрудняет понимание программы. В связи с этим некоторые автокоды дают возможность автору программы управлять размещением литералов, используя для этих целей команду ассемблеру ОРГЛТ Эта команда является указанием ассемблеру разместить в данном месте программы все литералы, которые использовались в про- грамме после предыдущей команды ОРГЛТ (или с начала про- граммы, если такая команда встретилась впервые). При этом ас- семблер снова не вставляет команды обхода констант — об этом должен позаботиться автор программы.
./ 468 АВТОКОДЫ у (Гл. 8 / 8.5.5. Дублирование команд. / Иногда при составлении программы определенную группу команд приходится повторять несколько раз, Возможно, с некоторой модификацией части адресов в этой команде. Например, вычисле- ние y=a»xs+aix2+a4x+o8 с использованием схемы Горнера можно было бы запрограммировать с помощью цикла. Однако для повы- шения быстродействия программы можно каждый шаг схемы Гор- нера реализовать отдельными командами. Тогда (в предположении, что коэффициенты запасены в виде массива констант с именем Л) можно составить следующую программу: Л, А, , У; У , У, X, У;) С, У, Л + 1, У;/ У , У, X, У; 1 С, У, А + 2, Y;f У , У, X, У; I С, У, Л-ЬЗ, У;/ Как видно, за первой командой этой программы следуют три пары аналогичных команд, которые отличаются друг от друга только тем, что второй адрес во второй команде последовательно увеличивается на единицу. Для упрощения написания подобного рода фрагментов про- граммы в некоторых автокодах имеется возможность дать указа- ние ассемблеру продублировать в программе определенную группу команд заданное число раз, с некоторой модификацией этих команд по ходу дублирования. Для этих целей используется команда ассемблеру вида ДУБЛ, (число команд), (число повторений} в которой каждый операнд есть целое без знака. Эта команда ас- семблеру записывается перед дублируемой группой команд. Моди- фикация дублируемых команд может производиться, например, по следующему правилу: ко всем отмеченным некоторым образом адресам в командах при каждом очередном повторении прибав- ляется соответственно 1,2, 3 и т. д. Если адрес а, подлежащий такой модификации, условиться записывать в виде а+%, то указанную выше последовательность команд можно задать следующими пред- ложениями автокода: П, А, , У; ДУБЛ, 2, 3; продублировать следующие 2 команды 3 раза У , У, X, У; С, У, 4 +%, У', Рассмотренный способ является своего рода компромиссом между
8.5] у КОМАНДЫ ТРАНСЛЯТОРУ 469 циклом и расписыванием вручную развернутой последовательности команд: вся эта Последовательность команд в вырабатываемой ассемблером программе будет присутствовать, а труд человека по их написанию экономится. 8.5.6. Управление счетчиком размещения. Как уже отмечалось ранее, для размещения в памяти объектов программы, определяемых автокодными предложениями, ассемб- лер использует счетчик размещения: очередной объект машинной программы, вырабатываемый при трансляции, размещается с ячей- ки, адрес которой равен текущему значению счетчика размещения, после чего значение счетчика увеличивается на число ячеек, отве- денных для размещения этого объекта. Таким образом, объекты программы, определяемые последовательными предложениями авто- кодной программы, обычно размещаются в последовательных ячей- ках памяти. Некоторые автокоды предоставляют программисту возможность управлять этим процессом размещения объектов в памяти, что в ряде случаев оказывается полезным. Например, есть машины, где некоторые команды (например, команды управления внешними устройствами) всегда должны помещаться в определенном месте памяти, тогда как в программе их хотелось бы писать в обычной логической последовательности. Эти возможности реализуются следующими командами ассемб- леру. Команда вида [(метка):] ОРГСР, (имя), (адресное выражение); является указанием организовать вспомогательный счетчик разме- щения с заданным именем и присвоить ему значение заданного адресного выражения. Команда вида • [(метка):] ИСП, (имя) ; означает, что дальнейшую трансляцию ассемблер должен осуществ- лять с использованием вспомогательного счетчика размещения с заданным именем. Команда вида [(метка):] КИСП ; является указанием ассемблеру вернуться к тому счетчику разме- щения, которым он пользовался до предыдущей команды ИСП. При этом заметим, что во время использования ассемблером какого- либо из счетчиков размещения значения других счетчиков, органи- зованных по командам ОРГСР (а также основного счетчика), не меняются.
470 АВТОКОДЫ 1Гл. В Приведенная ниже схема (см. рис. 8.3) иллюстрирует размеще- ние в памяти отдельных частей автокодной/программы после ее трансляции, если в исходной программе испЬльзовались вспомога- тельные счетчики размещения. Как видно, из единой автокодной программы в результате трансляции получаются две отдельные части машинной программы, расположенные в разных областях памяти, которые могут взаимодействовать между собой, например, путем выполнения команд перехода, предусмотренных автором в каждой из частей программы. Управление счетчиком размещения может использоваться в раз- личных ситуациях. В частности, этот механизм иногда можно ис- пользовать для тех же целей, что и объявление эквивалентности. 0000-у с ........ • Пусть, например, был написан 0000-\ ( ..... • 0001: /*----i • • • оргср,ис,‘7ооо'; J иг и иг* • некоторый фрагмент автокодной программы__________________ { Y : Z ; R : ИСП, ИС; ПАМ, ПАМ, ПАМ, ПАМ, 20 20 40 НИ СП-, Рис. в.З. в котором определены некоторые внутренние объекты X, . У, Z, К. Допустим, что далее понадобилось ввести в употребление внутрен- ние массивы 411:15], В[1:15], С[ 1:401, Z>[1:5], причем оказалось, что их можно совместить в памяти с указанными выше объектами. Этой цели можно достигнуть, записав команды объявления экви- валентности: А: ЭКВ, X; В: ЭКВ, Х4-15; С: ЭКВ, Х4-30; D; ЭКВ, Х4-70; Этой же цели можно достичь и другим способом, при котором не придется вычислять величины сдвига относительно начала мас- сива X: ОРГСР, ИСП, А: ПАМ, В; ПАМ, С: ПАМ, D: ПАМ, НОВ, X; НОВ ; КИСП 15; 15; 40; 5;
3.5] \ КОМАНДЫ ТРАНСЛЯТОРУ ' 47] Тогда массивы А, В, С и D будут последовательно размещены с того же места, с которого началось размещение массива X, а объекты, следующие за командой КИСП, будут размещаться в памяти вслед за последним объектом, введенным в употребление до команды ОРГСР, НОВ, X ; 8.5.7. Определение межмодульных связей. Как правило, автокод служит для составления не самостоя- тельной, завершенной программы, а модуля, т. е. программы, рас- считанной на ее взаимодействие с другими программами (модулями). В связи с этим должна быть обеспечена возможность ссылаться на объекты, внешние по отношению к данному модулю, т. е. на объек- ты, определяемые в других модулях. Такими объектами могут быть как команды, на которые, например, осуществляется переход из данного модуля, так и величины (постоянные или переменные), которые должны использоваться в данном модуле. Мы уже знаем, что в некоторых системах программирования допускается исполь- зование внешних величин особого класса — так называемых общих величин, которые не принадлежат какому-либо модулю, но доступ- ны для использования в любом из них. Для ссылки на внешние и общие объекты также используются имена, которые называются соответственно внешними и общими именами. Чтобы предоставить автору модуля полную свободу в выборе обозначений, что особенно важно для независимого со- ставления отдельных модулей, в качестве таких имен могут исполь- зоваться любые имена, не используемые в этом модуле для других целей — независимо от того, какие имена даны этим объектам в тех модулях, где они вводятся в употребление (аналог формальных параметров в описании процедур в алголе). Конкретизация этих имен (а тем самым и определение их зна- чений) будет производиться редактором связей или загрузчиком при последующих использованиях загрузочного модуля, выраба- тываемого ассемблером. Ассемблер для своей работы должен располагать информацией о том, какие идентификаторы в данном модуле используются в ка- честве внешних и общих имен: во-первых, для того, чтобы не квали- фицировать как ошибку использование таких имен в адресных полях автокодных предложений при отсутствии определения этих имен в данном модуле; во-вторых, эта информация необходима ассемблеру для оформления вырабатываемого им загрузочного модуля, в частности — для формирования его паспорта. С этой же целью ассемблер должен располагать информацией об именах входных точек данного модуля, т. е. именах тех его объектов, кото- рые используются в других модулях (в качестве внешних по отношению к ним объектов).
АВТОКОДЫ [Гл. В 472, Для задания ассемблеру информации о внешних и общих име- нах, а также о входных точках данного модуля, служат специаль- ные команды ассемблеру, которые мы будем называть объявлениями- Объявление внешнего имени имеет вид (метка): ВНЕШ [,[(имя входа )Н. (имя модуля)]]; Объявляемое внешнее имя указывается в поле метки. Необяза- тельность задания части информации в этом предложении обуслов- лена тем, что при составлении данного модуля информация о том, какой вход какого модуля обозначается данным внешним именем, может быть либо известна частично, либо вообще неизвестна — например в случае, когда модуль, на один из внутренних объектов, которого ссылается данное внешнее имя, еще не составлен. Таким образом, возможны следующие конструкции: XI: ВНЕШ, Л1.М0Д1; Х2: ВНЕШ, В-, ХЗ: ВНЕШ, .М0Д2-, Х4: ВНЕШ-, Если информация о внешнем имени задана в его объявлении не полностью, то дополнительная информация должна быть задана ко времени редактирования связей — это дает возможность писать модули независимо друг от друга. Пусть, например, модуль с именем МОДА предназначен для вычисления функции y—f (х) и обраи ение к нему производится с обратной связью, причем переменные, представляющие аргумент и значение функции, принадлежат этому модулю. Допустим, что начало этого модуля помечено меткой АВХ, его команда воз- врата — меткой АВЫХ, аргументу функции в этом модуле дано имя X, а значению функции —имя Т. Тогда обращение к этому модулю из некоторого другого модуля МОДВ для вычисления, например, u=f (и+0.5), где и и о — внутренние для этого модуля переменные, можно записать в виде С, V, =0.5, Л; ПВ, «4-1, АН, АК; П, В, , U; и дать объявления внешних имен: АН: ВНЕШ, АВХ.МОДА; АК: ВНЕШ, АВЫХ.МОДА-, А: ВНЕШ, Х.МОДА; В: ВНЕШ, Т.М0ДА-, В результате редактирования связей и загрузки имена АН, АК, А и В в модуле МОДВ будут заменены адресами ячеек, в которых раз- мещаются соответственно команды с метками АВХ, АВЫХ и пере- менные X и Т, входящие в состав модуля МОДА, и тем самым будет обеспечено правильное взаимодействие этих двух модулей.
S.5] КОМАНДЫ ТРАНСЛЯТОРУ 473 Заметим, что в некоторых системах программирования информа- ция, задаваемая непосредственно для редактора связей, может отме- нять информацию, заданную ранее в объявлении внешнего имени. Объявление общего имени имеет вид (метка): ОБЩ, (имя общего блока) [. (сдвиг)]; где объявляемое общее имя указывается в поле метки. В системе программирования, допускающей использование общих объектов, обычно разрешается использовать любое количество общих блоков данных. Каждому из них дается свое имя — это имя и указывается в объявлении общего имени, а (сдвиг) задает относительный номер позиции в указанном блоке, на которую ссылается данное общее имя; если сдвиг не задан, то подразумевается позиция с номером 0. В некоторых системах связь модулей по переменным величинам допускается только через общие блоки. В этом случае в модуле МОДВ, содержащем указанное выше обращение к модулю МОДА, надо было бы дать объявление общих имен, например в виде А: ОБЩ, БЛОКД-, В: ОБЩ, БЛОКДА’, (при желании или необходимости иметь в модуле внутренние пере- менные и и о), а в модуле МОДА должны содержаться следующие объявления общих имен: X: ОБЩ, БЛОКД', Т: ОБЩ, БЛОКДА; Объявление входа имеет вид ВХОД, (имя); Внутреннее имя, указанное в адресном поле этого предложения, и объявляется входом, доступным для использования в других моду- лях. Например, в рассматриваемом нами модуле МОДА, в котором X и Т являются внутренними именами, должны содержаться следующие объявления его входов: ВХОД, АВХ\ ВХОД, АВЫХ-, ВХОД, X; ВХОД, Т; 8.5.8. Управление листингом. Ассемблер при своей работе выдает на печать определенную ин- формацию для пользователя, облегчающую ему дальнейшую работу со своей программой — такую распечатку называют листингом. Листинг обычно содержит следующие составные части. Диагностические сообщения об обнаруженных в тексте модуля пользователя синтаксических ошибках. О видах синтаксических
474 АВТОКОДЫ [Гл. В ошибок и о возможной реакции транслятора (в том числе и ассемб- лера) на эти ошибки говорилось в главе 6. Билйстинг — распечатка, состоящая из двух полей: в одном из них печатаются последовательные предложения исходного текста автокодной программы, а рядом с каждым из них печатаются соот- ветствующие им машинные эквиваленты (если они существуют), которые и образуют текст оттранслированной программы. Чтобы пользователю не приходилось иметь дело с языком загрузки (ко- торый, как мы знаем, может быть неудобен для его понимания че- ловеком), вырабатываемый ассемблером загрузочный модуль пе- чатается в терминах условных адресов, но по формату абсолютного модуля, размещенного в памяти с некоторого условного адреса — фиксированного или назначаемого автором модуля. Таблица имен, в которой печатаются имена, использованные в исходном тексте, и те условные адреса, которые были поставлены им в соответствие при распечатке выработанного модуля в билис- тинге. При этом обычно печатаются отдельно таблицы внутренних, внешних и общих имен. На разных этапах работы с модулем его автору бывает нужна различная информация, которую может выдавать ассемблер на печать, поэтому пользователю важно иметь возможность управлять содержанием выдаваемого листинга. Для этого служит специаль- ная команда ассемблеру вида (ВСЕ ) ппил'ГА'тг. J НИЧЕГО I ПЕЧАТАТЬ, < ТАБЛИЦЫ f \БИЛИСТИНГ [.<адрес>]J где фигурные скобки означают альтернативу (т. е. в команде надо ; указать одну из возможностей, перечисленных в этих скобках). Диагностические сообщения выдаются ассемблером всегда. 8.6. Условное ассемблирование Ассемблер при своей работе обычно подвергает обработке все без исключения предложения, образующие текст автокодной про- граммы. Однако в некоторых автокодах предусматривается весьма полезная возможность давать указания ассемблеру исключить из ' рассмотрения (проигнорировать) те или иные группы автокодных х предложений — с тем, чтобы они не нашли никакого отражения в вырабатываемом загрузочном модуле. Рассмотрим следующий несложный пример. Допустим, что с целью последующей отладки составляемой программы ее автор в разных местах программы задал некоторые вспомогательные действия (присваивание переменным определенных тестовых зна- чений, вывод на печать некоторых промежуточных результатов
8.6] УСЛОВНОЕ АССЕМБЛИРОВАНИЕ 475 и т. д.), включив в текст программы соответствующие предложения. При этом на разных этапах отладки он хотел бы выполнять не все, а только отдельные из этих вспомогательных действий. В частности, по окончании отладки все эти вспомогательные действия должны быть исключены из программы. Конечно, можно было бы все эти вспомогательные предложения автокода отперфорировать отдельно от основной части программы, и при подготовке к очередному вы- ходу на машину для отладки изымать из текста программы уже не нужные перфокарты и в соответствующие места вставлять другие перфокарты, на которых заданы те вспомогательные действия, ко- торые должны быть выполнены на очередном этапе. Поскольку автокодный текст при таком способе отладки каждый раз трансли- руется заново, то эти действия будут включены в вырабатываемую ассемблером программу. Однако такой способ требует от человека кропотливой работы по изъятию одних и вставке других перфо- карт, и при ее выполнении весьма вероятны ошибки. С практиче- ской точки зрения было бы удобнее все эти вспомогательные авто- кодные предложения сразу поместить на свои места в программе, а перед каждой трансляцией давать указания ассемблеру о том, какие из этих предложений должны транслироваться, а какие — опускаться. Для этого и служат специальные команды ассемблеру — коман- ды условных переходов. Такие команды задаются предложениями вида [(метка):] АУП, (отношение), (метка ассемблера); Встретив в программе предложение с кодом операции АУП (Ассемб- леру Условный Переход), ассемблер проверяет заданное в этом предложении отношение и в случае его выполнения переходит к рас- смотрению предложения, помеченного заданной меткой ассемблера, игнорируя ту часть автокодного текста, которая находится между данной командой условного перехода и предложением с указанной меткой; если же отношение не выполняется, то ассемблер, как обычно, переходит к рассмотрению следующего по порядку пред- ложения. Чтобы не связывать формулировку указаний ассемблеру с обо- значениями, используемыми в основной части программы, вводятся в употребление специальные метки, которые не могут совпасть с обычными метками в программе, например, (метка ассемблера): :=.(идентификатор) Из этих же соображений целесообразно ввести «пустую* команду для ассемблера, которая используется для того, чтобы отметить нужную точку в программе. Пустая команда имеет вид (метка ассемблера }:ПРОД-,
476 АВТОКОДЫ (Гл S и означает, что ассемблер должен просто продолжить свою работу со следующего предложения. Что касается отношения, то оно определяется обычным образом: (отношение): := (адресное выражение) (знак операции отно- шения) (адресное выражение) Однако мы знаем, что значения адресных выражений полностью определяются будущим распределением памяти. Поэтому, чтобы иметь возможность управлять процессом трансляций при помощи таких отношений, в них необходимо использовать такие величины, значения которых можно было бы определять и изменять в процессе трансляции по усмотрению автора программы. Такие величины называются переменными периода генерации. Переменные периода генерации ведут себя как обычные пере- менные языка программирования — с той лишь разницей, что переменные периода генерации существуют во время трансляции, а обычные переменные — во время выполнения программы. Переменные периода генерации вводятся в употребление с помощью автокодных предложений вида (метка): ППГ; например: А: ППГ-, КЛЮЧ'. ППГ-, Для присваивания значений переменным периода генерации имеется специальная команда ассемблеру — «ассемблерное при- сваивание», которая имеет вид ((метка ассемблера):] АПРСВ, (имя)= (адресное выражение); По этой команде переменной периода генерации с указанным име- нем ассемблер присваивает значение заданного адресного выра- жения, которое, в частности, может быть целым числом или само- определенной величиной. Как и обычно, требуется, чтобы к моменту выполнения этой команды значения всех операндов, входящих в адресное выражение, были уже определены (в части автокодного текста, предшествующей данной команде ассемблеру). Теперь вернемся к нашему примеру. Первую группу дополни- тельных команд в программе можно задать фрагментом вида АУП, КЛЮЧ О, .ЛИ; .................. • • | первая группа .МХ'.ПРОД', Для того чтобы эта группа команд была включена ассемблером в оттранслированную программу, достаточно в начале автокодной программы ввести в употребление переменную периода генерации КЛЮЧ и присвоить ей значение, равное нулю, с помощью команды
8.6] УСЛОВНОЕ АССЕМБЛИРОВАНИЕ 477 ассемблерного присваивания: КЛЮЧ: ППГ; АП PC В, КЛЮЧ=0; Если же вместо второй команды (или после нее) поместить, напри- мер, команду ассемблеру АП PC В, КЛЮЧ^Ь, то в результате выполнения команды АУП в приведенном фраг- менте ассемблер проигнорирует следующую за ней группу авто- кодных предложений, и потому первая группа дополнительных команд не будет включена в оттранслированную программу. Если каждую из вспомогательных групп команд, о которых говорилось выше, оформить в автокодной программе аналогичным' образом, то можно легко и просто управлять их включением в вы- рабатываемую ассемблером программу — для этого достаточно- в начале автокодной программы поместить нужные команды ассем- блерного присваивания. Выше мы рассмотрели возможность, аналогичную оператору «если» алгола if В then S. В ряде случаев может оказаться весьма полезной и возможность, аналогичная условному оператору алгола вида if В then SI else S2, позволяющая включить в определенное место окончательной программы либо одну, либо другую группу команд. Для реализации такой возможности достаточно ввести в употребление команду ассемблеру «безусловный переход», зада- ваемую предложением вида АБП, (метка ассемблера); Тогда включение в программу одной из двух групп команд может быть достигнуто по схеме, изображенной на рис. 8.4. АУП, L^Q, .Ml; ................... } первая группа АБП, .М2', .Ml: ПР0Д-, ................... ] вторая группа .М2: ПР0Д-, Рис. 8.4. Предварительное присваивание переменной периода генера- ции L соответствующего значения (например 1 или 0) обеспечит включение в вырабатываемую ассемблером программу либо пер- вой, либо второй группы команд. Таким образом, теперь для управления работой ассемблера имеется универсальный язык, так что весь без исключения текст, предъявляемый ассемблеру для обработки, можно трактовать как
•478 АВТОКОДЫ [Гл. 8 управляющую программу для ассемблера, тем более что и предло- жения автокода, определяющие обычную машинную команду или константу, можно трактовать как «команду ассемблеру» типа -«записать в вырабатываемую программу команду сложения с ад- ресами, являющимися значениями заданных адресных выраже- ний, и приписать метке этой команды — если она есть — текущее значение счетчика размещения» или «запасти в программе задан- ную константу». Говоря о командах ассемблерного перехода, следует отметить, что в большинстве автокодов допускаются переходы только вперед по тексту, так что возможности многократной обработки ассембле- ром какой-либо части автокодного текста весьма ограничены — они обеспечиваются только командой ДУБЛ. Условное ассемблирование на практике довольно широко ис- пользуется еще и для того, чтобы из одной и той же автокодной программы можно было легко получать различные варианты окон- чательной программы — например, различные модификации какого-либо транслятора, написанного на автокоде. 8.7. Макросредства Большинство современных автокодов предоставляют в распоря- жение пользователя удобный аппарат, позволяющий программным путем расширять набор машинных операций и тем самым сущест- венно облегчать работу по составлению программ,— это аппарат макросов. С идеей расширения набора машинных операций про- граммным путем мы уже встречались, когда рассматривали понятие стандартной подпрограммы и интерпретирующую систему ИС-2. В основе аппарата макросов также лежит понятие подпро- граммы, однако здесь характер использования подпрограмм несколько иной. В рассмотренном ранее способе использования подпрограмм каждая из них присутствовала в программе только в одном экземп- ляре, а для ее использования в основной программе писались об- ращения к ней. При этом возникал ряд дополнительных команд: перехода на подпрограмму, возврата в основную программу, коман- ды в основной программе для передачи подпрограмме значений фактических параметров или строки, содержащие адреса этих параметров, формирующая часть в подпрограмме при передаче «й адресов. Если основная часть (тело) подпрограммы мала, то число этих вспомогательных команд может оказаться даже больше числа основных команд в подпрограмме, и поэтому такое ее исполь- зование оказывается неэффективным. В данном случае было бы выгоднее вместо обращения к подпрограмме записать в основной программе «тело» подпрограммы, модифицированное примени- тельно к соответствующим фактическим параметрам. Эти два спо-
8.71 МАКРОСРЕДСТВА 47» соба использования подпрограмм называются соответственно за- крытым и открытым способами. Аппарат макросов как раз и яв- ляется удобным средством составления подпрограмм на автокоде и их использования открытым способом. Аппарат макросов в автокоде весьма похож, на аппарат про- цедур в алголе: описанию процедуры соответствует макрооп- ределение, а оператору процедуры соответствует макро- команда (или макровызов). Заметим, кстати, что се- мантика оператора процедуры в алголе соответствует именно откры- тому использованию тела процедуры. 8.7.1. Макроопределения. Макроопределение служит для описания вводимой в употребле- ние макрооперации, т. е. более крупной операции по сравнению- с машинными. Как и описание процедуры в алголе, макроопределе- ние состоит из заголовка и тела. Заголовок макроопределения, являющийся командой ассемблеру„ представляет собой предложение вида (имя макроУ.МАКРО, (список формальных параметров); Код операции МАКРО является признаком того, что данное пред- ложение является заголовком макроопределения; имя макро ис- пользуется для последующих ссылок на это макроопределение,, а список формальных параметров играет ту же роль, что и в описании процедуры алгола. Тело макроопределения, которое должно следовать за заголов- ком, представляет из себя обычный фрагмент автокодной про- граммы — именно он и определяет по существу вводимую в упот- ребление макрооперацию. Вслед за телом макроопределения должна располагаться команда ассемблеру, состоящая только из кода опе- рации КОНМ, которая является признаком конца макроопре- деления.- Приведем пример макроопределения, которое вводит в употреб- ление операцию нахождения большего из двух значений: г= =max (х, у): МАХ2: МАКРО, X, Y, Z-, заголовок В, X, Y-, ) ' ПУ, X, *4-2, Z; > тело П, Y, , Z,) КОИМ', признак конца Здесь тело макроопределения записано в терминах формальных параметров X, Y и Z: первые два из них представляют те значения,, из которых выбирается большее, а параметр Z представляет полу- чаемый результат.
•480 АВТОКОДЫ [Гл. 8 8.7.2. Макрокоманды. Макрокоманды служат для использования в программе опера- ции, введенной в употребление соответствующим макроопределени- ем. Каждая макрокоманда представляет из себя предложение вида [(метка):] (имя макро),(список фактических параметров); Назначение каждой компоненты этого предложения очевидно. В качестве фактических параметров допускаются адресные выра- жения. Используя терминологию алгола, можно сказать, что все фактические параметры вызываются по наименованию. Так, с использованием данного ранее макроопределения МАХ2 для вычисления &у=шах (и, о) в программе достаточно написать макрокоманду L: МАХ2, U, V, W; В процессе трансляции каждая макрокоманда заменяется ас- семблером на тело макроопределения, имя которого указано в макро- команде, с заменой в этом теле формальных параметров на соот- ветствующие им фактические параметры — этот процесс назы- вается макроподстановкой. В частности, приведенная выше макро- команда будет заменена последовательностью автокодных команд: L: В, U, И; ПУ, U, * + 2, W; П, V, , U7; Как видно, открытый способ использования подпрограмм обеспе- чивает получение более эффективной программы с точки зрения ее быстродействия, а при небольшом числе команд, образующих тело подпрограммы,— и о точки зрения расходования памяти. Если же тело подпрограммы содержит достаточно большое число команд и в программе содержится несколько обращений к ней, то открытый способ может привести к слишком большим затратам памяти, и в та- ких случаях следует отдать предпочтение закрытому способу. Поскольку число параметров в макроопределениях не ограни- чивается, то заголовок макроопределения и макрокоманда всту- пают в некоторое противоречие с бланком автокода (в случае блан- кового автокода), ибо это число может быть больше числа подполей адресов. Выход из этого противоречия предоставляется возмож- ностью использования в автокоде строк-продолжений. 8.7.3. Способы задания параметров. Выше мы использовали позиционный способ установле- ния соответствия между формальными и фактическими парамет- рами — путем их сопоставления в обоих списках слева направо. Другими словами, соответствие устанавливается по тем позициям в списках, которые'занимают формальные и фактические параметры {этот же способ используется и в алголе).
%7j МАКРОСРЕДСТВА 481 В: некоторых языках (в том числе и во многих автокодах) это соответствие устанавливается не по позициям, а по именам: в списке фактических параметров в явном виде указывается, какому фор? мальному параметру ставится в соответствие тот или иной факти- ческий параметр. В этом случае каждый элемент списка фактиче- ских параметров обычно задается в виде Х—А, где через X обозна- чен формальный, а через А — фактический параметры. С исполь- зованием такого способа задания соответствия рассмотренная выше макрокоманда для вычисления ш=шах (и, о) запишется в виде L-.MAX2, X=U, Y=V, Z=W-, Здесь каждый фактический параметр как бы снабжен «ключом», который и определяет, какому формальному параметру ставится в соответствие данный фактический параметр (в данном случае роль ключа выполняет имя формального параметра). В связи с этим параметры, снабжаемые ключами, принято называть клю- чевыми параметрами. В случае ключевых параметров позиции списка, в которых они указаны, уже не играют роли, так что вместо приведенной выше можно было бы с одинаковым эффектом записать макрокоманду L.MAX2, Y=V, Z=W, X=U; На практике довольно часто используется смешанный способ, когда часть параметров является позиционными, а часть — клю- чевыми. Это отличие отражается в списке формальных параметров: например, в этом списке сначала перечисляются все позиционные, а затем — ключевые параметры, причем в качестве признака клю- чевых параметров обычно используется знак =• , записываемый после имени параметра. Так, заголовок макроопределения СУМ: МАКРО, X, Y, U=, V=; означает, что параметры X и Y являются позиционными, a U и V — ключевыми. В макрокоманде, использующей данную макроопе- рацию, в первых двух позициях должны быть указаны фактические параметры, соответствующие X и Y (в указанном порядке), а за- тем — в произвольном порядке — указываются фактические пара- метры, соответствующие U и V, например: СУМ, А, В, V=C, l/=D; В некоторых автокодах (и других языках программирования) - разрешается задавать не все, а только часть фактических парамет- ров, используя «принцип умолчания», который в данном случае заключается в том, что если какой-либо фактический параметр не задан, то вместо него подставляется некоторое стандартное зна- чение. В этом случае и макроопределение может выглядеть не- сколько иначе — в его заголовке некоторым формальным парамет- рам ставятся в соответствие определенные априорные значения. Например, макроопределение, вводящее в употребление операцию э. 3. Любимский и др.
482 АВТОКОДЫ [Гл. 8 вычисления z/=sin (х) с точностью е, может иметь заголовок: ASIN: МАКРО, Х= , У= , EPS= =0.00001; в котором параметру EPS предписано стандартное значение, рав- ное 10-$. Если в программе требуется вычислить u=sin (о) с точ- ностью 10~•, то в макрокоманде надо задать три фактических пара- метра ASIN, Х=У, У=С/, £PS= =1в—8; Если же при вычислении и устраивает «стандартная» точность 10-s, то в макрокоманде достаточно задать только два фактических пара- метра: ASIN , Х=У , У={/; Ключевые параметры удобны в тех случаях, когда применяется принцип умолчания и разрешается задавать не все, а только часть параметров. В таких случаях особенно удобен смешанный способ, при котором обязательные параметры являются позиционными, а необязательные — ключевыми. Следует подчеркнуть, что имя макро, указываемое в поле метки заголовка макроопределения, выполняет роль названия (мнемо- кода) новой, укрупненной операции, поэтому в дальнейшем имя макро может использоваться только в качестве кода операции в макрокомандах — появление этого имени в адресном поле какой- либо команды, например в команде ПБ, , МАХ2; квалифицировалось бы ассемблером как синтаксическая ошибка. 8.7.4. Макроопределения и условное ассемблирование, В макроопределениях также может использоваться условное ассемблирование. Более того — именно в макроопределениях ус- ловное ассемблирование и используется чаще всего, так как оно позволяет сделать из тела макроопределения достаточно универ- сальную заготовку и затем получать из нее различные, минимально- необходимые последовательности команд при разных макроподста- новках. Например, о использованием условного ассемблирования можно дать одно макроопределение, с помощью которого в программе можно получать последовательности команд для нахождения как большего, так и меньшего из двух задаваемых значений. Для этого добавим в макроопределение еще один параметр Р, значение кото- рого задается в макрокомандах и который также может использо- ваться в качестве переменной периода генерации: MINMAX: МАКРО, Р, X, У, Z; АУП, P=l, .MIN; В, X, У, ; АБП, .МАХ; .MIN: В, У, X, ;
8.7) МАКРОСРВДСТВА 483 .МАХ: ПУ, X, *4-2, Z; П, Y, , Z; КОНМ-, Нетрудно убедиться в том, что по макрокоманде M1NMAX, 1, U, V, W; в программе будут сгенерированы команды В, V, IP, ПУ, U, «4-2, W\ W:=U и переход к *4-2 при o>sfalse П, V, , W; W: — V вычисляющие 5У=ггнп (U, V), а по макрокоманде MINMAX, О, U, V, W: будут сгенерированы команды В, U, V, ; = ПУ, U, *4-2, W; W:—U и переход к *4-2 при ®=false П, V, , W-, W:=V вычисляющие IF=max (U, У)- Условное ассемблирование в макроопределениях позволяет также получать наиболее эффективные программы с учетом значе- ний фактических параметров, если эти значения известны заранее, к моменту трансляции. Именно в такой возможности и заключается главное достоинство аппарата макросов. В качестве иллюстрации рассмотрим простейший пример. До- пустим, что при решении некоторой задачи довольно часто прихо- дится вычислять значения вида хр, где х — переменная, ар — целое без знака, причем 2^р^8, например х8, и т. д., так что при программировании целесообразно с помощью макроопределения ввести в употребление операцию возведения в степень, показателем которой является целое без знака из указанного выше диапазона. Конечно, в макроопределении можно было бы предусмотреть стандартную схему вычисления у=хр: POW: МАКРО, Р, X, У; П, X, , У; у:=х РА 9 • f-=9 L: У, У, X, У; у:=уХх ПМ, Р, *—1, 1 ( ); Повторение при f<Zp и Макроопределение получилось достаточно компактным, но при его использовании по любой макрокоманде в программе генерировалось бы четыре команды, по которым на вычисление хР требуется 2р тактов работы машины. Очевидно, что программа в этом случае по- лучилась бы не эффективной — ведь на самом деле для вычисления х* достаточно одной команды, для вычисления х8 и х* достаточно вы- полнить по две команды и т. д. 16»
484 ' АВТОКОДЫ [Гл. 8 Для получения более эффективной программы целесообразнее дать такое макроопределение, которое учитывало бы задаваемое значение р и для каждого из этих значений обеспечивало генера- цию в программе минимально необходимой последовательности команд. Для достижения этой цели можно использовать условное ассемблирование в макроопределениях. Можно дать, например, следующее макроопределение (мы не будем особенно заботиться об экономии автокодных предложений в макроопределении, ибо это никак не отразится на качестве получае- мой программы, а сделаем его максимально понятным): POW: МАКРО, Р, X, Y; У, X, X, Y у:=хХх АУП, Р—2, .М при р=2 выход АУП, Р=£3, .L4 У, Y, X, Y у: =уХх АБП, .М при р=3 выход .IA: АУП, Р^4, .L5 У, Y, Y, Y У-=У^У АБП, .М при р=4 выход .L5: АУП, Р^Ь, .L6 У, Y, Y, Y у:=ухх У, Y, X, Y W-^y'Xy АБП, .М при р=5 выход .L6: АУП, Р¥$, .L7 У, Y, X, Y у.—уХх У, Y, Y, Y У^уУ-У АБП, .М при р=6 выход ,L7z АУП, Р^7, .L8 У, Y, X, Y у.=уХх У, Y, Y, Y у—уху У, Y, X, Y у:=уХх АБП, .М при р—7 выход .L8: У, Y, Y, Y у-~уху У, Y, Y, Y у-—уху ЛИ: ПРОД-, КОНМ-, Нетрудно убедиться в том, что теперь по макрокоманде POW, 2, U, V-, в программе будет сгенерирована только одна команда, по макро- командам (POW, 3, U, V;) и (POW, 4, U, V',) — по две команды и т. д. Таким образом, для каждого конкретного значения р в про- грамме генерируется минимально необходимая последовательность команд, что и обеспечивает ее высокую эффективность в отношении выполнения данной операции. Ясно, что при закрытом способе ис- пользования подпрограмм подобного рода возможности практически нереализуемы.
МАКРОСРЕДСТВА 485 «U71 8.7.5. Локализация объектов макроопределений. Как и тело процедуры в алголе, тело макрооопределения может представлять собой довольно большую часть программы. В нем так- же — наряду с формальными параметрами — допускается исполь- зование как своих собственных имен, локализованных в этом теле,. так и имен из основной программы. Имена, определяемые внутри макроопределения, считаются локализованными в нем. Из этого сле- дует, что соответствующие объекты программы и метки ассемблера должны дублироваться при каждой новой макроподстановке. Рассмотрим, например, макроопределение, содержащее локализо- ванную в нем метку: МАХ2: МАКРО, X, У, Z; В, X, У; ПУ, Х.М, Z; П, У, , Z; М: ПАМ, 0; К0НМ-, Здесь метка М локализована в макроопределении и должна полу- чить значение, равное, адресу той команды в программе, которая следует за макрокомандой с операцией МАХ2. Ассемблер при каждой очередной макроподстановке должен производить систематическое изменение локальных меток (т. е. последовательно заменять локальную метку М, например, на метки ЛИ, М2 и т. д.), следя при этом, чтобы получаемые таким образом метки не совпадали с какой-либо другой меткой в программе. На- пример, после макроподстановки вызовов МАХ2, А, В, С; МАХ2, Ь, 'e,'F; должен получиться текст: В, А, ‘ В; ПУ, А, ЛИ, С; П, В, , С-, ЛИ:______________ В, D, Е-, ПУ, D, М2, F-, П, Е, , F-, М2:___________________ С одной стороны, этот принцип весьма удобен, так как он позво- ляет разрабатывать макроопределения независимо друг от друга и в значительной мере независимо от разработки основной программы.
486 АВТОКОДЫ [Гл. 3 Однако в нем есть и существенные недостатки. Во-первых, он не- редко приводит к ненужному дублированию, например, рабочих ячеек, которые вполне могли бы быть совмещены в памяти. Во-вто- рых, этот принцип затрудняет передачу информации от одного ма- кроопределения к другому или даже между экземплярами, полу- чившимися в результате двух подстановок одного и того же макро- определения. В дальнейшем мы увидим, что такое общение может быть весьма удобным. Борьба с этими недостатками путем определения в основной про- грамме специальных объектов для «внутренних» нужд используе- мых макроопределений могла бы лечь тяжелым бременем на плечи разработчика основной программы. Вместо этого обычно в макро- определениях допускаются определение и использование общих бло- ков ячеек памяти и общих групп переменных периода генерации. Эти объекты вводятся в употребление по специальным командам ас- семблеру, похожим на объявления ОБЩ: (метка): ОБПАМ [, [(имя общего блока ячеек памяти}] [.(сдвиг)]]; (метка): ОБППГ, (имя общего блока ППГ) [.(сдвиг)]; Как обычно, мы будем считать, что ассемблер допускает использо- вание двух непомеченных общих блоков: один из них служит для связи макроопределений через переменные периода генерации, а другой — через ячейки памяти во время выполнения программы. Таким образом, макроопределения становятся как бы «подмодуля- ми» того модуля, в котором они содержатся. Для иллюстрации использования этого аппарата введем в упо- требление операцию вычисления у—х^. Дадим сначала макроопре- деление, содержащее локализованную в нем ребочую ячейку: Р0Г6: МАКРО, X, У; У , X, X, Р; У , Р, Р, Y-, У , Y, Р, У; ПБ, , *+2; Р: ПАМ, 1 ; КОНМ-, Чтобы рабочая ячейка Р не дублировалась при каждой макропод- становке, можно дать следующее макроопределение, использующее первую ячейку непомеченного общего блока ячеек памяти: P0F6: МАКРО, X, У; Р: ОБПАМ-, У , X, X, Р; У , Р, Р, У; У , Y, Р, У; КОНМ-,
8.7] МАКРОСРЕДСТВА 48? 8.7.6. Макрооперации в макроопределениях. Для упрощения работы по составлению макроопределений, а также для обеспечения компактности их записи, автокод обычно разрешает использование в макроопределении макроопераций, вве- денных в употребление с помощью других макроопределений. Эти другие макроопределения могут, в частности, являться глобаль- ными по отношению к данному макроопределению, т. е. находиться вне его. Такая возможность позволяет быстро и удобно наращивать набор макроопераций, которые затем могут использоваться при сос- тавлении основной программы. Так, с использованием определенной ранее макрооперации МАХ2 (нахождение большего из двух значений) можно весьма про- сто и компактно определить макрооперацию МАХЗ для нахождения наибольшего из трех значений: МАХЗ: МАКРО, X, Y, Z, U; Р: ОБПАМ; МАХ2, X, Y, Р; МАХ2, Р, Z, U; КОНМ; или макрооперацию МА Xi для нахождения наибольшего из четырех значений: MAXit МАКРО, X, Y, Z, U, V; Р1: ОБПАМ; Р2: ОБПАМ, .1; МАХ2, X, Y, РЬ, МАХ2, Z, U, Р2; МАХ2, Р\, Р2, V; КОНМ; Аналогичным образом, используя определенную ранее макроопе- рацию MINMAX, нетрудно определить макрооперацию ММ IN для нахождения z==max{min(x, у), min (u, v)}: MMIN: МАКРО, X, Y, U, V, Z; Р1: Р2: ОБПАМ; ОБПАМ, .1; MINMAX, 1, X, Y, Pl; MINMAX, 1, U, V, Р2; MINMAX, 0, Pl, Р2, Z; КОИМ; Если теперь в автокодной программе встретится, например, макро- команда Л1ЛХ4, А, В, С, М, X;
488 АВТОКОДЫ [Гл. 8 то в результате макроподстановки будет получена следующая по- следовательность автокодных команд: В, А, В; ПУ, А, *+2, Р\; П, в, , Pi; в, С, М; ПУ, с, *+2, Р2; п, М, , Р2 ; в, Р1, Р2; ПУ, Pl, *+2, Х\ п, Р2, , X; В макроопределении могут также использоваться и локализо- ванные в нем макроопределения — например, для обеспечения боль- шей его компактности (аналогично тому, что в теле процедуры ал- гола могут быть даны описания других процедур). Так, макроопе- рацию нахождения наибольшего из четырех значений можно задать и таким макроопределением: МАХ4: МАКРО, х, Y, Z, МАХ2: МАКРО, А, в, С; в, А, В; ПУ, А, < *+2, С; п, в, 9 С; КОНМ-, Р1: ОБПАМ-, Р2- ОБПАМ, .1; МАХ2, X, Y, Pi; МАХ2, Z, и, Р2; МАХ2, Pi, Р2, V; КОНМ-, U, V; Естественно, что в этом случае макроопределение МАХ2 нельзя было бы использовать нигде вне макроопределения МАХ4. 8.7.7. Системные макроопределения. До сих пор мы исходили из того, что все макрооперации, которые используются в программе, должны быть определены с помощью макроопределений в самой этой программе. Однако это вовсе не обя- зательно — точно так же, как в случае использования подпрограмм закрытым способом организуется библиотека стандартных подпро- грамм, доступная для всеобщего использования, можно организо- вать и библиотеку макроопределений, которые могут использовать- ся в любой конкретной программе (аналог стандартных процедур в алголе). Макроопределения, включенные в библиотеку, называются системными .(или. библиотечными} макроопределениями. Набор системных макроопределений может постоянно расширяться (как и библиотека стандартных подпрограмм). Ясно, что наличие доста-
МАКРОСРЕДСТВА 489 В.71 точно богатой библиотеки существенно упрощает и ускоряет состав- ление программ. 8.7.8. Макро в языках высокого уровня. Аппарат макросов в автокоде представляет собой некоторую над- стройку над языком, в помощью которой вводятся в употребление более содержательные операции — причем только такие, которые могут быть выражены через основные операции языка. Но ведь эта же идея может быть применена и к языкам высокого уровня, например к алголу: о помощью макроопределений можно вводить в употребление новые типы операторов, которых нет в ал- голе, но которые можно выразить через имеющиеся типы операто- ров. Так, в алголе оператор цикла вида for V: =А while В do S по своему содержанию эквивалентен последовательности других операторов, имеющей вид £.у._Д. И“|В then go to ВЫХОД-, S; go to L\ ВЫХОД-. В связи с этим можно было бы считать, что в алголе нет операторов цикла, а есть системные макроопределения, с помощью которых и введены в употребление различные виды операторов цикла, напри- мер макроопределение begin ЦИКЛ ПЕРЕСЧЕТ (ПЕР, ВЫР, УСЛ, ОПЕРАТОР}-, L- ПЕР-.—ВЫР-, if 1 УСЛ then go to М-, ОПЕРАТОР-, go to L; M: end МАКРО При наличии такого макроопределения в тексте алгол-програм- мы можно было бы написать, например, «макрооператор» . . .; ЦИКЛ ПЕРЕСЧЕТ (I, Ж, х [»Ж|2); . . . Если сделать обычную макроподстановку, то мы получим в про- грамме ту же последовательность операторов, которая задается в неявном виде с помощью оператора цикла алгола. Так что.и в язы- ках высокого уровня с помощью макроопределений можно вводить в употребление новые, более удобные для решения той или иной задачи, типы операторов, например другие типы операторов цикла по сравнению с теми, которые есть в алголе. Казалось бы, используя эту идею, можно обойтись без большого разнообразия алгоритмических языков— достаточно было бы иметь один «базовый» язык и на его основе с помощью макроопределений строить любой нужный язык. К сожалению, на этом пути возникают и серьезные осложнения.
490 АВТОКОДЫ [Гл. 8 Одно из них состоит в том, что рассмотренный выше способ обра- щения к макрооперациям приводит к менее наглядной записи (по сравнению, например, с обычной записью оператора цикла в алго- ле). Эго неудобство особенно сильно проявляется в том случае, когда в составе задаваемого оператора (например, составного) со- держатся другие операторы цикла. Частичный выход из этого за- труднения можно найти, если в заголовке макроопределения и мак- ровызовах разрешить перемежать параметры и другие термины, образующие заголовок макроопределения или макровызов. В этом случае приведенное выше макроопределение могло бы иметь вид: begin for ПЕР: = ВЫР while УСЛ do ОПЕРАТОР-, {тело Ъ макроопределения j end МАКРО Здесь именем макро можно считать совокупность всех раздели- телей, между которыми расположены формальные параметры (for,while, do). Макровызов записывается так же, как и заголо- вок макроопределения, только вместо формальных параметров, вхо- дящих в этот заголовок, записываются фактические параметры: for i:=t*4-l while i^n. do x [/]:=# U] 4- i f 2 в результате чего получается обычная алгольная запись. Естествен- но, что макропроцессор должен быть сделан так, чтобы он допускал такую форму записи макровызова. Наряду с проблемой наглядности записи, при макрорасширени- ях над языками высокого уровня возникает и другое осложнение, проистекающее из нарушения локальности макроподстановки. Эта локальность заключалась в том, что фрагмент, вставляемый в про- грамму в результате макроподстановки, определялся только самим макровызовом и используемым в нем макроопределением и больше ни от чего не зависел, в то время как во многих реальных языках программирования дело обстоит иначе. Например, запись на алголе %: =0.6 еще не определяет однозначно ту последовательность команд, с помощью которых реализуется данный оператор—это зависит от приведенного ранее описания идентификатора х. Если он был описан как real х, то данный оператор реализуется командами, пересыла- ющими запасенную константу 0.6 в ячейку, отведенную для пере- менной х. Если же идентификатор х был описан как integer х, то указанных команд будет недостаточно, так как вещественное значе- ние 0.6 еще должно быть преобразовано в целочисленное значение. Эта ситуация связана с тем обстоятельством, что языки програм- мирования, вообще говоря, являются контекстно-зависимыми (как и разговорный язык), а учет контекста весьма затруднителен. Мы уже знаем, что в некоторых макропроцессорах предусматри- вается определенный учет контекста путем введения общих пере- менных периода генерации.
8.71 МАКРОСРЕДСТВА . 491 Например, в приведенном ранее макроопределении * MIN МАХ: МАКРО, Р, X, Y, Z; АУП, P=l, .MIN; В, X, Y; АБП, .МАХ-, .M1N: В, Y, Х-, .МАХ: ПУ, X, *4-2, Z-, П, Y, , Z-, КОНМ-, все параметры (в том числе и параметр Р, используемый В йайёстве переменной периода генерации) являются локальными — они зада- ются при каждом макровызове, так что это макроопределение и ка- кой-либо макровызов, например MINMAX, 1, U, V, W-, уже однозначно определяют результат макроподстановки, который тем самым не зависит от контекста. Чтобы сделать возможным учет контекста, исключим Р из числа параметров и определим в основной программе переменную периода генерации с этим именем. Тогда указанную макрооперацию MIN МАХ можно задать макроопределением с тремя параметрами: MINMAX: МАКРО, X, Y, Z; АУП, P=l, .MIN; В, X, Y-, АБП, .MAX-, В, Y, Х-, ПУ, X, *4-2, Z; П, Y, , Z; КОНМ-, -.MIN: .MAX: Как видно, в этом случае при макрокоманде MINMAX, U, V, W результат макроподстановки зависит не только от заданных пара- метров, но и от значения глобальной переменной периода генера- ции Р, с помощью которой и можно передать информацию о внеш- ней ситуации. Можно избавить основную программу от необходимости работать с переменными периода генерации, придав ей более наглядную фор- му. Для этого мы введем еще одно макроопределение и используем связь между макроопределениями через общую переменную периода генерации: ВИД: МАКРО, R ; Р-. ОБППГ-, АПРСВ, Р=0; АУП, R~T(MAX), .М-, АПРСВ, Р=1; ЛИ; ПРОД-, КОНМ-,
492 АВТОКОДЫ [Гл. 8 Если в макроопределение MINMAX вставить строку Р: ОБППГ-, то при трансляции автокодного текста, имеющего вид ВИД, =T(MIN)-, MINMAX,'А, В,'с:, MINMAX, D, Е, F\ 'вИД,'=т\мАХ),. MINMAX, А, В, н\ ’ по первой и второй макрокомандам с кодом операции MINMAX будут сгенерированы последовательности команд для вычисления C=min (А, В) и F=min (D, Е), а по третьей макрокоманде — по- следовательность команд для вычисления Я=шах (А, В), хотя Все макрокоманды одинаковы. . В этом направлении можно пойти и дальше — ввести в употреб- ление целый массив глобальных переменных периода генерации, через который можно передавать большой объем информации. Од- нако при этом значительно усложняется реализация и, самое глав- ное, пропадет простота и наглядность использования макроопе- раций, которые как раз и обеспечиваются локальностью макро- подстановок. 8.8. Ассемблер Ассемблером называется специальная программа (транслятор), входящая в состав математического обеспечения машины, которая предназначена для перевода модуля пользователя, написанного на автокоде, в загрузочный модуль, представленный на внутреннем языке системы. Входной информацией для работы ассемблера является текст модуля на автокоде и таблица соответствия мнемонических и циф- ровых кодов операций (если мнемонические коды операций зафик- сированы и не изменяются, то эту таблицу можно включить в ас- семблер в качестве его составной части). Результатом работы ассемблера является модуль загрузки, состоящий из тела модуля на внутреннем языке и паспорта модуля, в котором содержится имя модуля, а также таблицы внешних имен, общих имен и входов данного модуля. Ассемблеры принято делить на классы в зависимости от числа проходов по заданному тексту, производимых в процессе работы ассемблера — однопроходные, двухпроходные и т. д. Необходимость нескольких проходов по исходному тексту связана главным образом
8.8] АССЕМБЛЕР 493 с тем, что автокод разрешает использование имени до того, как оно -будет определено. Если, например, при рассмотрении ассемблером -команды (ПБ, ,L\) меткой L помечена одна из предыдущих команд, то значение L (равное адресу команды, помеченной этой меткой L) к моменту трансляции рассматриваемой команды безусловного перехода будет уже определено и эту команду можно оттрансли- ровать. Если же меткой L помечена одна из последующих Команд, то еще неясно, каким адресом следует заменить эту метку, и тем самым невозможно оттранслировать данную команду перехода. В связи с этим первый просмотр исходного текста обычно является предварительным — на этой фазе трансляции собираются сведения об используемых именах и определяются их значеиия, а выработка оттранслированной программы осуществляется при следующих просмотрах исходного текста при использовании информации» полученной при первом просмотре. Конечно, при каждом из про- смотров (в том числе и при первом) транслируемый текст может преобразовываться с целью упрощения работы с ним на следующих фазах, так что по окончании выполнения последней фазы получа- ется и готовый модуль загрузки (при этом по ходу дела состав- ляется и паспорт модуля). Что касается однопроходной трансляции, то в этом случае при первом просмотре исходного текста ассемблер делает все, что можно сделать на этой фазе, оставляя в некоторых командах пустые адреса, если значение соответствующего адресного выражения невозможно вычислить в момент трансляции данной команды. При этом ассемб- лер ведет таблицу незаполненных команд и накапливает необходи- мую информацию — с тем чтобы по окончании просмотра, Иногда будет получена вся необходимая информация, доопределить и соответствующим образом скорректировать те команды, которые раньше не были записаны в окончательном виде, так что вместо второго просмотра ассемблер работает с отдельными командами.^ Однопроходная схема трансляции удобна, в частности, тем, что в этом случае нет необходимости хранить в памяти всю исходную программу — после ввода очереднрго автокодного предложения оно транслируется и дальше не используется, так что следующее предложение можно вводить на то же самое место памяти. При многопроходной трансляции весь исходный текст вводится в па- мять машины и хранится в ней столько времени, сколько это нужна для работы ассемблера. С другой стороны многопроходная схема более удобна для построения ассемблера, так как era можно раз- бить на отдельные части, каждая из которых осуществляет опреде- ленную фазу трансляции. Поскольку эти .части достаточно авто- номны, то их можно программировать и в случае необходимости модифицировать независимо друг от друга. Кроме того, в процессе работы ассемблера в оперативной памяти можно иметь не весь ас- семблер, а только работающую его часть.
494 АВТОКОДЫ [Гл. 4 Мы рассмотрим схему работы двухпроходного ассемблера, что является наиболее типичным случаем. При этом мы будем предпо- лагать, что в автокоде допускается использование — кроме симво* лических команд и констант — следующих команд ассемблеру: ПАМ, ЭКВ, ВХОД, ВНЕШ, ОБЩ, ПЕЧАТЬ, НАЧАЛО и ДОНЕЦ. Общая схема трансляции имеет вид, представленный на рис. 8.5. Теперь надо описать и н т е р ф е й с, т. е. связь между частями ассемблера, реализующими эти две фазы: четко определить, какую информацию и в какой форме одна часть передает другой. При четко описанном интерфейсе отдельные части ассемблера могут программироваться независимо друг от друга. Итак, задачей первой фазы является сбор сведений об исполь- зуемых именах — эти сведения необходимы для последующего перевода автокодных предложений в машинные слова, образующие тело модуля, а также для составления его паспорта. Эти сведения, Рис. 8.5. полученные во время первой фазы, будут оформлены в виде т а б* Л и ц ы имен (ТИ) следующим образом. Для каждого имени в ТИ отводятся два машинных слова, со- держащие следующую информацию: 45 I...I 41 401...I 2 I I Имя Тип Значение
8.81 АССЕМБЛЕР 495 Поскольку имя содержит не более пяти символов, то для его хра- нения отведем младшие 40 разрядов первого слова. Последователь- ные символы имени представляются последовательными восьмер- ками двоичных разрядов (байтами). Допустимые типы имен перенумеруем, и номер типа имени будем хранить в первом поле второго слова. Значение имени будем записывать во втором поле этого слова. Номера типов имен, а также значения имен различных типов определим следующим образом: Тип имени N Значение имени Внутреннее Внешнее Общее Эквивалентное 1 Относительный адрес в теле модуля 2 Порядковый номер внешнего имени 3 Порядковый номер общего имени 4 Адресное выражение, через которое определяется имя Если наложить дополнительное ограничение — что все имена, входящие в адресное выражение, через которое определяется имя с помощью операции ЭКВ, должны быть определены раньше,— то в ТИ можно было бы сразу заносить соответствующие значения для имен четвертого типа; в противном случае эти значения должны доопределяться в конце первой фазы. Мы такого ограничения на- кладывать не будем и ради единообразия все необходимые дооп- ределения будем делать в конце фазы. .При этом в качестве адресных выражений в предложениях ЭКВ^б^дем допускать только имена или целые без знака. л'ч При практическом использовании ассемблера важную роль играет выдаваемая им на печать диагностика об обнаруживаемых ошибках. Однако мы при рассмотрении схемы трансляции ограни- чимся минимальными возможностями в этом отношении, которые будут носить лишь иллюстративный характер. 8.8.1. Первая фаза работы ассемблера. Итак, задача первой фазы ассемблирования — определить зна- чения используемых в автокодной программе имен. Результатом выполнения этой фазы является описанная выше таблица имен (ТИ). Для получения значений имен, заносимых в ТИ, использу- ются: счетчик размещения (СЧР), текущее значение которого равно относительному адресу в вырабатываемой модуле, начиная с ко- торого начинается машинная реализация очередного автокодного предложения; счетчик внешних имен (СЧВНЕШ), текущее значение которого равно порядковому номеру внешнего имени в таблице внешних имен, которая будет формироваться на второй фазе работы ассемблера и включаться в паспорт модуля (нумерация во всех таблицах ведется, начиная с нуля); аналогичные счетчики для общих имен (СЧОБЩ) и входов в модуль (СЧВХ). Для составления ТИ понадобится счетчик заносимых в эту таблицу имен, который обозначим через i. Через п обозначим число
АВТОКОДЫ СЧР: =СЧВНЕШ- =СЧОБ1Ц: = СЧВХ:=О; j:=Q | еткаб ТИ[2*1у^<метка> j:=2*i+1i l:=l+1 Ввод очередной строки Выделение поля метки нет Диагностика „повторное* определение имени <метка>; ]:=2*к+1, где к-порядковый номер данного имени в ТИ ^ПЕЧАТЬ Выделение и дешифрация (7)%^ поляКОП ПАМ. КОНСТ ВНЕШ . .IR___ эк в £ _ ________________г | тнт=1;'зн: =СЧР-, СЧР:=СЧР+1 ~~| нет Станд. г маш, операция \Т14[Ц^(Ш9ЗК)\ ВХОД ф ТИП-1 ЗН:=СЧР СЧВК=СЧВХ-И ПЕЧАТЬ Фиксация режимов печати конец ОВраТТотка Осехэкввтн Рис, 8,6,
8.8) АССЕМБЛЕР 497 строк в загрузочном модуле, являющихся машинным эквивалентом транслируемого автокодного предложения (для некоторых авто- кодных предложений эТот эквивалент может быть и пустым). Пе- . ременные ТИП и ЗН будут представлять соответственно тип об- рабатываемого имени и е»значение. Под словами «ввод очередной строки» будем понимать выде- ление из транслируемого автокодного текста очередного его пред- ложения, подлежащего рассмотрению. В качестве иллюстрации возможностей ассемблера «устранять» некоторые из обнаруживаемых в процессе трансляции ошибок предусмотрим при повторных определениях какого-либо имени выдачу диагностического сообщения и принятие во внимание ас- семблером последнего из таких повторных определений (и тем самым игнорирование предыдущих). При использовании введенных обозначений первую фазу работы ассемблера можно изобразить блок-схемой, приведенной на рис. 8.6. 8.8.2. Вторая фаза работы ассемблера. Задачей второй фазы работы ассемблера является генерация тела модуля загрузки и формирование его паспорта. При трансляции автокодного предложения, задающего машин- ную команду, ее мнемонический код операции заменяется цифро- вым. Для этого ассемблер использует таблицу соответствия мне- монических и цифровых кодов операций (ТКОП), которая состоит из пар строк вида мнемонический КОП цифровой КОП В этой таблице перечисляются все мнемонические коды операций, разрешенные к использованию в данном автокоде — в том числе и коды операций, адресованных ассемблеру (ПАМ, ЭКВ и т. д.). Это полное перечисление необходимо, например, для выявления синтаксических ошибок в транслируемой программе. Поскольку команды транслятору не переводятся в их машинные эквиваленты, то мнемоническим кодам операций, адресованных ассемблеру, можно не ставить в соответствие каких-либо цифровых кодов, однако для удобства работы с таблицей для каждой из них также отводится пара строк (содержимое второй из них может быть про- извольным). Если мнемонические кода операций не меняются при эксплуатации ассемблера, то таблица ТКОП является постоянной, и обычнб включается в состав ассемблера.
498 АВТОКОДЫ (ГЛ. 8 Адресные выражения в автокодных командах заменяются на их значения (значения имен, входящих в адресные выражения, берутся из таблицы ТИ, составленной на первой фазе). При трансляции предложения, задающего константы, каждая из них в случае необходимости переводится в двоичную систему счисления и представляется в виде соответствующего машинного слова. Для формирования паспорта модуля ассемблер предварительно составляет таблицы внешних имен (ТВНЕШ), общих имен (ТОБЩ) и входов (ТВХ). Таблица внешних имен заполняется по мере вы- явления в тексте объявлений внешних имен, т. е. предложений вида (метка ):ВНЕШ[,[ (имя входа)][. (имя модуля >11; например: АН: ВНЕШ, А.МОДХ', (напомним, что порядковые номера внешних и общих имен, а также входов, присваиваемые этим именам в качестве их значений в ТИ во время первой фазы, определялись по мере выявления в исходном тексте соответствующих объявлений). Для каждого внешнего имени в ТВНЕЩ отводятся две строки вида правая метка имя модуля Само определяемое внешнее имя в таблицу можно не заносить, поскольку оно содержится в ТИ, и в качестве значения этого имени в ТИ зафиксирован его порядковый номер, по которому однозначно определяется соответствующая ему пара строк в ТВНЕШ. Если в объявлении внешнего имени какая-либо из компонент в правой его части отсутствует, то соответствующая ей строка из указанной выше пары строк оставляется пустой — в дальнейшем такие пустые строки будут заполнены редактором связей. Таблица общих имен заполняется по мере выявле- ния в тексте объявлений внешних имен, например X: ОБЩ, БЕТА. 5 Для такого типа объявления в ТОБЩ заносится очередная пара строк вида имя общего блока сдвиг
8.8] АССЕМБЛЕР 49» Само общее имя в эту таблицу не заносится по тем же причинам,, что и в случае таблицы ТВНЕШ. Если в объявлении сдвиг не задан» то он принимается равным нулю. Таблица входов заполняется по мере выявления объяв- лений входов, например ВХОД, ВХ2‘, Для такого объявления в таблице ТВХ формируется очередная пара строк вида входная метка ее значение Вообще говоря, можно было бы обойтись без предварительного формирования этой таблицы, поскольку вся заносимая в нее ин- формация уже получена в ТИ, однако ради единообразия работы с таблицами мы предусмотрим ее формирование во время второй фазы. Обозначим через СЛОВО группу из N машинных слов, в которые переводится очередное автокодное предложение и которые записы- ваются в очередные N строк вырабатываемого загрузочного модуля. Массив ячеек памяти, в кагором размещается загрузочный модуль, назовем ПРОГ. При использовании введенных обозначений вторую фазу работы ассемблера можно изобразить блок-схемой, приведенной на рис. 8.7. В рассмотренной схеме работы ассемблера предполагалось, что в автокоде отсутствуют макросредства (макроопределения и макрокоманды). При наличии в автокоде таких средств возможны два способа построения ассемблера. Первый способ заключается в том, чтобы в ассемблере преду- смотреть отдельную его часть, называемую макрогенератором, назначение которого состоит лишь в том, чтобы в исходной програм- ме, содержащей макроопределения и макрокоманды, выполнить все необходимые макроподстановки. Таким образом, в результате работы макрогенератора получается расширенный (за счет проде- ланных макроподстановок) текст автокодной программы с удале- нием из него всех макроопределений — так, как если бы программа сразу была написана на автокоде без использования макросредств. Эта автокодная программа затем уже и транслируется ассемблером в загрузочный модуль по рассмотренной нами схеме. Таким образом, в этом случае обработка исходной автокодной программы идет по схеме Текст . с макро Макро-генератор Текст . без макро-^ Ассемблер . Модуль ~загрузка
6 GO АВТОКОДЫ (Гл. в Рис. 8.7.
«.8) АССЕМБЛЕР SOI Недостаток', этого способа состоит Ъ том, что для выполнения макрогенератором своих функций ему также приходится про- сматривать весь текст исходной программы, чтобы выявить все содержащиеся в нем макрокоманды и сделать соответствующие макроподстановки. Однако этот способ построения ассемблера имеет и свои достоинства. Главное из них состоит в том, что если первоначально в автокоде не были предусмотрены макросредства, то их включение (а также возможные последующие изменения этих средств) не требует переделки существующего ассемблера — для этого достаточно составить (или изменить) макрогенератор, согла- совав его работу с имеющимся ассемблером. Кроме того, при тран- сляции программ, в которых макросредства не используются, их сразу можно передавать на обработку ассемблеру, минуя макро- генератор. Второй способ заключается в том, чтобы расширить функции ассемблера, поручив ему и выполнение функций макрогенератора. Ассемблер, выполняющий и эти функции, называется макроассемб- лером, работающим по схеме: Текст Макро-ассемблер Модуль с макро-’' ^загрузки Эта схема трансляции является более эффективной с точки зрен- ии я затрат машинного времени, поскольку макроподстановки можно сделать при том же просмотре автокодного текста, при котором составляется таблица имен (во время первой фазы). Однако в этом случае ассемблер, естественно, усложняется за счет возлагаемых на него дополнительных функций, а также в определенной мере снижается его быстродействие при трансляции программ, не ис- пользующих макросредств.
Г лав a 9 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ 9.1. Причины возникновения 9.1.1. Быстродействие и производительность ЭВМ. Одной из важнейших характеристик каждой ЭВМ является ее быстродействие, под которым понимается среднее число машинных операций (с учетом того, что операции различных типов выполняются с различной скоростью), выполняемых данной ЭВМ в единицу времени. Однако быстродействие ЭВМ не определяет полностью ее ре- альную производительность: если на решение какого- либо набора задач на данной ЭВМ затрачивается К единиц времени ее непрерывной работы, то повышение быстродействия этой машины, например в 10 раз, еще не означает, что на решение того же самого набора задач будет затрачено в десять раз меньше времени ее ра- боты. Дело в том, что быстродействие ЭВМ определяется в предпо- ложении, что при выполнении программ используется только опе- ративная память (ОП), т. е. память, состоящая из адресуемых ячеек, каждая из которых непосредственно доступна процессору. Быстродействие ЭВМ в значительной мере определяется быстродей- ствием ее оперативной памяти, поэтому ее делают достаточно быст- рой. Так, в большинстве современных ЭВМ оперативная память реализуется с использованием весьма быстрых элементов — фер- ритов, что обеспечивает обращение к памяти для чтения или записи одного слова за время порядка 1 мксек. (1 микросекунда=10“* с.). Но для того чтобы на ЭВМ можно было решать достаточно широкий класс задач, ее память должна иметь достаточно большую е м- кость. Делать же быструю память очень большой емкости не- выгодно, так как она и обходится дорого, и при ее изготовлении возникают серьезные технологические трудности. Например, в ферритовой памяти каждый двоичный разряд реализуется с помощью, отдельного физического элемента (феррита), так что на изготовление памяти из N 45-разрядных ячеек требуется 45М отдельных ферри- тов, которые надо объединить в единую схему, обеспечив прямой доступ к каждой ячейке без заметной потери быстродействия са- мого элемента, что представляет довольно сложную задачу.
в. 1] ПРИЧИНЫ ВОЗНИКНОВЕНИЯ 503 Для выхода из этого затруднения можно воспользоваться тем обстоятельством (отмеченным ранее при рассмотрении различных систем команд), что при выполнении программ частота обращений к разным ячейкам памяти различна. Эта неравномерность обра- щения к ячейкам памяти наводит на мысль о том, что то массивы данных, которые в процессе выполнения программы используются сравнительно редко, можно хранить не в оперативной, а в какой-то вспомогательной памяти, вызывая эти массивы данных (или их порции) в оперативную память только по мере необходимости на время их обработки. Поскольку обращения к вспомогательной памяти за порциями хранящихся в ней данных производятся го- раздо реже, чем обращения к оперативной памяти, то ее быстродей- ствие не играет решающей роли — важно, чтобы обеспечение боль- шой ее емкости обходилось сравнительно недорого. На самом деле все современные ЭВМ имеют такую вспомогательную память, которую обычно называют внешней памятью (ВП) — в отличие от нее оперативную память называют еще и внутренней. Можно счи- тать, что внешняя память также состоит из отдельных ячеек, каж- дая из которых предназначена для хранения одного машинного слова. Принципиальное различие между оперативной и внешней па- мятью состоит в том, что процессор имеет непосредствен- ный доступ т о л ь к о к ячейкам оперативной памяти; машинные же слова, хранящиеся в ячейках внешней памяти, могут быть ис- пользованы процессором только через посредство оперативной памяти, т. е. эти слова предварительно должны быть переданы (прочитаны) в оперативную память. В случае необходимости дан- ные, хранящиеся в оперативной памяти, могут быть переданы на хранение (записаны) во внешнюю память. 9.1.2. Виды внешней памяти. Наиболее распространены три вида устройств, на которых реализуется внешняя память: магнитные барабаны (МБ), магнит- ные диски (МД) и магнитные ленты (МЛ), причем даже в одной и той же ЭВМ, как правило, используются устройства различных видов. Заметим, что все указанные выше виды устройств базиру- ются на явлении остаточного магнетизма. Магнитный барабан представляет собой цилиндр, боковая поверхность которого покрыта магнитным слоем (веществом, спо- собным намагничиваться). По образующей цилиндра, достаточно близко к его поверхности, размещаются магнитные головки, ко- торые служат для чтения и записи информации. Магнитная головка представляет собой кольцеобразный сердечник с обмоткой и не- большим зазором в точке, ближайшей к поверхности барабана. В процессе работы ЭВМ барабан постоянно вращается вокруг своей оси. Если через обмотку головки пропустить импульс тока
504 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ [Гл. » того или иного направления (что соответствует цифре 0 или I), то в сердечнике возникает магнитное поле, которое в зазоре будет частично распространяться в воздухе и тем самым воздействовать на ближайший участок поверхности барабана. Таким образом, участок поверхности, который в этот момент проходит мимо зазора головки, намагнитится в определенном направлении, а в силу остаточного магнетизма это состояние намагниченности сохра- нится, что и соответствует запоминанию двоичной цифры, подава- емой в обмотку записи. Когда в процессе вращения барабана намагниченный участок снова будет проходить мимо зазора головки, в ее обмотке появится электродвижущая сила (ЭДС) индукции соответствующей формы, что и означает чтение запомненной цифры. Для чтения запомненной в определенном участке цифры нужно лишь в соответствующий момент времени подключить обмотку головки к выходным цепям. Совокупность участков поверхности МБ, намагничиваемых одной головкой, называют дорожкой. Современная технология позволяет выделить на одной дорожке десятки тысяч запоминающих участков, поэтому дорожки обычно условно делят на части, на- зываемые секциями. Общая емкость МБ определяется числом дорожек (т. е. числом магнитных головок) и емкостью каждой из них. Намагничиваемые участки, предназначенные для хранения одного машинного слова, могут группироваться по-разному. Можно, например, для хранения слова выделять очередную группу участ- ков, расположенных на одной дорожке,—эта группа участков и будет являться «ячейкой памяти» на МБ. «Адрес» этой ячейки будет состоять из номера дорожки, номера секции в данной дорожке и номера ячейки в этой секции. Поскольку дорожку обслуживает только одна магнитная головка, то при обращении к МБ для чтения (записи) какого-либо слова придется ожидать того момента, когда в процессе вращения барабана требуемая ячейка подойдет к маг- нитной головке — затрачиваемое на это время называют временем ожидания (тож). Среднее время ожидания составляет половину оборота барабана, так что при скорости вращения в 3000 об/мин (эта цифра близка к реальной) среднее тож=10 мсек. (Одна милли- секунда=10~* сек). После того как нужная ячейка подошла к го- ловке, можно производить непосредственное чтение (запись). Для этого все участки, образующие ячейку, должны пройти под маг- нитной головкой, на что затрачивается дополнительное время — время чтения (т„). Таким образом, время обращения выражается формулой тобр=тож+тЧ1. Сокращение среднего времени ожидания может быть достигнуто' как за счет повышения скорости вращения барабана, что приво- дит к значительным трудностям технического характера, так и. за счет специального размещения машинных слов (или групп слов)
S.i] ПРИЧИНЫ ВОЗНИКНОВЕНИЯ 505' по дорожке о учетом последовательности Их использования в дан- ной программе — эта проблема оптимального размещения инфор- мации на барабане является также достаточна сложной. Что касается уменьшения т,т, то эта. задача может быть решена достаточно просто за счет соответствующего объединения отдельных участков в запоминающую ячейку. С этой точки зрения целесооб- разнее всего было бы объединять в ячейку запоминающие участки, находящиеся на разных дорожках, но расположенные по одной образующей цилиндра: в этом случае все разряды слова можно было бы записывать (читать) одновременно с помощью разных головок, так что время чтения одного слова практически равно нулю. Правда, в этом случае число магнитных головок должно быть по крайней мере равно числу разрядов в слове (или кратно ему). На практике чаще всего используются промежуточные спо- собы группирования участков в ячейку памяти. При любом из этих способов т„ значительно меньше среднего т0Ж) поэтому тобр«тож. Барабан может иметь достаточно большую емкость (от 4 тыс. до 1 млн. слов) при относительно невысокой (по сравнению с оперативной памятью) стоимости. При этом наиболее дорогой частью МБ являются магнитные головки, причем стоимость головки пропорциональна числу участков, которые она способна выделять на дорожке, а общая емкость барабана определяется как емкостью каждой дорожки, так и их числом (т. е. числом головок). Магнитный диск — это по сути дела магнитный барабан с малой высотой, у которого рабочей является не боковая, а торцевая по- верхность. Дорожки также образуют участки, расположенные по. некоторой окружности на рабочей поверхности, причем число таких дорожек на диске может быть весьма большим. Снабжать каждую дорожку своей головкой было бы невыгод- но — обычно диск имеет только одну головку, которая может пере- мещаться вдоль радиуса диска и тем самым может быть подведена к любой из дорожек — именно поэтому диск является более эко- номичным устройством, чем барабан. Естественно, что здесь в. запоминающую ячейку объединяются последовательные участки одной и той же дорожки. Диск является более медленным устрой- ством, чем барабан, поскольку здесь время обращения тобр=тподв-|- +Тож+Тчт> где тподв — время подвода головки к нужной дорожке (которое зависит от исходного положения головки), тож — время ожидания того момента, когда требуемая ячейка в процессе вра- щения диска подойдет к головке, и тЧ1 — время чтения. Что ка- сается тож и тчт, то эти времена примерно такие же, что и в случае МБ (при одинаковых скоростях вращения барабанов и дисков и при размещении разрядов ячейки на МБ на одной дорожке). Время подвода существенно зависит от того, насколько была удалена головка от нужной дорожки — среднее время подвода значительно превышает тож и т„.
506 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ 1Гл. 9 Как правило, устройство содержит не один диск, а пакет из 5—10 дисков, насаженных на общую ось (дисковод). Все головки крепятся на общую штангу, с помощью которой производится одновременный подвод всех головок к дорожкам с одним и тем же номером, находящимся на разных дисках (совокупность таких дорожек образует «рабочий цилиндр», ибо обращение ко всем этим дорожкам можно производить одновременно). Основное достоинство магнитных дисков — их большая емкость при небольшом числе магнитных головок: пакет дисков может иметь емкость в сотни миллионов слов. Магнитная лента, используемая для реализации внешней памяти ЭВМ, работает по тому же принципу, что и магнитная лента обычного магнитофона. Здесь информация запоминается на магнит- ном слое, нанесенном на длинную узкую ленту, на которой может содержаться всего одна дорожка и поэтому можно обойтись одной магнитной головкой (на самом деле МЛ обычно имеет 4—10 дорожек). Лента — в отличие от барабанов и дисков — обычно находится в состоянии покоя и приводится в движение только при необходи- мости произвести обмен. Время обращения здесь определяется главным образом временем подвода нужного участка ленты к маг- нитным головкам. На прогон всей ленты (длина которой обычно составляет сотни метров) затрачиваются минуты, так что быстро- действие МЛ значительно ниже, чем барабанов и дисков. Для размещения данных на ленте выделяются отдельные участ- ки, называемые зонами или блоками. На некоторых типах этих запоминающих устройств каждая зона снабжается своим номером, записанным на ленте в начале зоны, и устройство обеспечивает подвод зоны с заданным номером; на других типах этих устройств зоны номеров не имеют, а устройство обеспечивает только-продви- жение ленты на одну зону в прямом или обратном направлении. Магнитные ленты являются наиболее дешевыми, но и наиболее медленными видами внешней памяти. Емкость одной ленты состав- ляет несколько миллионов слов. Магнитные ленты и диски являются сменными устрой- ствами: на один и тот же магнитофон (дисковод) можно устанавли- вать для работы разные ленты (диски), причем в случае МД обычно меняются не отдельные диски, а весь их пакет. Это обстоятельство позволяет использовать диски и ленты для хранения данных и вне машины, а также о их помощью передавать данные с одной машины на другую. Обычный режим использования внешней памяти заключается в том, что большие массивы данных в процессе решения задач по- стоянно хранятся во внешней памяти, а для их обработки эти мас- сивы или отдельные их порции считываются в оперативную память; обработанная порция в случае необходимости снова может быть записана во внешнюю память.
9.11 ПРИЧИНЫ ВОЗНИКНОВЕНИЯ 507 Для указания начала нужной порции данных во внешней' па- мяти также используется понятие «адреса», однако этот адрес Лвп имеет своеобразный вид. Как правило, он состоит из двух частей: номера устройства внешней памяти N.^, и адреса в этом устрой- стве Лву, причем адрес в устройстве может иметь различную струк- туру для разных видов устройств, например «номер секции + номер в секции» для МБ, «номер цилиндра + номер диска + номер сек- ции + номер в секции» для МД, «номер зоны» для МЛ. В наборе машинных операций предусматриваются специальные операции обмена данными между оперативной и внешней памятью. В соответствующих им командах задается информация о виде об- мена (чтение или запись), адреса начала обмениваемой порции данных в оперативной и внешней памяти, а также число машинных слов, образующих эту порцию данных (если обмен допускается Рис, 9,1, только порциями опеределенной длины, то эта информация в ко- манде не задается). Кроме того, в команде могут задаваться раз- личные режимы обмена (например, с контролем или без контроля правильности обмена). Поскольку для операций обмена приходится задавать сравнительно большой объем информации, то соответ- ствующие им команды обычно имеют специальный формат. Таким образом, внешняя память (независимо от вида устрой- ства, на котором она реализована) имеет следующие характерные особенности: — отсутствие непосредственного доступа из процессора к хра- нящимся в ней данным; — большое время обращения, главным образом из-за времени ожидания и (или) подвода; — групповой обмен данными; — использование адресов, определяющие место хранения каж- дой группы данных во внешней памяти, причем эти адреса в разных видах внешней памяти образуются по-разному. С учетом внешней памяти структуру ЭВМ можно изобразить в виде схемы, изображенной на рис. 9.1.
508 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ [Г, 9 В отличие от схемы, приведенной в гл. 4, здесь содержатся стрелки, показывающие передачу управляющих сигналов в про* цессор от всех других устройств — более конкретно об этих сиг* налах будет сказано несколько позже. 9.1.3. Внешняя память и языки программирования. Включение внешней памяти в состав ЭВМ диктуется не прин- ципиальными, а практическими соображениями—стремлением сде- лать ЭВМ пригодной для решения возможно более широкого класса задач. Машинные программы, рассчитанные на обработку больших объемов данных, обычно содержат в себе операции обмена между оперативной и внешней памятью. Что касается языков программирования высокого уровня, то их можно разделить на две группы. К одной из них можно отнести языки (назовем их «реалистиче- скими», например алгамс, ПЛ/1, некоторые версии фортрана), кото- рые учитывают такую особенность реальных ЭВМ, как наличие в их составе внешней памяти и которые явно включают в себя операто- ры, предназначенные для работы с нею. Эти операторы имеют вид читать (41, 42, п) писать (41, 42, п) где параметры 41 и 42 задают начало обмениваемой порции данных в оперативной и внешней памяти, ап — объем этой порции. В этих языках предусматривается описание массивов, размещаемых во внешней памяти, например exarray X [1:50000]. К другой группе можно отнести языки, которые не учитывают указанной особенности реальных ЭВМ («нереалистические» языки); а предполагают, что вся память ЭВМ однородная (т. е. оперативная). К таким языкам относится, например, алгол-60. Если для реали- зации алгоритма, записанного на таком языке, емкость оперативной памяти оказывается недостаточной, то при переводе этого алго- ритма на язык машины возникают серьезные трудности. В неко- торых трансляторах предусматривается решение проблемы раз- мещения данных и организации обмена данными в реальной памяти, но для эффективного решения этой проблемы нужна дополнитель- ная, содержательная информация (например, частота использо- вания различных массивов), которой транслятор не располагает, н поэтому он может получить весьма неэффективную объектную программу. По этой причине во многих трансляторах решение этой проблемы не предусматривается, и если емкость оперативной па- мяти оказывается недостаточной, то они просто прекращают транс- ляцию. ДЛя иллюстрации специфики работы с внешней памятью рас- смотрим следующую задачу. Пусть имеются вещественные векторы 4, В [1:60000], и требуется выполнить действие А+В^А.
9.1) ПРИЧИНЫ ВОЗНИКНОВЕНИЯ БОЭ. На алголе программа решения этой задачи имела бы вид (дей- ствия, связанные о определением значений векторов А и В, а также с использованием измененного вектора А, условно записаны в виде операторов ввода и вывода): begin array А, В [1:600001; integer г, ввод (Л, В); for t:=l step 1 until 60000 do Л[<]:=Л[114-В[11; вывод (Л) end Запись алгоритма получилась компактной и естественной, однако подавляющее число трансляторов просто не стали бы транслировать такую программу из-за нехватки (оперативной) памяти. При использовании «реалистического» языка программирова- ния пользователь сам может решить проблему размещения масси- вов в памяти с учетом ее неоднородности, а для обработки масси- вов, размещенных во внешней памяти, он должен предусмотреть считывание нужных порций этих массивов в оперативную память, введя для этих целей в употребление вспомогательные «буферные» массивы, размещенные в оперативной памяти. На такого рода языке программа решения рассматриваемой задачи могла бы иметь вид: begin exarray Л, В [1:600001; array D, Е [1:600]; integer i, j; ввод (Л, fi); for к = 1 step 1 until 100 do begin читать (Л1600Х (i—1)4-11, Dili, 600); читать (B[600x (i—1)4-11, fill, 600); for /:=1 step 1 until 600 do D[j]:=D[/14-E[/]; писать (Л1600Х (t—1)4-11, Dll 1, 600) end; вывод (Л) end При таком «явном» программировании работы с внешней па- мятью возникают следующие неудобства: — иногда (как, например, в нашем случае) требуется перекон- струировать алгоритм, сформулированный в предположении од- нородности памяти ЭВМ; — при описании действий по переработке массивов в алгоритме приходится использовать идентификаторы буферных массивов, из-за чего теряется наглядность и простота понимания программы, тем более что с целью экономии оперативной памяти одни и те же буферные массивы могут использоваться для обработки разных массивов; — если буферный массив используется для размещения частей основного массива, расположенного во внешней памяти, то при формулировании алгоритма его переработки приходится исполь- зовать другие индексы, причем пересчет индексов в случае много- мерных основных массивов вовсе не тривиален. Основное преимущество использования реалистических языков программирования состоит в том, что без использования внешней памяти многие задачи было бы вообще невозможно решить на данной ЭВМ.
ею МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ (Гл. 9 9.1.4. Эффективность использования оборудования. Характерной особенностью всех видов внешней памяти явля- ется их низкое быстродействие по сравнению с процессором и опе- ративной памятью. Эту же особенность имеют и устройства ввода/ вывода (устройства ввода с перфокарт, печатающие устройства и т. д.), поскольку при их работе также используется механическое движение. Все такие «медленные» устройства будем называть внеш- ними устройствами (ВУ), а под термином «обмен» будем понимать обращение к любому из этих устройств. Тогда можно сказать, что решение практически любой задачи связано с обменом — хотя бы потому, что в любой задаче имеет место по крайней мере ввод на- чальных данных и вывод получаемых результатов. Временная диаграмма процесса решения задачи на ЭВМ в общем случае имеет вид: обмен счет обмен счет обмен счет ----------------------------------------------- Если команды программы выполняются машиной строго по- следовательно (что имело место на первых ЭВМ), то процессор, начав'выполнение команды обмена, не может перейти к выполне- нию следующей команды программы, пока этот обмен не будет закончен, и тем самым вынужден простаивать. А если учесть ре- альное соотношение быстродействия процессора и внешних уст- ройств, то оказывается, что при решении многих задач процессор лишь время от времени включается в работу на короткий проме- жуток времени. С другой стороны, каждое из внешних устройств бездействует до тех пор, пока в процессе выполнения программы не встретится команда, по которой производится обмен с данным устройством. А если учесть и то обстоятельство, что во многих задачах используется лишь часть из имеющихся внешних устройств, то возникает парадоксальная ситуация: при работе ЭВМ устрой- ства, входящие в ее состав (в том числе и процессор), в основном простаивают! А это значит, что реальная производительность ЭВМ сказывается чрезвычайно низкой по сравнению с ее потенциальными возможностями и что быстродействие ЭВМ не так уж существенно влияет на ее производительность, как это может показаться на лервый взгляд. Проблема эффективного использования оборудования ЭВМ возникла и по причинам организационного характера. На первых порах ЭВМ использовались в «открытом» режиме, при котором программист имел непосредственный доступ к машине и мог вести отладку своей программы (и даже счет по отлаженной программе), работая за пультом управления ЭВМ. Пульт управления, как лравило, предоставляет весьма широкие возможности управления работой ЭВМ, которые, в частности, очень удобны для отладки
9.2] СОВМЕЩЕНИЕ РАБОТЫ УСТРОЙСТВ 5П программ. Так, на пульте управления в виде световой сигнализации постоянно фиксируется текущее содержимое всех основных регист- ров машины, так что в любой момент времени, остановив машину, можно прочесть содержимое каждого из них. Однако ясно, что открытый режим весьма неэффективен с точки зрения использова- ния оборудования: при каждом останове машины программист тратит слишком много времени на расшифровку и анализ инфор- мации, выведенной на пульт управления, на принятие решения и выполнение ручных действий на пульте управления, в течение которого все оборудование простаивает. По этой причине довольно' быстро перешли к «закрытому» режиму, при котором программист к машине уже не допускается. Непосредственный же пропуск про- грамм через машину стал осуществляться специальным лицом — оператором ЭВМ. Эта мера существенно сократила потери машин- ного времени из-за программистов, однако полностью устранить подобного рода потери не удалось — они возникали по «вине* оператора, поскольку ему приходилось выполнять довольно много ручных действий по смене программ, пропускаемых через машину, по установке требуемых лент на магнитофоны и других действий, указанных в инструкциях к программам. Ускорившийся темп прохождения задач через машину приводил также к ошибкам в действиях оператора, что часто влекло за собой потери затрачен- ного машинного времени, а это опять же эквивалентно простоям: оборудования. Проблема эффективного использования оборудования станови- лась все более острой по мере повышения быстродействия ЭВМ и увеличения числа внешних устройств, включаемых в ее состав. 9.2. Совмещение работы устройств ЭВМ. Мультипрограммный режим Первое, что сделали инженеры для сокращения простоев про- цессора из-за низкого быстродействия внешних устройств,— снаб- дили каждое из этих устройств своим, «местным» устройством уп- равления, в результате чего эти устройства стали работать авто- номно от процессора, по принципу выполнения поступающих от- него заказов. В этом случае для выполнения команды обмена про- цессору достаточно включить в работу соответствующее внешнее устройство, передав его местному устройству управления необхо- димую информацию, заданную в команде; фактическое же выпол- нение обмена обеспечивается местным устройством управления в соответствии с поступившим заказом, а тем временем процессор переходит к выполнению следующих команд программы. Таким путем была обеспечена возможность параллельной работы .раз- личных устройств и тем самым сокращения их простоев. Для со- гласования работы отдельных устройств процессору теперь не-
512 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ ГГл. 9 обходима „информация о состоянии внешних устройств- В связи с этим каждое внешнее устройство по окончании выполнения оче- редного заказа посылает управляющий сигнал процессору, свиде- тельствующий о том, что данное устройство освободилось и может принять к исполнению следующий заказ на обмен. В этом и за- ключается смысл соответствующих стрелок, показанных на по- следней схеме структуры ЭВМ (см. рис. 9.1). Эта мера позволила в ряде случаев заметно увеличить загружен- ность отдельных устройств ЭВМ и сократить их простои, однако она оказалась явно недостаточной для более или менее удовлетво- рительного решения этой проблемы — хотя бы потому, что во многих задачах, в силу их специфики, совмещение работы отдель- ных устройств оказывается невозможным. Так, в рассмотренном выше примере добавления к вектору А вектора В нельзя начать счет (сложение очередных порций компонент этих векторов), пока не закончено считывание необходимых порций компонент, и нельзя начать это считывание, пока не закончены получение и запись предыдущей порции компонент вектора А. Кроме того, в одной N tr tg *з Г» tj tg t? tg tgtjg tn ti2 trs t» its tn T t Рис. 9.2. задаче обычно используется лишь часть внешних устройств, име- ющихся в составе ЭВМ, и поэтому остальные устройства неизбежно простаивают. Но если при решении на ЭВМ только одной задачи практически невозможно обеспечить достаточно полную нагрузку даже тех устройств, которые используются в данной задаче, то естественный выход из положения — решать на машине одновременно несколько задач. Суть этой идеи состоит в том, что если в процессе решения некоторой задачи какое-либо устройство оказы- вается хотя бы временно незагруженным, то для устранения его простоя можно попытаться на этот период использовать данное устройство, для другой задачи. Такой режим работы ЭВМ, при кагором машина выполняет одновременно несколько программ, и получил название мультипрограммного (т. е. многопрограммного) режима. Рассмотрим на схематическом примере, что может дать такой режим. Пусть на машине одновременно решаются три задачи: № 1, №2 и № 3. Допустим, что управление прохождением задач производится по их срочности: в первую очередь должно обеспе-
9.2) СОВМЕЩЕНИЕ РАБОТЫ УСТРОЙСТВ 513 чиваться прохождение самой срочной задачи (пусть таковой, яв- ляется, задача № 1); менее срочная задача (№ 2) должна решаться в том случае, если прохождение задачи X® I по какой-либо, причине (например, из-за обмена) задерживается, а наименее срочная задача (Ms 3) должна решаться только в том случае, если невозможно продолжить решение ни задачи Xs 1, ни задачи № 2. Пусть задача Xs 1 начинается с небольшой фазы счета, за кото- рой, чередуясь между собой, следуют фазы обмена и счета (см., рис. 9.2, на котором сплошными отрезками изображены фазы счета, а пунктирными — фазы обмена). В момент задача Xs 1 .перешла в фазу обмена, а поскольку на. время этой фазы процессор не используется, то с момента Л можно начать .решение задачи ,Х° 2, которая до этого находилась в фазе ожидания. В момент tt задача Xs 2 также перешла в фазу обмена, а поскольку обмен в .задаче. Xs 1 еще не закончился, то процессор может переключиться на решение задачи Xs3, которая до этого находилась в фазе ожидания. В момент t3 закончился обмен в задаче Xs 2, и поскольку она является более срочной, чем задача №3, процессор переключается на решение задачи Xs 2, переводя задачу Xs 3 в фазу ожидания, хотя фаза счета в этой задаче не была за- вершена. По аналогичной причине в момент tt процессор пере- ключается на задачу Xs 1 как более Срочную, переводя задачу №2 также в фазу ожидания. В момент tt процессор в задаче Хе 1 ос- вобождается — он переключается на задачу Xs 2, завершая в мо- мент t„ незаконченную ее фазу счета и т. д. Если учесть отрезок времени от 0 до Т, то из приведенной диа- граммы видно, что при одновременном решении трех задач процессор оказывается незагруженным (простаивает) только в интервалах времени (/,, /8) и (/17, Т), тогда как при решении только одной задачи Xs 1 он простаивал бы в интервалах (/ъ /4), (tt, /8), (/ц, tu) и (tw Т), т. е. гораздо больше. Кроме того, теперь наряду с внешними уст- ройствами, используемыми в задаче Xs 1, оказываются загружен- ными (и работающими параллельно с ними) и другие внешние уст- ройства, используемые в задачах Х$ 2 и Xs 3. Следует отметить, что решение задач Xs 2 и ХэЗ практически не увеличивает общего времени, затрачиваемого машиной на ре- шение задачи Xs 1 — полезная работа по решению задач Xs 2 и Xs3, выполненная на фоне решения задачи Хе 1, является чистым выигрышем за счет использования мультипрограммного режима работы машины. Таким образом, за счет мультипрограммного режима можно существенно повысить производительность ЭВМ при том же самом ее быстродействии. При этом ясно, что факти- ческое повышение производительности ЭВМ будет существенно зависеть от характеристик диаграмм одновременно решаемых задач: при неудачном подборе этих задач эффект использования мультипрограммного режима может оказаться и незначительным. >17 э. 3. Любимскнй и др.
614 - МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ (Гл 9 При мультипрограммном режиме каждая из решаемых задач может находиться в одной из трех фаз: счета, обмена и ожидания. Заметим, что наличие фаз ожидания (которые возникают в заранее непредсказуемые моменты времени), естественно, никак не должно сказываться ни на самой программе, ни на ее составлении. В связи с мультипрограммным режимом работы ЭВМ возникает новая проблема — управление потоком одновременно реша- емых задач. Очевидно, что это управление более сложное, чем в однопрограммном режиме. Эго управление, в частности, должно уметь: а) в заранее непредсказуемые моменты времени прерывать (при- останавливать) выполнение одной программы и переходить к вы- полнению другой; б) в любой момент времени продолжать выполнение прерванной ранее программы; в) динамически распределять ресурсы машины (память, внеш- ние устройства и т. д.) между отдельными программами; г) предотвращать непредусмотренное воздействие одновременно выполняемых программ друг на друга, и т. д. Реализовать столь сложное управление аппаратно практически невозможно, и к тому же такая реализация могла бы оказаться и невыгодной. Например, в рассмотренной нами схеме одновре- менного решения нескольких задач мы предполагали, что управ- ление должно обеспечивать немедленное продолжение счета более срочной задачи, если только этот счет оказывается возможным, в результате чего иногда приходится прерывать фазу счета какой- либо менее срочной задачи. А поскольку каждое прерывание тре- бует выполнения некоторых вспомогательных действий, то такое управление приводит к излишним потерям машинного времени. Более экономным было бы такое управление, при котором каждая задача прерывалась бы только по завершении очередной ее фазы' счета (если специфика решаемых задач позволяет использовать такое управление). На самом деле в различных условиях исполь- зования ЭВМ могут потребоваться различные дисциплины управ- ления, поэтому жесткое, аппаратное управление было бы даже нежелательным. Однако в аппаратной реализации такого управления нет особой необходимости: ведь сама ЭВМ универсальна, и поэтому для нее можно составить специальную программу, реализующую этот сложный алгоритм управления потоком одновременно решаемых задач в любой желаемой его модификации, оставив за аппаратурой лишь обычные функции. Эта специальная программа будет яв- ляться как бы «программным расширением! устройства управления машины. Предположим, что такая специальная программа составлена — назовем ее диспетчером и-добавим к потоку решаемых задач. Про-
9.2] СОВМЕЩЕНИЕ' РАБОТЫ УСТРОЙСТВ , 5]5 дессор должен включать эту программу в работу в момент появ- ления каждого «события», которое требует вмешательства со сто- роны управления потоком решаемых задач — с тем чтобы диспет- чер «разобрался» в сложившейся ситуации и принял соответствуют щее решение. Таким образом, диспетчер — в отличие от обычных программ — всегда должен присутствовать в потоке решаемых задач. Говоря о потоке задач, решаемых одновременно в мультипро- граммном режиме работы ЭВМ, необходимо подчеркнуть,, что факт одновременного выполнения машиной нескольких программ никак не должен сказываться на их составлении, иначе при подготовке программ возникли бы чрезвычайно большие трудности. Каждая программа должна составляться, как и раньше, исходя из .того, что машина будет выполнять только одну эту программу. Если же на самом деле параллельно с данной программой на машине будут выполняться и какие-то другие программы, то управление' должно. гарантировать правильность ее выполнения даже при наличии любого рода ошибок в других программах. Другими сло- вами, диспетчер должен обеспечить выделение для каждой из вы- полняемых программ как бы своей собственной виртуальной (воображаемой, мыслимой) машины с теми же логическими свой- ствами, что и реальная машина, и каждую из этих виртуальных машин так отобразить на реальную машину, чтобы они не мешали друг другу. Если на однопрограммной машине все ее ресурсы (машинное время, оперативная и вспомогательная память, устройства ввода/ вывода) отдаются в распоряжение (единственной) решаемой задачи, то теперь эти ресурсы приходится делить между несколькими од- новременно решаемыми задачами. Для эффективного использо- вания имеющихся ресурсов и разумного распределения их между задачами, естественно, необходимо учитывать потребности каждой задачи в этих ресурсах. Поэтому при мультипрограммном режиме работы ЭВМ каждая задача должна быть снабжена описательной частью, называемой паспортом задачи — содержащаяся в нем информация используется диспетчером при его работе, и в част- ности, при распределении ресурсов между задачами. Паспорт задачи обычно содержит в себе следующую инфор- мацию: — имя задания, которое, в частности, используется для иден- тификации выдач на печать, относящихся к этому заданию; — имя (или шифр) пользователя (отдельного лица или подраз- деления), которое используется в качестве пропуска на ЭВМ, для ведения учета расходуемых ресурсов машины и т. д.; — запрос на требуемые ресурсы; — приоритет, определяющий срочность задачи, и т. д. Каждый конкретный диспетчер устанавливает как состав информации, задаваемой в паспорте задачи, так и правила ее задания. 17*
516 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ 1Гл 9 Помимо управления потоком одновременно выполняемых про- грамм на диспетчер возлагается еще одна важная функция — управление внешними устройствами. Поскольку при составлении любой программы неизвестно, с какими другими программами она в дальнейшем будет выполняться на машине, то в программе нельзя писать обращения к каким-либо конкретным внешним устройст- вам — иначе может оказаться, что одно и то же устройство будет использоваться сразу в нескольких программах, одновременно выполняемых машиной, и тем самым будет нарушено правильное выполнение каждой йз них. Поэтому даже такое управление внеш- ними устройствами, как их распределение между задачами, должно производиться динамически, в зависимости от сложившейся ситуа- ции, что может делать только диспетчер. Кроме того, и конкретное управление работой некоторых устройств — в силу их специфики — также должно осуществляться с учетом текущей ситуации, что также может делать только диспетчер. Таким образом, диспетчер выполняет две основные функции: а) управление одновременным выполнением нескольких задач; б) управление внешними устройствами машины. 9.3. Требования к аппаратуре Чтобы диспетчер мог выполнять свои управляющие функции, в аппаратуру ЭВМ необходимо было внести следующие дополни- тельные возможности по сравнению с однопрограммными машинами. 9.3.1. Прерывания. Эта возможность аппаратуры состоит в том, что при появлении какого-либо события, требующего внимания со стороны диспет- чера, происходит прерывание (приостановка) выполнения программы и передача управления диспетчеру. При этом речь идет не о полном прекращении выполнения программы, а лишь о временной приостановке ее выполнения. Поэтому пре- рывание должно быть сделано так, чтобы в подходящий момент времени можно было продолжить выполнение прерванной про- граммы так, как если бы с точки зрения программы никакого пре- рывания и не было. Диспетчер, получив управление в результате прерывания, должен «обслужить» это прерывание, т. е. «выяснить» вызвавшую его причину и принять соответствующее решение. Если, например, причина прерывания заключалась в том, что выполняемая программа просто обратилась к диспетчеру с заказом на вычисление одной из стандартных функций (о возможности такого рода прерываний будет сказано ниже), то диспетчер выполняет этот заказ и возобновляет выполнение прерванной программы. Если же причиной прерывания было завершение обмена с магнитным барабаном в другой, более
S.3] ТРЕБОВАНИЯ К АППАРАТУРЕ 517 срочной задаче, то реакция диспетчера может заключаться в пере- ключении процессора на продолжение решения этой более срочной задачи, прерванной в свое время по причине начала обмена^ Обычно прерываниями управляет регистр прерываний. Число разрядов в этом регистре равно- числу возможных причин преры- ваний, т. е. событий, требующих внимания диспетчера («магнитный барабан № 1 закончил обмен»; <на перфораторе № 1 кончились карты»; «на читающем устройстве № 2 замяло карту» и т. д.). Число таких причин прерываний может быть достаточно большим. Если на каком-либо устройстве произошло такого рода событие, то в определенном разряде регистра прерываний аппаратно устанав- ливается цифра 1, являющаяся сигналом о том, что возникла при- чина прерывания, причем из-за автономности работы отдельных устройств ЭВМ единицы могут появиться одновременно в нескольких разрядах регистра прерываний. В обычном режиме работы машины появление хотя бы одной единицы в регистре прерываний автоматически влечет за собой (по завершении выполнения очередной команды) прерывание выполня- емой программы, под которым понимается описанная ниже по- следовательность действий, выполняемых аппаратным путем. 1°. Запоминание текущего состояния процессора. Для того чтобы в дальнейшем можно было продолжить выполнение прерванной программы, нужно обеспечить, чтобы к моменту возобновления ее выполнения процессор имел в точности такое же состояние (т. е. чтобы во всех его регистрах находилось то же самое содержимое), что и в момент прерывания этой программы. Однако при каждом прерывании в работу включается диспет- чер, а диспетчер — эт'о тоже программа, которая выполняется процессором с использованием того же оборудования, что и любая другая программа. Так что в результате работы диспетчера содер- жимое некоторых регистров процессора может быть изменено, а содержимое некоторых из них — например счетчика команд — наверняка будет изменено. Кроме того, в результате обслуживания прерывания диспетчер может принять решение о продолжении выполнения другой, прерванной ранее программы. Поэтому в мо- мент прерывания производится запоминание текущего состояния процессора в памяти машины. Вообще говоря, запоминание содержимого некоторых регист- ров процессора (например, индексных регистров) мог бы сделать диспетчер программным путем, т. е. путем выполнения соответст- вующих команд (с операциями типа РА или PC в УМИР-3). В не- которых же случаях программное запоминание в принципе невоз- можно — например содержимое счетчика команд будет заведомо изменено уже к началу работы диспетчера, поскольку к этому моменту в счетчике команд должен уже находиться адрес первой команды диспетчера. Поэтому ясно, что запоминание счетчика ко*
518 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ [Гл. 9 манд (и некоторых других регистров) можно сделать только аппа- ратно. На самом деле обычно выбирается некоторый компромисс — ' запоминание одной части состояния процессора возлагается на аппаратуру, а запоминание другой его части — на диспетчер. Од- нако ради простоты изложения мы будем считать, что все запоми- нание делает аппаратура. Аппаратное запоминание состояния процессора производится всегда в одной и той же фиксированной ячейке (или группе ячеек), содержимое которой называют словом состояния процессора (ОСП). Эго ССП в дальнейшем используется для восстановления состоя- ния процессора при возобновлении выполнения прерванной про- граммы. Чтобы ССП данной программы, полученное при ее прерывании в фиксированной ячейке, не было утеряно в результате прерывания других программ (которые, возможно, по решению диспетчера бу- дут продолжать выполняться процессором после обслуживания данного прерывания), диспетчер должен позаботиться о его сохра- нении. В связи с этим диспетчер на своем рабочем поле для каждой из одновременно решаемых задач отводит группу ячеек памяти, называемую информационным полем задачи — в этом поле хранится вся. необходимая для диспетчера информация о данной задаче, - в том числе и ее ССП. После того как в результате прерывания диспетчер получает управление, он переносит в информационное поле задачи ее ССП, записанное аппаратурой в фиксированной ячейке. Благодаря этому диспетчер может в любой момент времени продолжить выполнение каждой из прерванных ранее задач. 2°. Установление специального режима работы машины — ре- жима диспетчера. Установление такого режима означает, что с этого момента выполняется диспетчер, а не обычная программа. В частности, в этом режиме разрешается выполнение привилеги- рованных команд, о которых будет идти речь в 9.3.2. 3°. Блокировка прерываний. Если бы наличие единицы в каком- либо разряде регистра прерываний всегда влекло за собой преры- вание, то это сильно затруднило бы диспетчеру выполнение своих функций (или вообще сделало его невозможным), поскольку по- явление каждой новой единицы означало бы снова переход на на- чало диспетчера, что отвлекало бы его от начатого обслуживания какого-либо прерывания. Поэтому в момент прерывания и устанав- ливается особый режим работы машины, при котором блокируются (запрещаются) всякие прерывания, несмотря на появление единиц в регистре прерываний, свидетельствующих о наступлении собы- тий, также требующих внимания диспетчера. В случае необхо- димости диспетчер путем выполнения соответствующих команд может снова разрешить прерывания по той или иной совокупности причин, если он сочтет это целесообразным.
в.З] ТРЕБОВАНИЯ К АППАРАТУРЕ 519 4°. Включение в работу диспетчера. Для этого в счетчик команд заносится фиксированный адрес, адрес первой команды диспетчера. Вообще говоря, различные события, вызывающие прерывания, могут требовать различной скорости реакции диспетчера. Так,* замедленная реакция на окончание обмена с каким-либо устройством внешней памяти не может привести к серьезным последствиям —- в. худшем случае по этой причине переключение на ту задачу, в которой производился этот обмен (возможно, более срочную), про- изойдет несколько позднее. А если,, например, причина прерывания заключается в том, что в процессе чтения с перфоленты под считы- вающие элементы подошла очередная ее позиция, то недостаточно быстрое обслуживание такого прерывания может привести к тому, что эта позиция пройдет считывающие элементы без чтения и тем самым содержащаяся в ней информация будет утеряна. Поскольку реально используемая аппаратура может предьяв-' лять довольно жесткие требования к быстроте реакции на некото- рые события, то часто причины прерываний упорядочиваются в> регистре прерываний по срочности их обслуживания, что позволяет диспетчеру быстрее находить ту причину, которую он должен обслужить в первую очередь и тем самым ускорить свою реакцию' на соответствующее прерывание. Задача ускорения реакции На прерывания частично может решаться и аппаратным путем. Так, выше мы считали, что при любом прерывании переход осуществ- ляется всегда на начало диспетчера, который должен сам выяснить причину прерывания и в зависимости от этого осуществить переход на ту свою часть, которая фактически и обслуживает данную при- чину прерывания. Между тем за счет усложнения аппаратуры можно обеспечить, чтобы при прерывании переход осуществлялся сразу на ту часть диспетчера, которая обслуживает самую прочную из появившихся причин прерываний. Для этого с каждым разрядом регистра прерываний надо связать определенный адрес, по кото- рому аппаратура и осуществляет передачу управления. Естественно, что диспетчер в процессе своей работы должен погасить в регистре прерываний ту единицу, которая вызвала данное прерывание — с тем чтобы она не вызвала повторного прерывания по уже обслуженной причине. До сих пор мы говорили о внешних прерываниях, т. е. прерыва- ниях, связанных с работой внешних устройств. Однако бывают еще и внутренние прерывания, причины которых появляются внутри процессора: попытка деления на нуль, выполнение команды останова (напомним, что теперь по этой команде надо останавливать только соответствующую виртуальную, а не реальную машину) и т. д. На каждое такое событие также отводится свой разряд в регистре прерываний. Итак, регистр прерываний воспринимает и фиксирует ситуацию, складывающуюся на отдельных (довольно многочисленных) усд-
520 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ (Гл. 9 ройствах и даже отдельных их частях, а механизм прерываний обеспечивает своевременное _ включение в работу диспетчера, ко- торый и реагирует соответствующим образом на динамически возни- кающие ситуации. 9.3.2.. Привилегированный режим, „Диспетчер, будучи программным расширением устрой- ства управления, бсуществляет свои управляющие функции только путем выполнения входящих в него команд процессором — в этом отйошении диспетчер ничем не отличается от любой другой про- граммы. Но поскольку функции диспетчера весьма специфичны^ то в наборе ^машинных операций предусматриваются специальные операции, предназначенные исключительно для целей управления потоком программ. К ним, в частности, относится операция изме- нения содержимого регистра прерываний: с использованием этой операции диспетчер может погасить ту единицу в регистре преры- ваний, появление которой вызвало его включение в работу. К этой же категории относятся и операции, предназначенные для непо- средственного управления работой внешних устройств (как уже отмечалось, при мультипрограммном режиме такое управление должно осуществляться диспетчером). Очевидно, что использование таких операций в обычных про- граммах могло бы привести к нарушению правильности функцио- нирования диспетчера. Поэтому определенная группа машинных операций является особой — их использование является при- вилегией диспетчера; в обычных же программах использование этих операций запрещается. А поскольку эти запрещенные (при- вилегированные) команды все же могут появиться в обычных программах (например, в результате ошибок при их подготовке), то предусматриваются два режима работы машины: обычный (или режим пользователя) и привилегированный (или режим диспетчера). В обычном режиме работы машины попытка использовать в программе какую-либо привилегированную операцию влечет за собой прерывание этой программы. В привилегированном режиме беспрепятственно выполняются любые машинные операции. Как уже отмечалось, привилегированный режим устанавливается ап- паратно, когда в результате прерывания осуществляется переход на начало диспетчера. Обычный режим устанавливается диспетчером программным йутем при переходе к выполнению обычной программы, для чего предусматривается операция «возврат из прерывания», которая, естественно, также относится к группе особых операций. Поскольку такой переход всегда связан с восстановлением состояния процес- сора, то операция установления обычного режима, как правило, совмещается с операцией восстановления процессора по соответ- ствующему ССП.
9.3] ТРЕБОВАНИЯ К АППАРАТУРЕ 52| Таким образом, набор операций виртуальной машины стал мень- ше, чем в реальной машине. В качестве компенсации — чтобы це отнимать возможностей, существенно необходимых В обычных программах (например, возможность оперировать с внешними уст- ройствами),— предусматривается возможность обращения из обыч- ных программ к диспетчеру за определенными услугами. Эти об- ращения производятся по специальным командам, называемым директивами (или экстракодами). Например, с помощью одной из директив можно выдать диспетчеру задание произвести нужный обмен с тем или иным внешним устройством. Для реализации этих возможностей часто используют уже имеющийся аппарат преры- ваний: для обращения к диспетчеру используется одна из «запре^ щенных» операций — ее использование в обычном режиме влечет за собой прерывание и тем самым обращение к диспетчеру. Об- служивание такого прерывания диспетчером и будет заключаться в выполнении им заказа, содержащегося в данной директиве, в соответствии с дополнительной информацией, заданной по опре- деленным правилам при обращении к диспетчеру. Таким образом, набор операций виртуальной машины—-это набор операций реальной машины за вычетом запрещенных операций плюс набор директив диспетчера. Учитывая то обстоятельство, что набор директив (в отличие от набора машинных операций) не фик- сирован и при желании может быть произвольно расширен, воз- можности виртуальной машины в этом отношении могут быть го- раздо шире возможностей реальной машины. 9.3.3. Защита памяти. При однопрограммном режиме работы ЭВМ в распоряжение каждой задачи отдаются все имеющиеся, в машине ресурсы, в том числе и вся оперативная память. При мультипрограммном режиме в памяти находятся сразу несколько программ, выполняемых машиной параллельно, среди которых всегда присутствует и дис- петчер. Это означает, что в физической оперативной памяти реаль- ной машины одновременно размещаются оперативные памяти не- скольких виртуальных машин. Но тогда возникает проблема «за- щиты памяти»: если какая-либо задача по решению диспетчера переводится в фазу счета, то необходима гарантия того, что данная задача будет использовать только выделенную ей часть физической памяти, ибо обращения к ячейкам памяти, не принадлежащим данной задаче (что может, например, явиться следствием ошибок, содержащихся в ее программе), могут привести к нарушению пра- вильности выполнения других программ, в том числе и самого дис- петчера. В принципе контроль за выходом из заданного участка памяти - в выполняемой программе можно было бы осуществлять программ- ным путем, с помощью диспетчера: после выполнения очередно^
5S2 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ (Гл. • команды в программе диспетчер мог бы анализировать (исполни- тельные) адреса следующей подлежащей выполнению команды и выявлять факт их выхода за установленные пределы. Однако такой способ контроля был бы весьма неэкономным, так как в этом случае диспетчер должен был бы включаться в работу после выполнения каждой команды программы, находящейся в фазе счета. По- этому задача защиты памяти возлагается на аппаратуру, которая должна обеспечивать прерывание выполняемой программы при любой попытке обратиться к ячейке памяти, не принадлежащей данной задаче. Как правило (но не всегда) прерывание по защите памяти связано с наличием ошибок в программе, и если диспетчер при 'обслуживании такого прерывания установит, что это дейст- вительно так, то он обычно прекращает выполнение данной про- граммы. Наиболее, часто используются два способа защиты памяти. При одном из них каждой задаче диспетчер выделяет сплошной участок физической памяти с диапазоном адресов от Ан до Лг. В составе машины предусматриваются два регистра защиты -г- НАЧАЛО и КОНЕЦ (шм . ДЛИНА). При переводе какой-либо задачи в фазу счета диспетчер заносит в первый из этих регистров адрес, Лк, а во второй — адрес Ак (или длину I участка памяти). В процессе выполнения программы аппаратура при каждом обра- щении к памяти проверяет, не выходит ли данный (исполнительный) адрес за диапазон адресов, зафиксированный в регистрах защиты, и в случае выхода возбуждает прерывание. Другой способ защиты основывается на страничной стоуктуре памяти, которая широко используется в современных ЭВМ. При страничной структуре вся физическая память машины делится на равные части, называемые страницами, каждой из которых присвоен постоянный порядковый номер (нумерация, как обычно, начинается с нуля). Емкость одной страницы на разных ЭВМ колеблется от 128 до 1024 слов, причем ячейки памяти в каж- дой странице нумеруются, также начиная с нуля. Тогда можно считать, что адрес любой ячейки состоит из двух частей: номера страницы и номера ячейки в этой странице. Например, можно считать, что оперативная память УМИР-3 состоит из восьми стра- ниц, перенумерованных от 0 до 7, а каждая страница содержит по 512 ячеек с восьмеричными номерами от ООО до 777. Поэтому можно, например, считать, что адрес 3120 ссылается на ячейку с номером 120 в странице с номером 3. При страничной организации памяти для каждой виртуальной машины выделяется всегда целое число страниц (вообще говоря, не обязательно с последовательными номерами). Для защиты па- мяти в емтэве ЭВМ предусматривается набор регистров защиты, числйИ» разрядность которых, а также характер их использования зависят от применяемого варианта защиты.
е.31 ТРЕБОВАНИЯ К АППАРАТУРЕ 523 В случае страничной организации памяти наиболее часто ис- пользуются два основных варианта ее защиты. Первый вариант — это защита по ключу (которая используется, в частности, в машинах серии ЕС ЭВМ). В этом варианте каждой странице физической памяти ставится в соответствие свой регистр защиты с тем же порядковым номером, что и номер этой физической страницы. Для целей защиты памяти каждой из одновременно решаемых на машине задач диспетчер присваивает свой порядковый номер. После того как диспетчер выделит для каждой из этих задач определенные страницы физической памяти, он в каждый регистр защиты заносит номер задачи, для которой выделена страница, соответствующая данному регистру (отсюда видно, что максималь- ное число одновременно решаемых задач определяется разрядностью регистров защиты). Номер задачи, зафиксированный в каком-либо регистре защиты, выполняет роль «ключа» доступа к соответству- ющей странице. В процессоре имеется еще один регистр, в котором всегда хра- нится номер задачи, находящейся в фазе счета. Содержимое этого регистра (называемое «ключом процессора») формируется диспет- чером при каждом переводе той или иной задачи в фазу счета. При обращении к какой-либо странице из выполняемой про- цессором программы аппаратура проверяет совпадение ключа процессора с ключом, который нахЬдится в регистре защиты, со- ответствующем данной странице, и при несовпадении этих ключей (что свидетельствует о том, что задача пытается обратиться к не- принадлежащей ей странице) возбуждает прерывание, т. е. заносит единицу в соответствующий разряд регистра прерываний. В этом варианте содержимое регистров защиты изменяется сравнительно редко, только при изменении распределения страниц между задачами. Диспетчеру обычно присваивается ключ (номер) 0, и если ключ процессора равен нулю, то контроль на совпадение ключей при обращениях к памяти не производится — это дает возможность диспетчеру беспрепятственно обращаться к любым ячейкам памяти. Второй вариант базируется на принципе разрешения или запре- та доступа к данной странице из выполняемой программы, без какой-либо ее идентификации. В простейшем случае здесь каждой физической странице ставится в соответствие одноразрядный ре- гистр защиты (с тем же порядковым номером, что и номер страницы). При переводе какой-либо задачи в фазу счета диспетчер заносит в те регистры защиты, которые соответствуют страницам, выде- ленным для данной задачи, например цифру 1, а во все остальные регистры — цифру 0. Таким образом, цифрой 1 как бы помечаются те страницы, обращение к которым из данной задачи разрешено, а цифрой 0 — страницы, обращение к которым запрещено. Если в процессе выполнения программы будет сделана попытка обратить-
524 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ (Гл. 9 ся к странице, помеченной цифрой 0, возбуждается прерыва- ние. В этом варианте содержимое всех регистров защиты формиру- ется диспетчером каждый раз, когда какая-либо задача переводится в фазу счета (информация о распределении страниц между задачами все время хранится на рабочем поле диспетчера). Такой принцип защиты памяти (с некоторой его модификацией, которая будет рас- смотрена в следующем разделе) используется, в частности, в машине БЭСМ-6. 9.3.4. Перемещение (релокация) программ. Это — свойство аппаратуры, позволяющее достаточно просто продолжить выполнение прерванной программы на другом месте в памяти. Вообще говоря, эта особенность не является обязательной, но она очень удобна для реализации мультипрограммного режима, и потому она предусматривается в большинстве машин — тем более что она может быть реализована с помощью тех же средств, что и защита памяти. Выше мы исходили из того, что для каждой из одновременно решаемых на машине задач закрепляется свой участок оперативной памяти, который не может быть использован в других задачах. Однако этот способ приводит к весьма неэффективному использо- ванию одного из наиболее дефицитных ресурсов машины — ее оперативной памяти. Действительно, в этом случае, во-первых, в каждой задаче приходится запрашивать максимально необходимое для ее решения число страниц памяти, которые закрепляются за этой задачей на все время ее решения, хотя некоторые из запрошенных страниц на самом деле нужны лишь на непродолжительный период времени, после чего эти страницы вполне можно было бы выделить для ка- кой-либо другой задачи. Очевидно, что эффективность использо- вания ресурсов (в том числе и оперативной памяти) была бы зна- чительно выше, если бы по ходу решения задачи ей можно было добавлять недостающие и отбирать неиспользующиеся ресурсы, т. е. при динамическом распределении ресурсов. В таком случае при включении какой-либо задачи в число решаемых задач ей можно было бы, например, сначала выделить только минимально- необходимые ресурсы, а затем дополнять их в процессе решения задачи только по мере необходимости. Во-вторых, в каждый момент времени в фазе счета находится только одна из решаемых задач. При этом память, выделенная для других задач, в это время либо вообще не используется (задача находится в фазе ожидания), либо используется лишь частично (задача находится в фазе обмена). В связи с этим часть памяти, не используемой в этих задачах, можно было бы передать — в случае необходимости — в распоряжение задачи, находящейся в фазе
9.3] ТРЕБОВАНИЯ К АППАРАТУРЕ 625 счета. Но когда одна из этих задач в дальнейшем будет переводиться в фазу счета, то может оказаться более удобным и выгодным не воз- вращать ей те страницы памяти, которые были переданы в распо- ряжение другой задачи (в это время они могут, например, участ- вовать в обмене), а выделить ей другие, не используемые в этот момент страницы. Для этого и нужна возможность продолжать решение какой-либо задачи после ее прерывания на другом месте памяти, т. е. возможность динамически перемещать программу в памяти (другими словами, перемещать виртуальную машину внутри физической машины). Один из возможных способов обеспечения релокации программ (используемый, например, в машине БЭСМ-6) базируется на ис- пользовании регистров страничной приписки. Суть этого способа состоит в следующем. Как уже отмечалось, при подготовке своих программ пользо- ватель имеет дело не с реальной, а с виртуальной машиной, объем оперативной памяти которой может превосходить объем физической памяти реальной машины (если не использовать механизм базиро- вания, с которым мы познакомились в главе 4, то объем памяти виртуальной машины определяется числом разрядов в каждом из полей адресов в команде, которое может быть избыточным для объема реальной физической памяти). При составлении программы естественно использовать виртуальные страницы с последователь- ными номерами, начиная с нуля. В рассматриваемом способе каждой виртуальной странице памяти в машине ставится в соответствие свой регистр приписки с тем же номером, что и номер этой виртуальной страницы. В каждом регистре записываются номера физических страниц. Таким образом, количество регистров приписки определяет число допустимых виртуальных страниц, а разрядность этих регистров определяется числом физических страниц. Регистры страничной приписки используются для отображения страниц виртуальной памяти на страницы физической памяти. Делается это следующим образом. Пусть в какой-либо задаче ис- пользуются три виртуальные страницы с номерами 0, 1 и 2, а для этой задачи выделены из числа свободных три физические страницы с номерами 2, 3 и 4. При переводе этой задачи в фазу счета'дис- петчер в каждый из регистров приписки с номерами 0, 1 и 2 (которые соответствуют используемым в данной задаче виртуальным стра- ницам) заносит номер той физической страницы, на которую ото- бражена данная виртуальная страница. Если предположить, что виртуальная память состоит из 8 страниц (с номерами от 0 до 7), то в рассматриваемом нами случае будет сформировано содержимое регистров приписки, показанное на рис. 9.3. В связи о тем, что диспетчер (или, по крайней мере, его часть) должен постоянно находиться в оперативной памяти, физическую
МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ [Гл. 9 страницу с номером 0 можно постоянно закрепить за диспетчером и никогда не выделять ее для обычных задач. Поэтому наличие нуля в каком-либо регистре приписки означает, что соответству- ющая виртуальная страница не отображена на физическую память и поэтому такие виртуальные страницы в программе не должны использоваться № О 1 2 3 4 5 6 7 РСП Рис. 9.3. 2 3 4 О О О О О (в данном случае — это страницы с номерами 3—7). Информация о физических страницах, выделен- ных для каждой из решаемых задач, хранится в информационном поле этой задачи, так что при переводе какой-либо задачи в фазу Счета диспет- чер обеспечивает формирование нужного содер- жимого регистров приписки для этой задачи. В процессе выполнения программы при каж- дом обращении к памяти по какому-либо вир- туальному адресу аппаратура производит подме- ну номера виртуальной страницы, содержащего- ся в этом адресе, на номер физической страницы, выбираемый из соответствующего регистра при- писки. По адресу, полученному в результате та- кой подмены номера страницы, и производится фактическое обращение к физической памяти. Если, например, в рассматриваемом нами слу- чае в выполняемой программе встретится обраще- ние к памяти по указанному в ней виртуальному адресу 2147 (2 — номер виртуальной страницы, 147 — номер ячейки в этой странице), то аппара- тура подменяет номер 2 виртуальной страницы номером 4 физической страницы, выбираемым из регистра приписки с номером 2. По полученному таким образом адресу 4147 и будет произведено фактическое обращение к памяти, Если в результате прерывания, возникшего при решении данной задачи, диспетчер переведет в фазу счета другую задачу, в которой используются виртуальные страницы с номерами 0, 1, 2 и 3, ото- браженные на физические страницы с номерами 1, 5, 6 и 7 соот- ветственно, то диспетчер сформирует содержимое регистров при-» писки, показанное на рис. 9.4. Из описанного выше способа ис- пользования этих регистров видно, что если в этой задаче будет производиться обращение к памяти по тому же самому виртуально- му адресу 2147, то теперь фактическое обращение к памяти про- изойдет по адресу 6147 (а не по адресу 4147, как это имело место в первой задаче). Заметим, кстати, что регистры страничной приписки могут не- посредственно использоваться и для защиты памяти: поскольку физическая страница с номером 0 никогда не используется в обыч- ных программах, то выбор номера 0 из регистра приписки при под- мене номера виртуальной страницы на номер физической страницы
*.з] ТРЕБОВАНИЯ К АППАРАТУРЕ как раз и означает попытку обращения к ячейке памяти, не при- надлежащей данной задаче, и в этом случае возбуждается преры- вание по защите памяти. Такая ситуация будет иметь место, на- пример, в случае, когда во второй из упомянутых выше задач в программе встретится обращение к памяти по виртуальному адресу 4250, Из характера функционирования этого механизма видно, что каждую виртуальную страницу можно с одинаковым успехом ото- бразить на любую физическую страницу, причем это отображение может изменяться динамически № ‘ PC*1. без необходимости вносить в программу какие бы то ни было изменения — для этого достаточно в Нужный момент изменить то содержимое, которое заносится в регистры приписки при переводе дан- ной задачи в фазу счета. Это обстоятельство и обесценивает возможность релокации программ. Правда, при этом возникает проблема: как ' сохранить содержимое страниц, которые времен- но передаются в использование другим задачам, и как обеспечить нужное исходное содержимое _ тех страниц физической памяти, которые вновь выделяются для данной задачи вместо отобранных у нее для других задач страниц. Эта проблема решается за счет того, что вир- туальная оперативная память отображается на фи- зическую внешнюю память, т. е. содержимое стра- ниц виртуальной оперативной памяти хранится во внешней памяти машины. При переводе ка- кой-либо задачи в фазу счета диспетчер про- 0 1 1 5 2 6 3 7 4 о 5 0 6 0 7 0 Рис. 9.4. веряет, отображена ли виртуальная память дан- Ной задачи на физическую оперативную память (диспетчер по- стоянно следит за тем, как использована физическая память), и в случае необходимости копирует содержимое нужной виртуальной страницы из внешней памяти в отведенную для нее физическую страницу. Если же какая-либо страница физической памяти, ис- пользуемая в данной задаче, передается в распоряжение другой задачи, то диспетчер — в случае необходимости — копирует со- держимое этой страницы оперативной памяти во внешней памяти. Таким образом, в физической оперативной памяти можно иметь не все, а только часть виртуальных страниц. Следовательно, при выборе нуля из какого-либо регистра приписки (в результате чего, как мы знаем, возникнет прерывание по «защите памяти») имеются две возможности, в чем и должен разобраться диспетчер: либо такой страницы у задачи вообще нет, и тогда диспетчер квалифи- цирует эту ситуацию как наличие в программе ошибки/л ибо такая страница у задачи есть, но она в это время, не отображена на физи-
628 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ [Гл. » v- ческую оперативную память. В последнем случае диспетчер выде- ляет для этой задачи свободную в данное время физическую страницу (изгоняя, в случае необходимости, из оперативной памяти одну из Неиспользуемых в данный момент виртуальных страниц), делаёт «подкачку» требуемой виртуальной страницы (т. е. переписывает содержимое этой страницы из внешней памяти в выделенную фи- зическую страницу), дозаполняет регистры страничной приписки и продолжает решение задачи. Здесь, в частности, мы встречаемся с Примером того, что прерывание по защите памяти не обязательно есть следствие ошибки в программе. 9.3.5. Машинные часы. При практическом использовании мультипрограммногб режима в ряде случаев бывает важно, чтобы диспетчер мог вести учет ре- ального (астрономического) времени. Такая возможность важна, например, для выявления в программах ошибок типа «зацикли- вание», которые могут привести к бесполезным затратам большого количества времени работы процессора. При однопрограммном режиме оператор ЭВМ может достаточно просто контролировать время, затрачиваемое на решение задачи, и в случае превышения лимита, указанного автором программы, прекращать ее решение. При мультипрограммном режиме оператор ЭВМ практически лишен такой возможности — это может сделать только диспетчер, поскольку именно он переводит решаемые од- новременно задачи в различные фазы. ’Для этого в составе машины предусматриваются специальные часы, показания которых может снимать диспетчер. Базой для таких часов обычно является генератор импульсов, который с определенной частотой (через определенные промежутки времени, например, через’ 0.001 сек.) вырабатывает импульсы тока. При поступлении каждого такого импульса возбуждается прерывание. Поскольку диспетчер реагирует на каждое прерывание, то тем самым он получает возможность вести учет времени и, в частности, следит за соблюдением лимита времени, отведенного на решение каждой из задач. 9.4. Появление операционных систем Итак, с одной стороны, разработка и создание диспетчеров для ЭВМ была вынужденной мерой, обусловленной усложнением самих машин, причем диспетчер, будучи программным продолже- нием устройства управления машины, является неотъемлемой ее частью: без диспетчера современная ЭВМ вообще не может нор- мально функционировать. При этом следует отметить, что диспетчер сильно отличается от обычных программ как из-за сложности среды, в которой он функционирует, так и из-за специфики возлагаемых
9.4] ПОЯВЛЕНИЕ ОПЕРАЦИОННЫХ СИСТЕМ .629 . : . • - • - ? • на него функций. В частности, ему приходится иметь дело с парал- лельными процессами, протекающими асинхронно (не согласованно по времени)', что предъявляет к диспетчеру ряд специфических требований по сравнению с обычными программами. Поэтому раз- работка и создание диспетчеров, удовлетворяющих предъявляемым к ним требованиям, потребовали значительных усилий со стороны программистов. С другой стороны, благодаря тому, что диспетчер — помимо вы- полнения своих основных функций по управлению потоком задач — может оказывать программистам дополнительные услуги путем выполнения соответствующих директив, содержащихся в про- грамме, программисты получили возможность обеспечивать себе дополнительные удобства за счет создания нужного набора дирек- тив (т. е. получения достаточно удобного набора операций вирту- альной машины, в расчете на которую, собственно говоря, и со- ставляются программы). И когда программисты справились с труд- ностями, связанными с обеспечением основных функций диспет- чера, они стали думать об обеспечении удобств в своей работе путем создания необходимого набора директив. В этой деятельности можно выделить следующие основные направления. а) Улучшение набора директив, аналогичных машинным опе- рациям, но более содержательных (вычисление элементарных функций, операции обмена с внешними устройствами и т. д.). б) Создание директив принципиально нового типа, которые невозможно было осуществить аппаратным путем из-за их высокой сложности и которые придают виртуальным машинам качественно новые свойства по сравнению с реальными ЭВМ. Помимо этого на диспетчеры стали возлагать и такие инженер- ные и административные обязанности, как диагностика работы отдельных устройств машины, организация эффективного, решения множества задач, обеспечение различных режимов использования ЭВМ со стороны пользователей, учет расходования ими ресурсов машины, поддержание непрерывной работоспособности системы и т. *д. Диспетчеры, выполняющие подобного рода дополнительные функции, стали называть операционными системами (ОС). Таким образом, операционная система (как и диспетчер, который можно считать простейшей ОС) является программным расширением уст- ройства управления ЭВМ. Но поскольку операционная система выполняет довольно много дополнительных функций, то ее уже трудно рассматривать как единую программу: на самом деле это большой комплекс программ, работающих согласованно друг с другом, так что это действительно некоторая система специ- альных программ. Существуют разные точки зрения относительно того, какие программы следует относить к операционной системе. Одна из них
530 МУЛЬТИПРОГРАММНЫЙ РЕЖИМ РАБОТЫ ЭВМ (Гл. » ’ состоит в том, что к ОС относят все программы, предназначенные йё для решения каких-то конкретных, самостоятельных задач пользователей, а для обслуживания как пользователей, так и их программ. Однако в этом случае определение операционной системы оказывается весьма расплывчатым. Так, программы вычисления эле- ментарных функций традиционно относятся к ОС, в то время как более крупные программы, например программы вычисления оп- > рёделенных интегралов или решения систем дифференциальных уравнений, уже не принято относить к ОС. Еще сложнее обстоит дело с трансляторами, загрузчиками и т. д. С одной стороны, они ' выполняют явно обслуживающие функции, поскольку они не решают каких-либо конкретных задач, а лишь позволяют поль- зователям писать свои программы на различных языках. С другой стороны, выполнение, например, транслятора ничем не отличается от выполнения любых других программ пользователей: транслятор также может выдавать директивы обращения к внешним устройст- вам, прерываться, возобновлять свою работу и т. д. В связи с этим мы под операционной' системой будем понимать совокупность программ, выполняющихся в приви- легированном режиме. Операционную систему, систему програм- мирования, а также все другие обслуживающие программы, которые доступны всем пользователям для проведения вспомогательных вычислений, для организации счета или любых других форм об- служивания, принято называть программным обеспечением ЭВМ (иначе его еще называют математическим обеспечением (МО) ЭВМ). Вычислительная машина вместе с ее программным обеспечением называется вычислительной системой — именно с нею, а не с самой ЭВМ, и имеют дело пользователи. Заметим, что в английском языке для первой из этих компонент (ЭВМ) употребляется термин hardware («жесткое оборудование»), а для второй компоненты — термин software («мягкое оборудова- ние») — мягкое в том смысле, что эту компоненту можно легко изменять, дополнять, усложнять и т. д., не изменяя самой .ЭВМ. На первых ЭВМ математическое обеспечение практически отсутст- вовало, и все полезные обслуживающие программы приходилось создавать самим пользователям. Сейчас пользователям поставляются уже не ЭВМ как таковые, а вычислительные системы с весьма раз- витым математическим обеспечением, причем доля этой компоненты в общей стоимости вычислительной системы постоянно растет, составляя в современных системах около 80%. С системами программирования мы уже познакомились в главе 6. Более подробному рассмотрению операционных систем посвящена следующая глава.
Г л а в a 10 ОПЕРАЦИОННЫЕ СИСТЕМЫ Конкретные операционные системы, разработанные для разных ЭВМ, или даже для одной и той же ЭВМ, но в разное время или разными коллективами разработчиков, довольно сильно отлича- ются друг от друга. Эти отличия обусловлены свойствами и назна- чением самих ЭВМ, спецификой их использования в разных вы- числительных центрах, а иногда и просто научными взглядами их авторов. В связи с этим в данной главе мы не будем иметь в виду какую-либо одну операционную систему, а рассмотрим наиболее типичные возможности, имеющиеся в разных системах. При этом мы не будем касаться вопросов реализации операционных систем, 10.1. Работа с данными К этой группе мы отнесем те возможности, которые ОС предо- ставляет пользователю для доступа к данным, хранящимся как в оперативной, так и во вспомогательной памяти. С этой точки зрения в обычной машине в распоряжении поль- зователя имеются адресуемая оперативная память (обычно весьма ограниченной емкости); вспомогательная память, реализованная в Виде конкретных устройств, при работе с которыми пользователь оперирует с адресами, указывающими место начала нужной порции данных на этих устройствах (например, номер зоны на МЛ); те реальные устройства ввода/вывода, которые входят в состав данной ЭВМ, а также соответствующие машинные операции. Виртуальная машина, предоставляемая операционной системой в распоряжение пользователя, имеет в этом отношении гораздо большие возможности. 10.1.1. Виртуальная оперативная память. Виртуальная оперативная память может значительно превос- ходить по емкости физическую оперативную память за счет того, что ОС может отображать ее на физическую внешнюю память, емкость которой в современных ЭВМ достаточно велика, и по мере надобности вызывать в физическую оперативную память отдельные страницы оперативной памяти различных виртуальных машин. Возможность такого отображения основывается на том, что в реальных программах вычисления всегда «сконцентрированы по
532 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. (0 памяти» — в том смысле, что на отдельных (порой достаточно дли- тельных) этапах вычислений используется лишь сравнительно небольшая часть всей оперативной памяти, необходимой для данной задачи. В частности, основной объем вычислений производится по ко- мандам, которые входят в тот или иной цикл и поэтому, как правило, размещаются в одной и той же странице памяти, так что переход от одной страницы к другой при выборе команд производится срав- нительно редко. Поэтому в физической оперативной памяти можно хранить не всю программу, а только работающую ее часть. Если по ходу выполнения программы окажется, что очередная подлежа- щая выполнению команда не находится в физической оперативной памяти, то можно сделать «подкачку», т. е. вызвать нужную стра- ницу виртуальной памяти из внешней памяти в оперативную; если при этом в физической оперативной памяти нет свободного места, то такой вызов можно произвести на место одной из отрабо- тавших частей программы. Если на машине имеется возможность аппаратной релокации программ, то такая подкачка осуществляется сравнительно просто. Сказанное выше относится также и к мас- сивам обрабатываемых данных. Естественно, что выполнение всех дополнительных действий, связанных о функционированием механизма подкачки, обеспечи- вается ОС — пользователь об этом может вообще не знать. . По сути дела единственное, что требуется от аппаратуры для обеспечения указанных выше возможностей — это увеличение диа- пазона допустимых адресов в командах, чтобы пользователь мог свободно оперировать большим объемом виртуальной оперативной памяти. Например, в машинах серии ЕС ЭВМ этот объем составляет 2м байта, тогда как объем реальной (физической) памяти на кон- кретной машине может быть значительно меньше. По этой, в част- ности, причине современные ЭВМ имеют обычно одноадресную или дробно-адресную систему команд. 10.1,2. Виртуальные устройства ввода/вывода. Число устройств ввода/вывода в виртуальной машине не за- висит от числа таких устройств в ЭВМ и фактически неограничено. Необходимость одновременного использования нескольких уст- ройств ввода или вывода часто обусловливается спецификой задачи. Пусть, например, в некоторой задаче требуется получить и отпе- чатать три таблицы значений ut, vt, wt соответственно (t=1, ..., n), которые связаны между собой отношениями ut—Fl (f/-i, Щ/-1), Pj=F2 («|, ie»/-i), Wi—F3(ut,»vi) при заданных начальных значе- ниях Uo, о», w0- Предположим, что в данной задаче нет возможности поместить в оперативной памяти все эти три таблицы целиком и поэтому .каждое очередное вычисленное значение надо выводить на печать. Но поскольку по характеру задачи придется последова-
10. ti РАБОТА С ДАННЫМИ 533 тельно вычислять значения, принадлежащие разным таблицам, то с использованием только одного печатающего устройства до- вольно трудно отпечатать отдельно три разные таблицы. Поэтому в программе удобно использовать три виртуальных печатающих устройства (с номерами 1, 2 и 3), каждое из которых служит для печати одной таблицы. В этом случае программа (точнее, ее фраг- мент) будет иметь вид: ы:=«0; о:=о0; о»:=аЮ; for i:=l step 1 until n do begin u:=F\ (v, вывод (1, и); v:=F2 (и, w)', вывод (2, о); w:—F3 (и, v); вывод (3, w) end Если на самом деле в машине имеется только одно печатающее устройство, то ОС должна отобразить на это единственное реальное устройство все три виртуальные устройства, использованные в программе. Для этого ОС имеет свои собственные ресурсы внешней памяти (обычно это системные ленты или диски). При обслужи- вании очередного заказа на печать, поступающего из программы пользователя, ОС вместо фактической печати передаваемых зна- чений накапливает их на системной ленте, а после того, как на ней все три таблицы будут записаны полностью, ОС (по завершении выполнения данной программы) обеспечивает последовательный вывод на печать каждой из этих таблиц о помощью одного и того же реального печатающего устройства. Для пользователя же эффект будет таким, как если бы каждая таблица печаталась по ходу ее получения на отдельном устройстве. Помимо моделирования любого числа виртуальных устройств ввода и вывода ОС обеспечивает перекодировку символов, вводимых о различных реальных устройств ввода (пятидорожечная перфо- лента, семиразрядные коды устройства подготовки перфокарт УПП и т. д.) в единую форму представления символов, так что программа пользователя, производящая обработку вводимых символов, может не учитывать разницу в их кодировке на различных устройствах ввода (и даже не знать о ее существовании). ОС может даже рас- ширять набор допустимых символов на устройствах. Если, напри- мер, на каком-то конкретном печатающем устройстве нет символов, являющихся знаками логических операций, ОС тем не менее может вывести на него логическое выражение, заменяя недостающий сим- вол некоторой стандартной последовательностью имеющихся на этом устройстве символов, например 'NOT' вместо *]'AND' вме- сто Д и т. д. Аналогично может быть расширен и набор символов вводного устройства: при вводе с него комбинации символов 'NOT' операционная система может передать для дальнейшей обработки код одного символа "].
534 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 Все эти возможности, обеспечиваемые ОС, позволяют пользо- вателю при составлении своей программы иметь дело лишь с функ- циональной стороной внешних устройств (печать, перфорация и т. д.), не учитывая специфических особенностей конкретных уст- ройств (перфорация на бумажной ленте или картах, построчная или поколонная перфорация на картах и т. д.). Более того — тип требуемого устройства вывода можно вообще не фиксировать в программе, а указать в паспорте задачи, так что не меняя програм- мы можно обеспечивать вывод на разные устройства — для этого достаточно лишь задать новую информацию в паспорте. То же самое можно сказать и об устройствах ввода. 10.1.3. Виртуальная внешняя память. Как уже отмечалось, емкость виртуальной оперативной памяти может значительно превосходить емкость физической оперативной памяти ЭВМ. Тем не менее в виртуальной машине также необхо- димо иметь свою (виртуальную) вспомогательную внешнюю память хотя бы потому, что емкость виртуальной оперативной памяти все же может оказаться недостаточной при решении некоторых задач. Однако эта необходимость обусловливается и более вескими причинами. Прежде всего тем, что виртуальная машина — путем соответ- ствующего ее отображения на реальную ЭВМ — предоставляется в распоряжение задачи пользователя только на время ее нахож- дения в стадии решения (начиная с ее включения в поток решаемых задач и кончая ее исключением из этого потока). Между тем по ходу решения своей научной или иной проблемы пользователю, как правило, требуется несколько (порой довольно много) сеансов ра- боты на машине, и при этом на очередном сеансе работы должны использоваться результаты, полученные на предыдущих сеансах. Поэтому пользователю необходимо иметь возможность сохранять нужные данные во внешней памяти машины — в противном случае он должен был бы при каждом сеансе работы предусматривать ввод в машину нужных ему данных (например, с перфокарт) и вывод на перфокарты всех тех результатов, которые должны ис- пользоваться в последующих сеансах. Использование для этих целей непосредственно физической внешней памяти весьма затруд- нительно хотя бы из-за большого и часто меняющегося контингента х пользователей, ибо в этой ситуации практически невозможно за- крепить за каждым отдельным пользователем определенную часть внешней памяти нужного ему объема и обеспечить сохранность записанной в нее информации, т. е. предотвратить неправомерное ее использование ср стороны других пользователей. Поэтому в распоряжение пользователя и предоставляется не физическая, а виртуальная внешняя память, которая характеризуется лишь ее
10.1J РАБОТАС ДАННЫМИ «33 функционал ь н ы м и свойствами (тем, что записываемые в нее данные могут храниться для последующего многократного использования в течение любого желаемого периода времени), без конкретизации того, на какой части какого именно устройства (барабана, диска и т. д.) она реализована. Как и в .случае виртуаль- ной оперативной памяти, операционная система отображает вирту-, дльную внешнюю память на конкретные устройства машины,и обеспечивает правильное ее функционирование. ' Особенно важным является то обстоятельство, что ОС придает ' виртуальной внешней памяти дополнительные свойства, сущест- венно облегчающие пользователю работу с нею. Дело в том, что при использовании физической внешней памяти любые данные прихо- дится представлять в виде последовательности машинных слов, а для ссылок на эти данные использовать адреса, определяющие место этих данных во внешней памяти. . Например, при использовании магнитной ленты пользователь оперирует только с номерами зон на. ленте, поэтому он должен знать, какая часть его данных, представленная в виде последова-. тельиости машинных слов, помещается в одной зоне, а для работы с этими данными он должен предусматривать в своей програм- ме своевременное чтение содержимого зоны с нужным номером в оперативную память и выделение из прочитанного содержи- мого требуемых данных, подлежащих обработке, что очень не- удобно. Виртуальная же внешняя память представляет собой совокуп- ность наборов данных, организованных специальным образом и доступных для их обработки на машине. Такие наборы данных называются файлами. Одна из важнейших особенностей файла состоит в том, что структура содержащихся в нем данных не определяется , особенно- стями конкретной машины, в том числе и того или иного устройства, используемого в качестве носителя данных (барабан, диск и т. д.), а соответствует собственной структуре описываемого объекта. Еди- ницей данных в файле является, как правило, не машинное слово, а запись, т. е. документ, который описывает какой-либо самостоя- тельный объект и который может быть представлен как одним, так и несколькими машинными словами. Запись может являться от- дельным числом, строкой текста, совокупностью сведений о лице, полученных при переписи населения и т. д. Если, например, файл представляет собой текст автокодной программы, то каждое очередное автокодное предложение, неза- висимо от его длины, является записью в файле. В программе, производящей какую-либо обработку этого текста (его . редактиро- вание, трансляцию и т. п.), можно обращаться к файлу для чтения из него очередной записи, представляющей собой очередное авто- кодное предложение вместо того чтобы предусматривать в про-
бзв ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 грамме чтение с магнитной ленты зоны с определенным номером и затем каким-то образом выделять из прочитанной порции данных соответствующую последовательность машинных слов. Другая важная особенность файлов состоит в том, что при ра- боте с ними пользователь идентифицирует сами данные, а не место во внешней памяти, на котором они записаны. Как правило, эта идентификация производится с помощью имён, присвоенных фай- лам — подобно тому как в алголе для ссылки на требуёмое отдель- ное (переменное) значение используется не адрес ячейки памяти,, отведенной для хранения этого значения, а имя, присвоенное этому переменному значению (т. е. идентификатор переменной). Как правило, ОС допускает использование файлов нескольких различных структур, удобных для организации и обработки раз- личных типов данных. В соответствии с этим ОС предоставляет в распоряжение пользователя директивы для работы с файлами различной структуры. Наиболее типичными являются следующие структуры файлов. Адресный файл. Здесь предполагается, что набором данных, образующих файл, является перенумерованная последовательности машинных слов, так что для ссылки на требуемое машинное слово указывается имя файла и номер этого слова (а не его адрес во внеш- ней памяти). При этом чтение и запись могут производиться лю- быми порциями слов, начиная со слова с указанным номером, для чего используются директивы ЧИТАТЬ (Ф,N, М, А) и ПИСАТЬ (Ф, N, М, А), где Ф — имя файла, М — порядковый номер в файле первого из передаваемых слов, Af — число передаваемых слов, А — адрес в оперативной памяти, начиная с которого надо разместить читаемые из файла слова (находятся слова, записываемые в файл). Данная структура файла является самой простой. Последовательный файл. Это наиболее часто используемая в практической работе структура файла, состоящего из последова- тельности записей общего вида. Последовательный файл отражает весьма типичный характер работы с записями, когда записи распо- лагаются в файле по порядку их поступления, а при обработке файла они последовательно перебираются друг за другом. Различаются файлы с записями фиксированной длины и с за- писями переменной длины. В первом случае все записи файла имеют одинаковую длину, задаваемую в момент образования файла. Во втором случае разные записи одного и того Же файла могут иметь различные длины — длина каждой записи определяется при ее образовании. Для работы с таким файлом каждая запись должна содержать дополнительную информацию, позволяющую определить ее границы. Здесь обычно используется один из двух способов: либо длина записи указывается в ее начале, либо запись завер- шается специальным кодом, являющимся признаком конца записи (в каждом файле используется один из этих способов).
10.1] РАБОТА С ДАННЫМИ 537 Читатель, по-видимому, уже обратил. внимание на сходстве» между, последовательными файлами и списками. Файл действительно представляет собой список, но организованный во внешней памяти из-за своих больших размеров либо из-за того, что он должен не- однократно использоваться при последовательных сеансах решения на машине одной или нескольких задач. Последовательные файлр обычно применяются в тех случаях, когда однажды занесенная в файл информация впоследствии используется многократно, без ее изменения (например, информация, полученная в результате переписи населения и занесенная в файл, впоследствии может использоваться для получения самых различных статистических данных). Чтобы перебор записей осуществлялся максимально быстро, для их объединения в единую структуру удобнее исполь- зовать не сцепление, а векторную форму. Как мы знаем.из главы,3, в этом случае замена одной записи на другую при различных их длинах весьма затруднительна. В связи с этим ОС обычно не допу- скает замены записей внутри последовательного файла, а выполнение директивы ПИСАТЬ означает, что запись, включаемая в файл по этой директиве, становится последней в этом файле. При обменах с последовательным файлом, производимых по директивам вида ПИСАТЬ (Ф, А) и ЧИТАТЬ (Ф, А), указывается только имя файла Ф и адрес А записи в оперативной памяти. Пе-. реход к следующей записи файла обеспечивается операционной системой после каждого обмена. С этой целью ОС для каждого файла заводит свой указатель — ячейку, в которой все время хра- нится адрес очередной записи файла, и при каждом обмене ОС «продвигает» этот указатель вперед на одну запись (т. е. формирует в этой ячейке адрес следующей записи). Кроме упомянутых выше директив чтения и записи, для работы с последовательными файлами используются вспомогательные ди- рективы, с помощью которых можно установить указатель на начало первой записи в данном файйе, продвинуть указатель на одну запись вперед или назад не производя фактического обмена, а также пере- дать в файл специальную запись, являющуюся признаком конца файла. Индексный файл. В отличие от предыдущих структур файлов, здесь каждая запись имеет свое имя, что позволяет обращаться к отдельным записям в произвольном порядке. Для работы с ин- дексным файлом используются директивы вида ЧИТАТЬ (Ф, 3, А) и ПИСАТЬ (Ф, 3, М, А) где Ф — имя файла, 3 — имя записи, М — длина записи, А адрес оперативной памяти, а также директива вида ЗАБЫТЬ (Ф, 3), с ее помощью из файла можно исключить запись с данным именем. Для работы с индексным файлом ОС организует таблицу, в которой хранятся имена записей и их адреса во внешней памяти -г. при этом используются методы, которые мы рассматривали в главе 3.
В38 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 Индексно-последовательный файл. Эта структура файла объе- диняет в себе преимущества двух последних структур: быстроту выбора очередной записи в последовательном файле и простоту обращения к произвольным записям в индексном файле. В индексно- последовательном файле имена могут иметь не все, а лишь отдель- ные записи, которые являются головными в некотором «разделе» последовательных записей, так что имя записи играет роль и имени соответствующего раздела. В этом случае, используя директивы работы с индексным файлом, можно перейти к любому разделу, а затем работать с этим разделом как с последовательным файлом, после чего можно перейти к любому другому разделу и т. д. Такой способ работы оказывается достаточно удобным и эффективным во многих практических задачах. Желаемая структура того или иного файла, вводимого в упо- требление, задается в паспорте задачи или в директиве, по которой создается этот файл. Для предотвращения случайной порчи ин- формации, содержащейся в файлах, вводятся специальные дирек- тивы открытия и закрытия того или иного файла. В директиве открытия файла обычно указывается и допустимый характер обмена с этим файлом (только чтение, только запись, или и чтение и за- пись). Операционная система выполняет директивы обмена с каж- дым из файлов только между моментами его открытия и закрытия, а также контролирует соблюдение заданного характера обмена с этим файлом. При открытии файла ему может быть приписан определенный буфер в оперативной памяти — в этом случае весь обмен с файлом ведется через этот буфер. Размер буфера обычно выбирается таким, чтобы в нем помещалась не одна запись, а группа (блок) записей. Если требуемая запись уже находится в блоке, прочитанном ранее на буфер, то обращение к внешней памяти фактически не произ- водится; если же требуемой записи на буфере нет, то на буфер из внешней памяти считывается целый бло$ информации, содержащий ату запись. Таким образом, при последовательном просмотре всех записей файла число фактических обменов с внешней памятью уменьшится в К раз, где К — среднее количество записей в блоке. Это число К называют фактором блокирования записей. Наиболь- ший эффект блокирование записей дает в случае последовательного файла. Указанные выше свойства виртуальной внешней памяти не только существенно упрощают ее использование в программах (по сравнению с физической внешней памятью), но и обеспечивают большую гибкость в последующем использовании однажды состав- ленных программ. Поскольку файл отражает лишь логическую структуру набора данных, не конкретизируя его фактического положения на том или ином устройстве, то программа получается машинно-независимой. И точно так же, как отдельные страницы
10.1] РАБОТА С ДАННЫМИ 539 * “ / виртуальной оперативном памяти тон или иной задачи , могут ото- бражаться операционной системой на различные страницы физи- ческой памяти, один и тот же файл может быть размещен на раз- личных носителях данных — на одном из устройств внешне^ па- мяти, на перфокартах, отпечатан на рулоне бумаги и т. д. При этом конкретизировать размещение файла ОС может либо по соб- ственному усмотрению, либо по указанию пользователя., Ёсли, например, какой-либо файл в программе представляет собой ре- зультаты ее выполнения, то при использовании этой программы пользователь — дав соответствующее указание операционной си- стеме — может либр записать эти результаты во внешнюю память (указав конкретный тип устройства внешней памяти или передав решение этого вопроса на усмотрение ОС), либо вывести на печать или на перфорацию. 10.1.4. Архивная служба. Обычно ОС допускает образование как временных файлов, существующих только во время выполнения задачи, так и личных библиотек файлов с целью длительного их хранения и использо- вания. Кроме того, ОС имеет и постоянную библиотеку файлов, в которой хранятся данные, доступные для всеобщего пользования: таблицы, программы, обеспечивающие функционирование системы программирования (трансляторы, загрузчики и т. д.), различного рода обслуживающие программы и т. п. ОС широко использует' промежуточные системные файлы и для своих собственных нужд: для хранения задач, введенных в систему для решения, для ото- бражения виртуальной оперативной памяти виртуальных машин, для виртуализации 'устройств ввода/вывода и т. д. Для долговременного хранения всех имеющихся файлов и обес- печения правильности их использования в ОС имеется специальная ее часть — архивная служба, которая выполняет сле- дующие основные функции. а) Копирование файлов. Файлы в основном хранятся на маг- нитных дисках и лентах, которые по разным причинам со временем теряют свои рабочие качества и выходят из строя. Поэтому, чтобы гарантировать сохранность файлов, их нужно систематически ко- пировать на работоспособные диски и ленты. Обеспечение длитель- ного хранения файлов избавляет пользователей от необходимости хранить свои данные на перфокартах или перфолентах и много- кратно вводить их в машину. б) Обеспечение эффективной работы с данными. Как мы Уже знаем, магнитные ленты являются наиболее медленным видом внешней памяти. Поэтому на время выполнения программы, ис- пользующей те или иные файлы, ОС для повышения скорости ра- боты с данными может переписать их на более быстрый вид,внешней памяти, например на магнитный барабан, а по окончании выпол*
940 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 нения данной программы — в случае необходимости — вновь пере- писать эти файлы на исходное место во внешней памяти. в) Защита файлов. Поскольку в системе одновременно хранится много файлов, принадлежащих разным пользователям, то ОС должна пресекать неправомерные обращения к чужим файлам, используя для этих целей фамилии пользователей, которым при- надлежат те или иные файлы, систему паролей и т. д. Для реализации всех этих функций ОС обычно ведет каталоги файлов, в которых для каждого файла хранится дополнительная информация о нем, например: — имя файла; — фамилия хозяина данного файла; — пароль, при сообщении которого разрешается доступ к файлу; — состояние файла (где он сейчас расположен, когда он был создан и сколько времени должен храниться, когда копировался последний раз и т. д.). Нередко каталоги файлов имеют иерархическую структуру, которая отражает структуру организации, эксплуатирующей дан- ную вычислительную систему. Например, создаются каталоги файлов, принадлежащих отдельным сотрудникам, каталоги катало- гов всех сотрудников, работающих в одном отделе и т. д. При та- кой организации каталогов нетрудно ввести пароль на право пользования всеми файлами данного отдела, данного сотруд- ника и т. д. 10.2. Управление процессами На традиционной ЭВМ решение любой задачи сводится к един- ственному процессу обработки данных, который реализуется про- цессором по заданной программе. Между тем решение ряда задач естественным образом распадается на несколько «вычислительных» процессов, которые хотя и взаимодействуют между собой, но могут выполняться одновременно. Эта ситуация особенно характерна для задач, связанных с изучением таких сложных реальных объектов, поведение которых обусловливается одновременно протекающими и взаимодействующими физическими процессами, каждый из ко- торых при решении этой задачи на ЭВМ моделируется своим вычис- лительным процессом. В связи с этим операционная система может допускать исполь- зование в одной и той же задаче нескольких виртуальных процес- соров, которые работают параллельно по программам, заданным для каждого из них. В этом случае общая программа решения задачи состоит из нескольких частей, каждая из которых является програм- мой для соответствующего виртуального процессора. Один из этих процессов выделяется в качестве инициирующего, а в паспорте за- дачи указывается начало программы, задающей этот процесс —
10.21 УПРАВЛЕНИЕ ПРОЦЕССАМИ 541 с его запуска и начинается решение задачи. Для запуска других процессов используются директивы вида ЗАПУСТИТЬ ПРОЦЕСС (N, AN) где N — номер процесса, AN — адрес начала программы для этого процесса. По этой директиве в параллельную работу включается новый виртуальный процессор, причем в его счетчик команд зано- сится указанный в директиве адрес AN. Вместо команды останова в программе используется директива прекращения данного процес- са, а решение задачи считается законченным, если прекращены (завершены) все ее процессы. Если в реальной машине имеется только один процессор, то с его помощью ОС моделирует параллельную работу нескольких виртуальных процессоров, выделяемых для каждого из предусмот- ренных в задаче процессов, так что реально в каждый момент вре- мени в фазе счета находится, конечно, только один из этих процессов. Существуют ЭВМ с несколькими физическими процессорами, ко- торые могут работать параллельно, но и в этих машинах число вир- туальных процессоров может превосходить число физических, так что все наши рассуждения остаются в силе и для таких машин. Каж- дый процесс, будучи запущенным, обычно выполняется до тех пор, пока он не будет приостановлен по какой-либо причине или не за- кончится — лишь в этом случае производится переключение фи* зического процессора на другой процесс. С точки же зрения про- граммиста — это действительно отдельные процессы, реализуемые параллельно работающими процессорами. Использование параллельных процессов в одной и той же зада- че является удобным средством описания одновременно протекаю- щих и взаимодействующих явлений. Кроме того, их использование позволяет в ряде случаев сократить время решения задачи и повы- сить общую производительность машины, так как переключение с од- ного процесса на другой в рамках одной задачи обычно осущест- вляется операционной системой значительно быстрее, чем пере- ключение с задачи на задачу (в этом случае не нужно, например, менять содержимое регистров приписки; по сравнению с переклю-' чением с задачи на задачу уменьшается вероятность того, что при- дется делать «подкачку» страниц и т. д.). Конечно, для того чтобы все параллельно работающие виртуаль- ные процессоры совместно решали одну задачу, они должны быть синхронизированы, т. е. их работа должна быть согласована по вре- мени. Задача такой синхронизации значительно упрощается за счет услуг, предоставляемых пользователям операционной системой. В качестве иллюстрации использования таких возможностей рассмотрим первую фазу работы ассемблера, блок-схема которой была приведена ранее. В более общем виде эту фазу можно изо- бразить следующей блок-схемой (см. рис. 10.1).
54? ОПЕРАЦИОННЫЕ СИСТЕМЫ (Гл. 10 Из этой.схемы видно, что первая фаза ассемблирования по сути дела распадается на два процесса: а) ввод автокодных строк; б) обработка введенных строк. Поскольку в нашей схеме предусмотрен ввод только одной стро- ки с последующей ее обработкой, то эти два процесса все время че- редуются, благодаря чему и обеспечивается их согласование. Од- нако очевидно, что данным схема весьма неэффективна, поскольку один процесс все время задерживает другой: процесс ввода не может Рис. юл. продолжаться, пока не закончена обработка введенной строки, а про- цесс обработки, завершив работу с очередной строкой, вынужден ждать завершения ввода следующей строки. Между тем эти процессы могли бы протекать и параллельно: ведь во время обработки какой-либо строки вторым процессом, на что иногда требуется значительное время, первый процесс мог бы продолжать вводить все новые и новые строки, создавая запас вве- денных, но еще -необработанных строк; с другой стороны, наличие такого запаса позволило бы беспрепятственно продолжать процесс обработки даже при некоторых задержках процесса ввода, надри- мер из-за устройств, с которых производится ввод строк. Поэтому целесообразнее было бы для вводимых строк выделить в памяти определенный буфер и для выполнения первой фазы ассем- блирования предусмотреть в программе два параллельных процес- са: первый — ввод строк на буфер, и второй — обработка строк, находящихся на буфере (с исключением из него каждой обработан- ной строки). Тогда схема первой фазы ассемблирования примет вид, изображенный на рис. 10.2. Теперь программисту удобно считать, что в его распоряжении имеются два виртуальных процессора, один из которых обеспечивает ввод на буфер автокодных предло- жений, а второй обрабатывает строки, выбираемые из буфера, при- чем эти два процессора работают параллельно. Но поскольку емкость буфера ограничена, а процессы ввода и об- работки строк протекают параллельно и независимо друг от друга, то может случиться, что обработка не будет успевать за вводом и буфер переполнится, либо наоборот — обработка обгонит ввод и буфер окажется пустым. Заранее же рассчитать протекание этих процессов во времени и тем самым согласовать их работу довольно трудно — хотя бы потому, что обработка различных предложений автокода производится по-разному и тем самым занимает разли'ч-
10.2) УПРАВЛЕНИЕ ПРОЦЕССАМИ 543 ное время работы процессора. Поэтому необходимо принять специ- , альные меры по синхронизации этих двух процессов. Казалось бы, что эту синхронизацию можно осуществить с по- мощью самих программ, определяющих каждый из процессов, на- пример следующим образом. Введем в употребление целочислен- ную переменную п, общую для обоих процессов, текущее значение которой равно числу необработанных строк на буфере (пусть,4 для Рис. 10.2. определенности, всего на буфере могут быть помещены три автокод- ные строки). Решение задачи начнем с запуска процесса ввода, в ко- тором первоначально, выполняется действие п : =0, после чего осу- ществляется запуск процесса обработки. По окончании ввода каж- дой строки первый процесс должен выполнять действие п : =п-Н, а второй процесс по окончании обработки очередной строки и ее удаления с буфера должен выполнять действие п : =п—1. Для со- гласования этих двух процессов первый из них при п=3 должен ждать, пока второй не освободит место на буфере, а второй должен ждать при п=0, пока на буфер не будет введена хотя бы одна новая - строка. Работу и взаимодействие этих процессов можно изобразить следующей блок-схемой, изображенной на рис. 10.3. По этой схе- ме может идти параллельная работа двух процессов. Однако в этой схеме есть принципиальный недостаток, связанный с изменением значения переменной п, по которому производится синхронизация/ двух параллельных процессов. Так, на одноадресной машине каж- дый из операторов п: =и-Н и п: =п—1 реализуется тремя коман- дами (ЧТ — обозначение операции чтения слова в сумматор из па- мяти, а ЗП — обозначение обратной для нее операции): ЧТ п ЧТ п С =1 и В =1 ЗП п ЗП п Но поскольку процессы параллельны и асинхронны, то не исключе- но, что после выполнения первой идо выполнения третьей команды первого процесса, реализующих действие п: =п-Н, будут выполнены все три команды второго процесса, реализующие действие п : —п—Г
544 ОПЕРАЦИОННЫЕ СИСТЕМЫ (Гл. 10 и тогда вычитание единицы из п фактически не произойдет! (то же. самое может произойти и на трехадресной машине с несколькими физическими процессорами, которые асинхронно обращаются к па-, мяти для выборки и записи операндов). Обработка Ввод Рис. 10.3. Не меньшие трудности вызывает и реализация операции «ждать». Если для этой цели написать, например, пару команд Н, , п, \ <о:=п=О ПЕ, ,*—1, ; Переход при w=true то соответствующий виртуальный процессор будет постоянно занят их выполнением, и если физический процессор на машине только один, то .этот виртуальный процессор не сможет прерваться и воз- вращение к процессу ввода вообще не произойдет. Для устранения этих трудностей переменная п должна находить- ся в распоряжении операционной системы, а изменение ее значения должно производиться только по специальным директивам: в этом случае ОС может обеспечить правильное изменение и использова- ние значения этой переменной. Подобного рода переменные, находящиеся в распоряжении опе- рационной системы, получили название «семафорные переменные» (или просто «семафоры») по аналогии с семафорами на железной дороге. Для работы с семафорами, а также для реализации связан- ной с ними процедуры ожидания, используются специальные ди-
10.21 УПРАВЛЕНИЕ ПРОЦЕССАМИ 545 рективы, простейшие из которых обычно обозначаются через V(n) и Р(п) и имеют следующий смысл: V(ri): п:—п+\ Р(п): если п=0, то прервать данный процесс и ждать дирек- тивы V(n) от другого процесса; в противном случае п:—п—1. Операционная система, как правило, предоставляет для исполь- зования несколько семафоров и к началу решения каждой задачи обеспечивает их нулевые исходные значения. Вернемся к рассмотрению нашего примера. Поскольку при сде- ланных предположениях ОС обеспечивает ожидание в каком-либо Ввод Обработка Рис. 10.4. процессе только при нулевом значении семафора, указываемого в директиве Р(п), то в рассматриваемом примере понадобятся два семафора. Пусть семафор га представляет число необработанных строк на буфере, а семафор k — число свободных мест в нем. Тог- да процесс ввода должен продвигаться или ждать по семафору k, а процесс обработки — по семафору п. Теперь работу рассматриваемых нами параллельных процессов ввода и обработки строк можно изобразить схемой, изображенной на рис. 10.4 (напомним, что ОС обеспечивает начальные значения п=0 и й=0). Если организация решения задачи с использованием параллель- ных процессов разумна, то дополнительные затраты машинного времени на синхронизацию процессов окупятся. Например, время 18 Э. 3. Любимский н др.
546 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 выполнения рассматриваемой нами первой фазы работы ассемблера может существенно уменьшиться за счет совмещения ввода и обра- ботки автокодных предложений. Аппарат параллельных процессов позволяет также обеспечить специальное обслуживание задач, т. е. позволяет предусматривать в задаче процессы специального назначения. Некоторые из них > могут возбуждаться по директивам пользователя, а некоторые — самой операционной системой в зависимости от ситуаций, возни- кающих в процессе решения задачи. Примером такого специального процесса может служить про- цесс обработки необычных, «аварийных» ситуаций — деления на нуль, извлечения квадратного корня из отрицательного числа и т. д. На первых ЭВМ возникновение такой ситуации автоматически влекло за собой останов машины. При мультипрограммном режиме работы это влечет за собой прерывание, при обслуживании которого диспетчер обычно исключает данную задачу из потока решаемых задач. И в том, и в другом случае появление такой необычной си- туации расценивается как следствие ошибок в программе, и потому ее дальнейшее выполнение считается нецелесообразным. Однако такой вывод не всегда является достаточно обоснован- ным — иногда «необычная» ситуация отнюдь не является «аварий- ной», препятствующей продолжению решения задачи. Действительно, допустим, что нужно определить значения xt и ха физических величин, которые недоступны для непосредствен- ного измерения, но зато могут быть измерены величины a, b и с — такие, что искомые значения Xi и xt являются корнями квадратного уравнения ах2+Ьх+с=0, причем по своему смыслу xt и ха являются действительными значениями. Если значения а, b и с измеряются с некоторыми случайными погрешностями, то для уменьшения влияния этих погрешностей можно провести серию измерений {at, bi,C{} (i=l, 2,. . ., N) и в качестве хх и хг принять средние арифметиче- ские значения „ _ S*i. / 2*»./ — N ’ х» — N .* где хх, i и xSt i — корни уравнения а(х2+й^+С(=0. Однако из-за по- грешностей в значениях at, bt и ct решение некоторых уравнений может привести к извлечению квадратного корня из отрицательного числа, и в результате возникнет «аварийная» ситуация, которая на самом деле свидетельствует лишь о недоброкачественности соответ- ствующего измерения. Конечно, можно было бы либо предварительно обработать полученные экспериментальные данные и отбросить из- мерения подобного рода, либо при решении каждого уравнения про- изводить анализ его дискриминанта, но в любом из этих способов придется выполнять дополнительную работу, к тому же совершен- но излишнюю в случае доброкачественных измерений. Поэтому «от- браковку» измерений целесообразнее было бы производить по ходу
10.2] УПРАВЛЕНИЕ ПРОЦЕССАМИ" 547 вычислений: если извлечение корня из очередного дискриминанта приводит к «аварийной» ситуации, то это является лишь сигналом о том, что очередную тройку аь bit ct надо просто опустить из рас- смотрения и перейти к обработке следующей тройки, а не прекра- щать решение задачи. Для этих целей в задаче можно предусмотреть специальный про- цесс со своей программой, который должен возбуждаться при воз- никновении каждой «аварийной» ситуации и принимать меры по ее устранению, если это оказывается возможным. Если аварийная си- туация возникла во время выполнения команды извлечения корня при решении квадратного уравнения, то специальный процесс мо- жет обеспечить продолжение решения задачи с определенной коман- ды и тем самым ликвидировать «аварийную» ситуацию. Если же выяснится, что эта ситуация возникла в каком-то другом, заранее непредусмотренном месте, то специальный процесс может расценить ее появление как следствие ошибки в программе и прекратить ее выполнение. Очевидно, что рассматриваемый процесс нельзя возбуждать по директиве пользователя, поскольку аварийная ситуация может возникнуть в любых, в том числе и заранее непредвиденных ме- стах — его должна возбуждать операционная система. Разумеется, операционной системе должна быть задана (например, в паспорте задачи) информация о том, для каких ситуаций в задаче предусмот- рены специальные процессы и где начинается программа каждого из них. При рассмотрении защиты памяти отмечалось, что прерывание по защите памяти также обычно свидетельствует об ошибках в про- грамме, однако в ряде случаев и это прерывание может быть пре- дусмотрено заранее и разумно использовано. Пусть, например, какой-то процесс использует для обработки данных буфер, длину которого трудно определить заранее. Если под буфер заказать опе- ративную память с запасом, то. это может привести к нерациональ- ному ее использованию; если же заказать памяти слишком мало, то ее может оказаться недостаточно. Между тем, если ОС допускает динамическое распределение ресурсов, то для буфера первоначаль- но можно отвести всего одну страницу. Для контроля переполнений буфера в программе можно, конечно, предусмотреть соответствую- щие команды, но многократное их выполнение повлечет дополни- тельные расходы машинного времени. При отсутствии такой про- верки может возникнуть аварийная ситуация — попытка выйти за пределы заказанной памяти, что прицрдет к прерыванию по защите памяти. В таком случае в программе также можно предусмотреть специальный процесс, который будет возбужден операционной си-, стемой в случае прерывания по защите памяти. Этот специальный процесс может установить, при каком обращении к памяти про- изошло прерывание — если это случилось именно при работе с бу- 18*
548 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 фером, то можно просто запросить у ОС дополнительную страницу и продолжить решение задачи. Если же это прерывание произошло в непредусмотренном месте, то специальный процесс расценит воз- никшую ситуацию как следствие ошибки в программе и прекратит ее выполнение. Чтобы специальный процесс мог выполнить свои функции, ему придется анализировать состояние других процессов в момент воз- никновения данной ситуации, в частности — выяснять, при выпол- нении какой именно команды возникла эта ситуация. Поэтому для каждого такого специального процесса ОС выделяет свой виртуаль- ный процессор. При этом она может обеспечить приостановку ра- боты всех остальных виртуальных процессоров и тем самым сделать их доступными для анализа в специальном процессе. 10.3. Управление взаимодействием задач Если основной заботой диспетчера было полное разделение одновременно решаемых задач, т. е. полная изоляция виртуальных машин, одновременно существующих в одной физической машине, то операционные системы идут по пути постепенного налаживания связей между ними. Сначала это была связь задач через архив по входным и выходным данным: файл, полученный в результате ре- шения задачи А, мог использоваться в качестве исходных данных в задаче В, так что задачу В можно рассматривать как продолже- ние задачи А. Затем системы стали обеспечивать передачу данных от одной задачи к другой непосредственно через оперативную па- мять: задача А может вызвать в решение задачу В, разрешив ей использовать определенную часть своей памяти, через которую и производится обмен данными между задачами. Современные опе- рационные системы идут в этом направлении значительно дальше: ОС может рассматривать все находящиеся в ее поле зрения задачи (в том числе и те, которые еще не решаются) как единый коллектив задач, отдельные члены которого могут взаимодействовать друг с другом и устанавливать между собой различные типы связей. При этом можно выделить три наиболее характерные вида взаимо- действия задач: Г. Следование задач; это простейший вид взаимодейст- вия, при котором одна задача является непосредственным продол- жением предыдущей. 2°. Одна задача использует другую для выполнения стандарт- ных вычислений, т. е. обращается к ней как к процедуре. При этом обратившаяся задач'а связана с задачей-процедурой толь- ко аргументами и результатами, так что обратившейся задачи не должно касаться, какие ресурсы потребуются для выполнения процедуры, и в частности — какие для этого потребуются вспомо- гательные процедуры. Таким образом, вызванная задача-процедура
10.3) УПРАВЛЕНИЕ ВЗАИМОДЕЙСТВИЕМ ЗАДАЧ 549 может быть как достаточно простой (типа вычисления z/=sin(x)), так и достаточно сложной (например, транслятор, аргументом ко- торого является программа, написанная на исходном языке, а ре- зультатом— оттранслированная программа). Наличие в ОС таких возможностей позволяет, в частности, сложную проблему разби- вать на подпроблемы и каждую из них программировать незави- симо от того, как будут решаться остальные. Программирование каждой из этих подпроблем, в свою очередь, может быть сведено к независимому программированию еще более простых подпроб- лем и т. д. 3°. Одна задача выступает для другой (или других) в роли опе- рационной подсистемы: задачи, выполняемые под управлением этой подсистемы, пользуются ее услугами наряду с услугами основной ОС или вместо них. Такое взаимодействие задач позволяет создавать специализированные операционные под- системы, которые обеспечивают пользователям дополнительные удобства без заметного усложнения основной ОС, за счет предостав- ления пользователям дополнительных директив, выполняемых этими подсистемами. Степень близости задач определяется по их собственному усмот- рению и может варьироваться, вообще говоря, в достаточно широком диапазоне. Полная изоляция какой-либо задачи от всех остальных может рассматриваться как частный случай (минимальная бли- зость). Максимальная близость приводит к тому, что две задачи могут иметь полностью общую оперативную и вспомогательную память, т. е. фактически к тому, что в одной и той же памяти будут работать две группы виртуальных процессоров. 10.3.1. Множество задач. Обычно в поле зрения ОС находятся три группы задач. 1°. Смесь. Сюда входят задачи, которые уже выполняются опе- рационной системой: им выделены виртуальные машины, отобра- женные на физическую машину, и между ними делится время физи- ческого процессора. 2°. Пакет. Это задачи, которые введены в систему для решения, но которые еще не попали в смесь и ждут своей очереди. 3°. Задачи архива. Эту группу составляют задачи, паспорта и программы которых записаны в системный архив и которые доступ- ны для использования другими задачами. Некоторые задачи пакета являются самостоятельными (о чем в их паспортах имеется соответствующая отметка). Такие задачи автоматически включаются операционной системой в смесь по мере освобождения нужного им количества ресурсов и с учетом их при- оритета. Остальные задачи из пакета и любые задачи из архива могут попасть в смесь только в результате выполнения директивы, по ко-
550 ОПЕРАЦИОННЫЕ СИСТЕМЫ (Гл. 10 торой одна из задач смеси вызывает эту задачу для совместной рабо- ты или по иной заявке пользователя для выполнения очередного этапа нужной ему работы. Операционная система предоставляет в распоряжение пользо- вателя определенные возможности (в том числе и ряд специальных директив) для организации необходимого взаимодействия задач. 10.3.2. Последовательное взаимодействие. На практике особенно часто встречается простейший случай вза- имодействия задач, когда назначение каждой из них состоит в вы- полнении очередного — достаточно крупного и автономного — этапа решения некоторой более общей задачи. Например, редактирование текста модуля, написанного на ка- ком-либо алгоритмическом языке, с целью внесения в него необ- ходимых изменений, может представлять собой самостоятельную задачу, которую пользователь хочет решить на ЭВМ с использова- нием соответствующей программы — редактора текстов, если его целью является получение исправленного текста данного модуля. Точно так же трансляция модуля, написанного на одном из ал- горитмических языков, которая выполняется с помощью соответ- ствующего транслятора, может являться самостоятельной задачей, если пользователь ставит своей целью только получить нужный ему загрузочный модуль. В других же случаях каждая из этих двух задач является частью более общей задачи, решая которую пользователь преследует цель получить загрузочный модуль с предварительным внесением не- которых изменений в текст транслируемого модуля. Нетрудно представить себе и такие задачи, решение которых рас- падается на целый ряд этапов, каждый из которых представляет собой некоторую самостоятельную задачу, решаемую по опреде- ленной программе, например: «редактирование текста модуля А — трансляция модуля А — трансляция модуля В — редактирование текста модуля С — трансляция модуля С — загрузка модулей Л, В, С — выполнение полученной программы». В подобного рода случаях решение на ЭВМ некоторой задачи сводится к последовательному решению нескольких самостоятель- ных частных задач, причем в набор исходных данных для каждой частной задачи, решаемой на очередном этапе, могут входить резуль- таты решения предыдущих частных задач. Организация взаимодействия задач в таких случаях сводится к тому, чтобы по окончании решения одной из них обеспечить ввод в решение нужной следующей задачи, а также обеспечить нужное согласование последовательно решаемых задач по исходным данным и получаемым результатам. Поскольку каждая из таких задач мо- жет фигурировать либо в качестве совершенно самостоятельной за- дачи, либо в качестве одной из взаимодействующих задач, то ор-
10.31 УПРАВЛЕНИЕ ВЗАИМОДЕЙСТВИЕМ ЗАДАЧ ' 551 ганизация их взаимодействия с помощью директив, включаемых в программу решения соответствующей задачи, привела бы к нео& ходимости вносить изменения в эту программу при различных ее использованиях. Очевидно, что такой способ неудобен, особенно для системных программ, постоянно хранимых в архиве и доступ* ных для общего пользования (трансляторы, редакторы текстов и т. Д-). В связи с этим подавляющее большинство операционных си- стем предоставляет в распоряжение пользователей аппарат для ор- ганизации такого взаимодействия в виде специального языка управ- ления заданиями, на котором пользователь формулирует операци- онной системе задание на выполнение с помощью ЭВМ нужной ему работы. 10.3.2.1. Язык управления заданиями. В языке управления зада- ниями имеется несколько типов управляющих операторов, каждый из которых имеет определенное назначение. С помощью этих-уп- равляющих операторов и производится описание работы, предъяв- ляемой вычислительной системе для выполнения. При этом пред- полагается, что работа в общем случае состоит из нескольких эта- пов, каждый из которых определяется своим шагом задания. Каж- дый шаг задания, как правило, состоит из управляющих операторов, которые содержат информацию о том, какую программу надо выпол- нить на данном этапе, и описывают входные и выходные наборы дан- ных для этой программы, т. е. указывают, откуда надо взять исход- ные данные и что следует делать с получаемыми результатами (вы- вести их на печать, на перфорацию, запомнить во внешней памяти и т. Д-). Наряду с управляющими операторами в шаге задания могут в явном виде присутствовать и сами исходные данные (или их часть). Управляющие операторы также наносятся на внешние носители данных, например на перфокарты. При этом синтаксис и форма представления управляющих операторов на перфокартах выбирают- ся так, чтобы ОС могла отличить их от остальных данных. Упорядоченная последовательность шагов заданий и является заданием, которое пользователь предъявляет ОС для исполнения. Таким образом, задание — это тоже своего рода программа, но в отличие от обычных программ она адресуется для исполнения не непосредственно процессору ЭВМ, а ее операционной системе, и поэтому записывается не на машинном языке, а на языке управле- ния заданиями, который понятен ОС, так что ОС может восприни- мать фразы на этом языке и соответствующим образом их интерпре- тировать с использованием ЭВМ. Шаги задания выполняются операционной системой в порядке их следования в задании. Поскольку каждый шаг задания начи- нается с управляющего оператора, который задает программу, подлежащую выполнению на этом шаге, то последовательность ша- гов заданий и определяет последовательность решения взаимодейст- вующих задач. Для того чтобы эти задачи представляли собой от-
552 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 дельные этапы решения какой-то более общей задачи, они должны быть согласованы по своим входным и выходным наборам данных — с тем чтобы в качестве входных наборов данных одной задачи можно было брать выходные наборы данных одной или нескольких пре- дыдущих задач, благодаря чему и осуществляется их взаимодей- ствие. Для этой цели используются файлы. Как уже отмечалось при рассмотрении виртуальной внешней памяти, файл отражает лишь логическую структуру представляе- мого им набора данных, не фиксируя расположение этого набора на том или ином носителе данных (на перфокартах, на тех или иных устройствах внешней памяти и т. д.). Эта конкретизация размеще- ния файлов может быть сделана вне программы, так что изменение расположения того или иного файла, используемого в программе, не влечет за собой ее изменения. Для конкретизация размещения используемых в задачах файлов в языке управления заданиями предусматривается специальный тип управляющих операторов — оператор описания данных. Каж- дый шаг задания — наряду с оператором, указывающим подлежа- щую выполнению программу — содержит и операторы описания дан- ных, с помощью которых конкретизируются все файлы, использу- емые в этой программе. Путем соответствующего описания файлов в разных шагах задания и можно обеспечить взаимодействие задач по входным и выходным данным. Пусть, например, в задаче А файл ФА представляет выходной набор данных (т. е. результаты ее решения). Если задача А исполь- зуется как самостоятельная, то в управляющем операторе, описы- вающем файл ФА, можно задать его размещение на рулоне бумаги, и тогда ОС обеспечит вывод результатов решения задачи А на пе- чать. Теперь допустим, что в задаче В файл ФВ представляет вход- ной набор данных (т. е. исходные данные). Если нужно, чтобы за- дачи Ли В решались последовательно, причем в качестве исходных данных для задачи В надо взять результаты решения задачи А, то достаточно в операторе описания файла ФА задать его разме- щение во внешней памяти, а в операторе описания файла ФВ в задаче В отождествить его с файлом ФА (один из таких способов отождествления может состоять в том, что для файла ФВ зада- ется точно такое же размещение, которое было задано для файла ФА). Подобно тому как разные ЭВМ имеют разные машинные языки, различные ОС также имеют различные языки управления заданиями. Однако в этих языках (как и в машинных языках) есть много об- щего, поэтому в качестве иллюстрации мы остановимся на достаточ- но типичном языке управления заданиями в операционной системе машин серии ЕС ЭВМ.* В этом языке большинство управляющих операторов имеют сле- дующий формат:
10.3] УПРАВЛЕНИЕ ВЗАИМОДЕЙСТВИЕМ ЗАДАЧ 553 //< имя>—<операция>_- <список параметров^ <комментарий Косые черты //, с которых начинается управляющий оператор, перфорируются в первых двух колонках перфокарты, что и является признаком управляющего оператора. (имя) — это, как правило, идентификатор, выполняющий роль наименования (метки) данного оператора. Как обычно, это имя используется для ссылок на данный оператор; такая необходи- мость возникает и в языке управления заданиями — например, для того чтобы в других операторах можно было сослаться на ин- формацию, заданную в этом операторе, а не повторять ее заново. (операция) — это одно из служебных слов, зафиксированных в языке, которое и определяет содержание данного оператора (т. е. название операции, которую должна выполнить ОС). (список параметров) — служит для конкретизации той опе- рации, которая задана в этом операторе. При этом ОС ЕС ЭВМ до- пускает использование как позиционных, так и ключевых парамет- ров, а каждый параметр может состоять из нескольких подпарамет- ров. В языке управления заданиями широко применяется принцип «умолчания»: если какой-либо параметр не задается в явном виде, то ему приписывается некоторое стандартное значение. (комментарий) — это необязательная последовательность сим? волов, которая не влияет на характер выполнения задания, а ис- пользуется главным образом для целей документирования. В некоторых управляющих операторах отдельные поля формата могут быть пустыми. Наиболее употребительными являются управляющие операторы с операциями JOB (задание), EXEC (execute — выполнить), DD (data definition — определение данных), а также пустой оператор. Чтобы уменьшить число управляющих операторов в задании, пре- дусматривается возможность использования управляющих процедур, т. е. таких управляющих «макрооператоров», с помощью которых можно вызвать по имени некоторую стандартную последовательность управляющих операторов — такие стандартные последовательно- сти могут постоянно храниться в системе (они называются «ката- логизированными процедурами»). Например, для выполнения про- граммы, написанной на алголе, необходимо использовать управляю- щие операторы, по которым ОС последовательно обеспечит трансля- цию исходной программы, установление связей с библиотечными подпрограммами, реализующими используемые в программе про- цедуры, загрузку программы и, наконец, ее выполнение. Всю эту последовательность действий можно задать ОС с помощью одного управляющего оператора типа ЕХЕС, указав в нем в качестве опе- рации имя соответствующей управляющей процедуры. Рассмотрим кратко назначение упомянутых выше основных уп- равляющих операторов и задаваемую в них информацию. .
554 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 Оператор JOB означает начало задания и по сути дела является паспортом задачи. Имя этого оператора играет роль и имени зада* ния. В поле списка параметров этого оператора можно задать 2 по- зиционных и 11 ключевых параметров, причем все они являются необязательными (если какой-либо параметр не задан, то ему по умолчанию придается некоторое стандартное значение). Позиционные параметры (учетная информация и автор задания) обязательны лишь в том случае, если в ОС включена специальная программа обработки учетной информации — в этом случае адми- нистрация устанавливает и правила записи этих параметров. Из ключевых параметров наиболее часто используются два: MSGLEVEL и TIME. Параметр MSGLEVEL (Message Level — уровень сообщений) определяет уровень подробности сообщений системы в процессе выполнения задания. Этот параметр задается в виде MSGLEVEL—(p, q) Подпараметр р(р=0,1,2) определяет распечатку управляющих опе- раторов задания по мере их выполнения системой: при р=0 печата- ется только оператор JOB', при р—\ печатаются все управляющие операторы, в том числе и операторы, определяемые процедурами, а при р=2 печатаются только операторы, указанные в задании в явном виде. Значение подпараметра <7=1 предписывает отпечатать сообщение о распределении устройств для размещения наборов дан- ных (файлов), используемых в программе, а значение q=0 говорит о том, что это сообщение печатать не нужно. Параметр TIME, задаваемый в виде TIME—(и, о), указывает верхний предел времени (и мин. осек.) процессора, запрашиваемого на выполнение задания. По истечении этого времени ОС снимает задание с выполнения, что позволяет избежать напрасных затрат машинного времени в случае зацикливания какой-либо программы, используемой в задании. С помощью других ключевых параметров, например REGION (область), C0ND (условие), PRTY (приоритет), задаются требуе- мые ресурсы оперативной памяти, условия прекращения выполне- ния задания, его приоритет и т. д. Пример оператора JOB'. ([PROBLEM JOB PETROV MSGLEVEL=(2,0), TIME=(l,30) Этот оператор означает начало задания с именем PROBLEM, ав- тором которого является Петров (в данном языке управления зада- ния русские буквы могут использоваться только в комментариях). Указано, что на выполнение задания не должно расходоваться более полутора минут процессорного времени, что при выполнении зада- ния ОС должна распечатывать только управляющие операторы, содержащиеся в задании в явном виде, и что пользователя не инте-
10.3] УПРАВЛЕНИЕ ВЗАИМОДЕЙСТВИЕМ ЗАДАЧ 555 ресуют сообщения о том, на каких именно устройствах будут раз- мещены наборы данных, используемые в задании. Оператор ЕХЕС означает начало очередного шага задания. Ос- новное назначение этого оператора — указать программу, подле- жащую выполнению на данном этапе, либо управляющую процеду- ру, которая определит одну или несколько таких программ, под- лежащих выполнению. Имя оператора играет роль имени шага за- дания. Это имя используется, в частности, в системных сообщениях о ходе выполнения задания. В списке параметров можно указать один позиционный и 8 клю- чевых параметров, причем позиционный параметр является обяза- тельным — именно он и задает программу или процедуру, подле- жащую выполнению на данном шаге задания. Позиционный параметр имеет две основные формы: Р0А1=имя программы Р7?0С=имя процедуры Заметим, что по форме записи этот параметр напоминает ключевой, хотя он является позиционным и в списке параметров должен быть указан первым. Такая форма выбрана в связи с тем, что имя про- граммы и имя процедуры синтаксически определяются одинаково, поэтому служебные слова PGM и PROC указывают, о каком из них идет речь. При этом в первом случае имеется в виду программа, хранящаяся в системной или личной библиотеке (причем программа с указанным именем ищется сначала в личной библиотеке, а если ее там не оказывается, то поиск продолжается в системной библио- теке). Одна из форм этого параметра позволяет удобно использо- вать программы, получаемые в этом же задании, например в резуль- тате работы транслятора. В этом случае программе можно не прис- ваивать определенного имени, а для ее указания можно просто со- слаться на тот оператор DD, в котором описан файл, представляю- щий собой нужную программу — например, файл, в который транс- лятор помещает выработанную им программу. Таким образом, вместо имени программы в первой из указанных выше форм может указываться ссылка на соответствующий оператор DD. Ряд ключевых параметров (TIME, REGION, C0ND) имеют тот же смысл, что и в операторе JOB, но относятся к данному шагу. Пара- метр PARM служит для передачи выполняемой на этом шаге про- грамме управляющей информации. Эта информация специфична для каждой программы — ее состав и назначение определяются автором программы. Для системных программ (трансляторов, загрузчиков и т. д.) этот состав зафиксирован. Например, для транслятора с ал- гола с помощью этого параметра (состоящего из нескольких подпара- метров) можно задать указания о том, какое представление чисел (в виде коротких или длинных слов) должно быть предусмотрено в оттранслированной программе; перфорировать или нет эту про- грамму; печатать или нет текст исходной программы и т. д.
656 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 Примеры операторов ЕХЕС-. 1/TRANSL EXEC PGM=ALG0L, PARM=(SHORT, SOUR СЕ, NOTEST) J/STEP2 EXEC PROC=FORTGC Первый из этих операторов открывает шаг задания с именем TRANSL, на котором должна быть выполнена программа с именем ALGOL (транслятор с алгола); ключевой параметр PARM с помощью подпараметров задает режимы работы этого транслятора: предус- мотреть представление чисел в виде коротких слов (SHORT)-, вы- вести на печать текст исходной программы (SOURCE)-, в вырабатыва- емой программе не предусматривать контроля за выходом индексов за допустимые границы (NOTEST). Остальным параметрам будут приданы стандартные значения. Второй оператор открывает шаг задания с именем STEP2, со- держание которого определяется процедурой F0RTGC. Эта ката- логизированная процедура порождает несколько управляющих операторов, в том числе один оператор ЕХЕС, задающий выполне- ние стандартного компилятора с фортрана (без оптимизации), и управляющие операторы, которые определяют некоторые из вы- ходных наборов данных этого транслятора. Оператор DD служит для описания файлов (входных, выходных и промежуточных наборов данных), которые используются в про- грамме (в программах), заданной оператором ЕХЕС. Например исходная программа, написанная на каком-либо язы- ке программирования, является входным набором данных для соот- ветствующего транслятора; одним из выходных наборов данных это- го транслятора является оттранслированная программа; другим выходным набором данных для транслятора является листинг и диагностические сообщения; при своей работе транслятор обычно использует промежуточные наборы данных — например формиру- емые им таблицы идентификаторов. Чтобы упростить дальнейшее использование каждой из состав- ленных программ, в принятой на машинах ЕС ЭВМ системе програм- мирования в программах не фиксируется не только место, где раз- мещаются используемые в них файлы (на перфокартах, во внешней памяти и где именно, выдаются на печать), но и некоторые их харак- теристики, например длина записей, образующих этот файл. Назначение операторов DD как раз и состоит в том, чтобы кон- кретизировать эти файлы: указать их конкретное размещение и, возможно, уточнить некоторые их характеристики, которые не были заданы в программе. Такое разделение описания файлов между программой и зада- нием обеспечивает независимость программы не только от места, но и от некоторых характеристик используемых в ней файлов. Бла- годаря этому обеспечивается большая гибкость в использовании
10.3] УПРАВЛЕНИЕ ВЗАИМОДЕЙСТВИЕМ ЗАДАЧ 557 составленных программ и, в частности, простота настройки про- граммы, выполняемой на очередном шаге задания, на работу с дан- ными, являющимися результатами выполнения программ на пре- дыдущих шагах. Каждый из операторов DD описывает один из файлов, использу- емых в программе. Следует отметить, что в принятой на машинах ЕС ЭВМ системе программирования, в программе даже ссылка на используемый файл производится косвенно — с помощью указа- ния имени того оператора DD (так называемое dd-имя), в котором должен быть описан этот файл. Если программа пишется на автоко- де, то эти dd-имена выбираются автором программы по своему ус- мотрению; если же программа составляется транслятором (с ал- гола или фортрана), то в каждой вырабатываемой программе он использует одни и те же dd-имена, которые известны пользователям (точно так же фиксированы и dd-имена, с помощью которых сам транслятор ссылается на используемые в нем файлы). Файл, описываемый в каком-либо операторе DD, может разме- щаться во входном потоке данных для системы, в выходном ее по- токе или во внешней памяти. Во входном потоке могут находиться только входные файлы — в этом случае они вводятся через устройство системного ввода (как правило, с перфокарт), через которое вводятся и все задания си- стеме. Обычно через входной поток вводятся такие исходные дан- ные для программы, которые не являются результатами выполне- ния предыдущих программ. В выходной поток обычно помещают такие наборы данных, кото- рые не будут использоваться в других программах или в других заданиях (окончательные результаты решения какой-либо самостоя- тельной задачи, диагностические сообщения трансляторов, сооб- щения операционной системы о ходе выполнения задания и т. д.) — эти данные выводятся через устройства системного вывода, общие для всех задач. В качестве системного вывода обычно используется вывод на печать. Во внешней памяти могут размещаться любые файлы. В частно- сти, во внешней памяти удобно размещать те выходные наборы дан- ных, полученные в результате выполнения какой-либо программы, которые должны использоваться в качестве входных наборов данных в последующих шагах задания. Оператор DD имеет один позиционный параметр, который яв- ляется необязательным. Из трех возможных значений этого пара- метра наиболее часто используется значение *. Если оператор DD описывает входной файл, то значение » позиционного параметра говорит о том, что этот файл находится во входном потоке (на пер- фокартах, непосредственно за этим оператором DD). Конец такого файла во входном потоке отмечается оператором ограничения /». Отсутствие позиционного параметра в этом случае означает, что
Б58 ОПЕРАЦИОННЫЕ СИСТЕМЫ / [ГлЛО / входной файл размещается во внешней памяти, а его место в этой памяти конкретизируется с помощью ключевых параметров. Если между программами, выполняемыми на разных этапах за- дания (или даже в разных заданиях), должно осуществляться вза- имодействие типа следования, то файлы, но которым осуществляется связь программ, удобно размещать во Внешней памяти. Для упрощения организации такого взаимодействия предусмот- рена возможность вместо полного описания какого-либо файла в опе- раторе DD указать просто ссылку на описание, приведенное в од- -ном из операторов DD какого-либо предыдущего шага. Благодаря этому входной файл одной программы можно отождествить с выход- ным файлом выполненной ранее программы и тем самым обеспечить взаимодействие программ указанного типа. В качестве иллюстрации использования языка управления зада- ниями и организации взаимодействия задач типа следования рас- смотрим два примера. Первый из них иллюстрирует использование языка для решения отдельной задачи, а второй — для организации взаимодействия задач. Пример 10.1. Составить программу на алголе для получения последовательных целых чисел вида i3 для Г— 1,2,. . ., N. Составлен- ную программу оттранслировать и затем выполнить для 7V=2O. Для решения этой задачи можно составить следующее задание. Как обычно, задание должно начинаться оператором JOB: //TABL JOB PETROV MSGLEVEL=(2,0), TIME=(0,30) Этот оператор означает начало задания с именем ТABL, автором ко- торого является Петров и на выполнение которого запрашивается 30 сек процессорного времени. Для выполнения нашей работы на самом деле нужно использо- вать несколько программ — транслятор, редактор связей, загруз- чик и программу, которую мы должны составить на алголе. Для последовательного включения в работу упомянутых программ мож- но воспользоваться управляющей процедурой ALGOFCLG, кото- рая постоянно хранится в системе и зафиксирована в ее каталоге («каталогизированная» процедура). Для обращения к ней зададим оператор EX.EG, открывающий первый шаг задания (дадим ему имя TASK): //TASK EXEC PROC=ALGOFCLG При выполнении этого шага задания будет использоваться не- сколько файлов (даже довольно много). Некоторые из них (выход- ные и промежуточные файлы транслятора и др.) некоторым стандарт- ным образом описаны в самой управляющей процедуре с помощью соответствующих операторов DD. Некоторые же файлы, отражаю- щие специфику конкретного задания, должны быть описаны в за- дании в явном виде. Один из таких файлов с dd-именем ALGOL.
10.3] УПРАВЛЕНИЕ.ВЗАИМОДЕЙСТВИЕМ ЗАДАЧ 559 SYSIN представляв^ текст алгол-программы, подлежащей транс-' ляции — этот текст мот быть предварительно уже записан во внеш- нюю память, а может вводиться с перфокарт. Так же обстоит дело и с файлом с dd-именем GO. SYS Z N, который представляет собой исходные данные для оттранслированной программы. Что каса- ется выходного файла этой программы (выводимых ею результа- тов), которому транслятор приписывает dd-имя GO.ALGLDD01> то в управляющей процедуре дано такое его описание, которое пред- писывает вывести этот Набор данных на печать. Такое стандартное описание нас устраивает, поэтому можно не описывать повторно этот файл (заметим, что если дано два описания какого-либо файла, то учитывается то, которое дано позже).„ Мы будем исходить из того, что наша алгол-программа должна вводиться с перфокарт, поэтому соответствующий ей файл опишем таким оператором DD: //ALGOL. SYS/N DD * Как уже отмечалось выше, в этом случае текст алгол-программы на- до поместить во входном потоке вслед за данным оператором DD. Текст программы мы запишем на стандартном алголе, считая, что он дополнен процедурами ввода и вывода, обращение к которым производится операторами вида ввод (k, А, В, . . .) и вывод (k, А, В, ...). Поскольку ОС ЕС предоставляет пользователям не- сколько виртуальных устройств ввода и вывода, то первый пара- метр задает номер устройства (будем считать, что при вводе Л=0 задает устройство системного ввода с перфокарт, а при выводе k—\ задает устройство системного вывода на печать). В качестве осталь- ных фактических параметров могут использоваться простые пере- менные и идентификаторы массивов. Итак, вслед за приведенным выше оператором DD поместим текст: begin integer АГ; ввод (0, N\, begin integer array A (1 :AH; integer i; for i: = l step 1 until N do вывод (1, A) end end /* (вслед за текстом программы записан управляющий оператор, яв- ляющийся признаком конца этого текста) Будем исходить из того, что свои исходные данные (значение N) наша программа должна вводить с перфокарт, поэтому файл с dd- именем GO.SYSIN опишем следующим образом: //GO.SYSIN DD» Вслед за этим оператором разместим исходные данные с признаком их конца: 20
660 ОПЕРАЦИОННЫЕ СИСТЕМЫ J [Гл. 10 В данном случае все задание состоит из одногс/шага, поэтому даль- ше надо поместить пустой оператор / // который означает конец задания. / Пример 10.2. Пусть имеются две алгол-программы (назовем их TABL и SUM), нанесенные на перфокарты. Программа TABL по задаваемой последовательности из трех чисел {п, а, Ь}, образующей входной файл (п — целое, а и b — ве- щественные), получает последовательность значений {п, y0~f(x0), yi=f(xt), ..., yn=f(xn)}, образующих ее выходной файл, где xt= a-Hxh(i=0, h—(b—a)/n, a f(x) — некоторая функция. Программа SUM по задаваемой последовательности значений {т, х0, Xi....хт}, являющейся входным файлом, получает зна- т чение г = S */» являющееся выходным файлом. /=0 Пусть в архиве системы имеется наша личная библиотека с име- нем BIBL, которая каталогизирована в системе (т. е. все сведения об этой библиотеке занесены в каталог системы). Теперь допустим, что с помощью ЭВМ нужно выполнить следую- щую работу. 1°. Текст алгол-программы TABL записать под этим именем в биб- лиотеку BIBL для ее хранения и последующего использования. 2°. Оттранслировать и выполнить программу TABL для п=20, а=0, Ь=1. 3°. Оттранслировать и выполнить программу SUM, в качестве исходных данных для которой принять результаты, полученные по программе TABL. Таким образом, транслятор, используемый на этапе 2°, в каче- стве своих исходных данных (подлежащий трансляции текст) дол- жен принять, результат выполнения этапа Г (текст алгол-програм- мы TABL, записанный в библиотеку), который выполняется соот- ветствующей обслуживающей программой системы, а программа SUM, выполняемая на этапе 3°, должна быть применена к резуль- татам, полученным на предыдущем этапе по программе TABL. Как видно, здесь имеет место типичный случай взаимодействия за- дач типа следования. Для выполнения этой работы составим задание. Как обычно, оно должно начинаться управляющим оператором JOB-. HEXEMPL JOB PETROV, MSGLEVEL=(2,0), TIME=(3,0) Действия над библиотеками выполняются с помощью системной программы с именем 1EBUPDTE. В инструкции пользования этой программой сказано, какая информация должна быть за- дана при обращении к ней, и перечислены используемые в этой программе файлы с указанием соответствующих им dd-имен. В ча-
10.3] УПРАВЛЕНИЕ ВЗАИМОДЕЙСТВИЕМ ЗАДАЧ 56] стности, в onepatope\EXEC, который вызывает в работу данную программу, с помощькУключевого параметра PARM надо указать, создается ли библиотека заново (NEW) или она уже существует (OLD). По нашему предположению, библиотека BIBL уже сущест- вует (и каталогизирована) в системе. Поэтому оператор ЕХЕС, открывающий первый шаг задания, зададим в виде: //STEP\ EXEC PGM=IEBUPDTE, PARM=OLD Указанная здесь программа использует следующие файлы: выходной файл с dd-именем SYSPRINT — диагностические сооб- щения, выдаваемые данной программой; выходной файл с dd-име- нем SYSOUT2 — записываемый в библиотеку текст; входной файл с dd-именем SYSIN. Этот последний файл состоит из дополнитель- ных управляющих операторов, адресованных этой подпрограмме (а не ОС), и текста, который должен быть записан в библиотеку. Все эти файлы надо описать в задании с помощью операторов DD. Диагностические сообщения естественно вывести на печать — для этого в операторе DD в качестве значения ключевого параметра SYSOUT надо задать букву А, что и означает печать на АЦПУ: //SYSPRINT DD SYSOUT=A При описании файла с dd-именем SYSOUT2 в соответствующем операторе DD надо указать имя используемой библиотеки (BIBL) и ее «статус»: создается ли она заново (NEW) или эта библиотека уже существует (OLD). По нашему предположению библиотека BIBL уже существует: // SYSOUT2 DD DSN—BIBL, DISP=OLD Все исходные данные для программы IEBUPDTE (входной файл с dd-именем SYS IN) зададим на перфокартах во входном потоке: //SYSIN DD * Как уже отмечалось, в этот файл входят управляющие операторы, адресованные не ОС, а данной программе; чтобы ОС могла разли- чать такие операторы, они начинаются парой символов ./. Для за- писи текста в библиотеку используется оператор ADD, в котором надо указать имя раздела, который и представляет собой записан- ный текст (имя библиотеки уже было задано раньше): ./ ADD NAME=TABL Затем с помощью оператора NUMBER надо задать способ нумера- ции строк в записываемом тексте: параметр NEW 1 определяет на- чальный номер, а параметр INCR — шаг нумерации. Мы попросим перенумеровать строки с шагом 10, начиная с номера 5: ./ NUMBER NEW 1=5, INCR=IO. Далее во входном потоке надо поместить текст программы TABL (в которой ради простоты возьмем f(x)=x*): begin integer n; real a, b; ввод (0, n, a, b); begin array гДО:п]; integer i; real h, x; h: =(b—a)/n\ X'.=a\ for t:=0 step 1 until n do
662 ОПЕРАЦИОННЫЕ СИСТЕМЫ / [Гл. 10 / ' begin ^[i]:=xf2; xt=x+h end; вывод (1, n, у) end end /* Итак, первый шаг задания сформулирован. Для выполнения второго этапа работы зададим управляющий оператор ЕХЕС, указав в нем уже знакомую нам по -предыдущему примеру управляющую процедуру ALG0FCLG-. USTEP2 ЕХЕС PROC=ALGOFCLG В этом шаге задания надо описать файлы с dd-именами ALGOL. SYSIN (входной файл для транслятора), GO. SYSIN (исходныё данные для программы TABL), а также файл с dd-именем GO. ALGLDD01 — результаты, выводимые программой TABL. В от- личие от предыдущего примера стандартное описание этого файла, содержащееся в используемой процедуре и предусматривающее вывод этих результатов на печать, в данном случае нас не устраива- ет, поскольку мы хотим передать этот файл для использования программе SUM. В качестве входного файла для транслятора надо взять текст программы TABL, поэтому с помощью параметра DSN в операторе DD отождествим этот файл с разделом TABL в библиотеке BIBL (по правилам задания этого параметра надо указать имя библио- теки-и взятое в круглые скобки имя раздела). Кроме того, с помощью параметра DISP надо указать статут этого раздела — старый или новый: UALGOL. SYSIN DD DSN—B1BL (TABL), DISP=0LD Исходные данные для программы TABL зададим на перфокартах во входном потоке: l/GO.SYSIN DD* 20, 0.0, 1.0 /• Результаты, выводимые программой TABL для передачи их прог- рамме SUM, представим в виде файла с именем &SET (первый сим- вол & этого имени для ОС является признаком того, что этот файл временный — его нужно хранить только на время выполнения дан- ного задания) и разместим его во внешней памяти. При этом надо указать» что этот временный файл создается заново (NEW) и что его надо передать следующим шагам задания (PXSS). Укажем также, что мы хотим разместить его на устройствах прямого досту- па, т. е. на барабан или диск (SYSDA), и что для его размещения достаточно отвести один трак (единица объема памяти): //00. ALGLDD01 DD DSN=&SET, DISP=(NEW, PASS), UNIT—SYSDA, SPACE (TRK, 1) Для выполнения третьего этапа нашей работы опять обратимся к процедуре ALGOFCLG-. USTEP3 ЕХЕС PROC—ALGOFCLG
10.31 УПРАВЛЕНИЕ ВЗАИМОДЕЙСТВИЕМ ЗАДАЧ 563 Входной файл для' транслятора (текст алгол-программы SUM) зададим во входном потоке: //ALGOL.SYSIN DD » begin integer т, ввод (0, m); begin array x[0:m]; real r; integer i; ввод (0, x); r.—O; for i:=Ostep 1 until m do r:=r-bxU]; вывод (1, r) end end /* Чтобы программа SUM в качестве своих исходных данных исполь- зовала результаты, полученные по программе TABL, надо ее вход- ной файл с dd-именем GO.SYSIN отождествить с файлом &SET, помещенным во внешнюю память на предыдущем этапе. Для этого его надо описать точно так же, как и файл &.SET, а можно поступить проще: в операторе DD сказать, что это тот же самый файл, кото- рый был описан в данном задании (*), в шаге задания с именем STEP2, в операторе DD с именем GO.ALGLDDQX: //GO. SYSIN DD DSN==».STEP2.GO.ALGLDDQl, DISP^ OLD Результат, получаемый по программе SUM, мы хотим вывести на печать, так что нас устраивает стандартное описание выходного файла этой программы, данное в управляющей процедуре, и потому этот файл можно не описывать. Итак, окончательно наше задание примет вид: HEXEMPL JOB PETROV, MSGLEVEL=(2,0),TIME=(3,0) //STEPl EXEC PGM=IEBUPDTE, PARM=OLD //SYSPRINT DD SYSOUT=A //SYSOUT2 DD DSN=BI BL, DISP=OLD //SYSIN DD * ./ ADD NAME=TABL 4 NUMBER NEW\=b, INCR=\0 Текст алгол-программы TABL /• //STEP2 EXEC PROC=ALGOFCLG //ALGOL. SYSIN DD DSN=BIBL (TABL), DISP==0U> //GO.SYSIN DD » 20,0.0,1.0 /* //GO.ALGLDDOX DD DSN=&SET, DISP=(NEW, PASS), UNIT=SYSDA, SPACE (TRK,\) //STEPS EXEC PROC=ALGOFCLG ЦALGOL. SY SIN DD *_________________ Текст алгол-программы SUM
564 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 //GO. SYS IN DD DSN=».STEP2.GO.ALGLDDO\, DISP= OLD п Последний (пустой) управляющий оператор означает конец задания. 10.3.3. Параллельное взаимодействие. Связь между задачами, взаимодействующими по типу следова- ния, довольно ограничена: она осуществляется только на уровне «сходных данных и окончательных результатов каждой из них, представленных в том или ином файле. Существенно более широкие возможности открываются в том случае, когда взаимодействующие задачи выполняются параллель- но, т. е. когда соответствующие виртуальные машины существуют •одновременно. При этом, как и в предыдущем случае, между зада- чами возможен обмен данными, но теперь можно обмениваться и промежуточными результатами, что открывает дополнительные воз- можности. Кроме того, задачи могут синхронизировать свою работу, обмениваться сообщениями и даже оказывать друг другу услуги в организации управления процессами. Только при параллельном выполнении возможно использование одной задачи в качестве обоб- щенной процедуры или как операционной подсистемы для другой задачи или для нескольких других задач. Синхронизация параллельно выполняемых задач может быть осуществлена при помощи системных семафоров, о которых мы гово- рили при рассмотрении параллельных процессов. Нужно только, чтобы операционная система умела организовывать доступ к не- скольким семафорам из различных задач, что не представляет осо- бых затруднений. Остальные формы взаимодействия мы рассмотрим более псдробно. Доверение ресурсов. Каждая задача А в процессе своего выполне- ния может с помощью соответствующей директивы обратиться за услугами к некоторой другой задаче В. В простейшем случае по каждому такому запросу в решение будет вводиться отдельный экземпляр задачи В, который будет считаться подчиненной задачей по отношению к задаче А. При этом для проведения совместной ра- боты задача А может доверить задаче В для использования некото- рые из своих ресурсов (файлы, область оперативной памяти) — с тем, чтобы обе задачи работали над общими для них объектами. Задаче В — как и любой другой задаче — выделяется своя виртуальная машина со своими собственными ресурсами, в том числе оперативной и, возможно, внешней памятью, к которым добавляются и ресурсы, доверенные ей задачей А. Таким образом, взаимодейст- вие задач Л и В, несмотря на то, что для каждой из них выделяется . своя виртуальная машина, достигается за счет того, что соответст- вующие части этих различных виртуальных машин отображаются
10.3] УПРАВЛЕНИЕ ВЗАИМОДЕЙСТВИЕМ ЗАДАЧ 565 операционной системой на одну и ту же Часть физической машины. При этом, разумеется, требуется синхронизация процессов, исполь- зующих общие для обеих задач ресурсы. Заметим, что задача В, к которой обратилась за услугами зада- ча А, может в свою очередь обращаться за услугами к некоторой задаче С, доверяя ей для использования часть как своих собствен- ных ресурсов, так и ресурсов, доверенных ей задачей А, и т. д., так что на самом деле над одними и теми же объектами могут рабо- тать несколько задач. Директива доверения ресурсов может иметь вид: ДОВЕРИТЬ «имя задачи), фай™ТИ>}, (признаки)) С помощью признаков можно задать допустимый характер исполь- зования доверяемого файла или области памяти: только для чте- ния, только для записи, для чтения и записи. За соблюдением пра- вил использования доверенных ресурсов следит операционная си- стема. С помощью параметров можно также указать, доверяется файл целиком или частично. При доверении части файла для задачи В ,ве& выглядит так, как будто ей доверили целый файл, но меньшей длины. Например, последовательный файл может быть доверен за- дачей от того места, где находился указатель этого файла в момент доверения, и до конца файла. После этого в задаче В для этого файла ОС заводит новый указатель. Если при своем выполнении задача В выдаст директиву установить указатель на начало файла, то ОС вернет указатель в то положение, в котором он находился в момент доверения, а при попытке после этого сдвинуть указатель назад в задаче В возникнет аварийная ситуация. ОС контролирует и дру- гие аспекты корректности использования в задаче В доверенных ей ресурсов, например режимы их использования при передовереНии этих ресурсов третьим задачам. Конец доверения наступает либо по завершении задачи В, либо при выполнении в задаче В специальной директивы возврата дове- ренных ей ресурсов, либо при выполнении в задаче А директивы аннулирования доверения. Очевидно, что возможность обращения одних задач к другим с доверением им отдельных своих ресурсов является расширением и развитием понятия «обращение к подпрограмме», поскольку здесь ос- новная и вызванная ею в решение задачи в значительной мере не- зависимы по используемым в них ресурсам и защищены от непреду- смотренных влияний друг на друга из-за возможных ошибок в каж- дой из них. Обмен сообщениями. Рассмотренная выше форма взаимодействия задач, когда одна из них образует другую, доверяя ей для исполь- зования часть своих ресурсов, может привести к весьма нерацио-
566 ОПЕРАЦИОННЫЕ СИСТЕМЫ ' [Гл. 1» нальному использованию ресурсов физической машины. Действи- тельно, если в нескольких параллельно выполняемых задачах воз- никает необходимость использовать одну и ту же задачу В для сов- местной работы с ними, то задача В будет образована в нескольких экземплярах, причем для каждого из них будет выделена своя вир- туальная машина, требующая своего отображения на физическую машину. Между тем в ряде случаев целесообразно было бы иметь задачу В только в одном экземпляре, поручив ей обслуживание за- казов, поступающих от других решаемых задач. Такой способ целе- сообразен, например, в случае, когда задача В требует значительных ресурсов памяти, но выполняются процессором достаточно быстро. Однако на пути реализации такого «многостаночного обслужи- вания» возникает ряд проблем. Во-первых, нужен какой-то другой способ вызова задачи В в решение, ибо ни одна из задач, желающих воспользоваться услугами задачи В, не знает, была ли эта задача уже вызвана в решение или нет. Во-вторых, задача В должна каким- либо образом узнать, что ее услугами хочет воспользоваться какая- то задача, и какая именно. В-третьих, необходимо обеспечить пра- вильное взаимодействие задачи В с другими задачами, даже в том случае, когда к задаче В поступает новый заказ на обслуживание раньше, чем она закончила выполнение предыдущего заказа. Некоторые операционные системы предусматривают возмож- ность такого взаимодействия задач и предоставляют в распоряжение пользователей соответствующие директивы. Основой для организа- ции указанного взаимодействия может служить обмен сообщения- ми между задачами: любая задача А может послать сообщение зада- че В, рассчитанной на такую форму взаимодействия, сформировав в некоторой группе ячеек памяти текст сообщения и выполнив спе- циальную директиву вида: СООБЩЕНИЕ ((имя задачи), (адрес текста), (длина текста)) При выполнении этой директивы ОС проверяет, находится ли зада- ча-адресат, имя которой указано в директиве, среди решаемых в данный момент задач. В случае необходимости ОС отыскивает эту задачу (среди задач, введенных в систему для решения, а если там ее не оказывается — то в архиве) и вводит ее в решение. В задаче-ад- ресате должен быть предусмотрен процесс приема сообщений — ОС и возбуждает этот процесс, передав ему текст сообщения и имя зада- чи-отправителя. При этом сама ОС, конечно, не вникает в суть сооб- щения — его трактовка производится процессом приема сообщений в задаче-адресате (таким образом, процесс приема сообщений пред- ставляет собой еще один пример специального процесса, наряду с процессом обработки аварийных ситуаций). При передаче сообщения может оказаться, что задача-адресат В занята обработкой поступившего ранее сообщения от какой-либо другой задачи. В таких случаях ОС ставит поступивший заказ в оче- редь и организует обслуживание образовавшейся очереди: по мере
10.3] УПРАВЛЕНИЕ ВЗАИМОДЕЙСТВИЕМ ЗАДАЧ 567 того как задача В завершает выполнение переданного ей заказа-, ОС снова включает ее в работу, передавая ей следующее сообщение из очереди, если оно там имеется. Для того чтобы выполнить поступивший заказ и выдать получен? ные результаты, задаче В могут потребоваться для использования массивы и файлы задачи А. В этом случае используются те же са- мые директивы доверения ресурсов, что и при взаимодействии с под- чиненной задачей. Окончив выполнение заказа, задача В может уве- домить об этом задачу А, послав ей соответствующее сообщение. Об- мен сообщениями наряду с системными семафорами может приме- няться и для синхронизации совместной работы нескольких задач. Управление. Управление представляет наиболее развитую форму взаимодействия задач. Это управление может, например, начинать- ся с того, что одна задача В, будучи образованной по директиве из другой задачи А, не включается в смесь до тех пор, пока задача А не выдаст специальную директиву «начать выполнение подчиненной задачи». Между директивами образования и начала выполнения подчиненной задачи В главная задача А может выполнять директивы доверения и некоторые другие директивы управления. Взаимодействие типа управления может иметь место и между не- зависимыми задачами, если одна задача В доверяет другой задаче А право управлять своими процессами для выполнения какого-либо заказа. Задача А может выдавать различные директивы управления зада- чей В, с помощью которых она может например: — прервать любой процесс в задаче Л; — снять это прерывание, в результате чего продолжится выпол- нение прерванного процесса в задаче В, если только он не был прер- ван по другим причинам; — выбрать содержимое любых регистров и ячеек памяти вир- туальной машины задачи В и изменить это содержимое; — взять на себя обработку аварийных ситуаций, возникаю- щих в задаче В; — взять на себя выполнение некоторых директив задачи В и т. д. Именно эта форма взаимодействия задач и позволяет создавать специализированные операционные подсистемы и другие задачи специального назначения. Типичным примером специализированной задачи, осуществляющей управление другой задачей, является «от- ладчик» — задача с помощью которой можно производить отладку вновь составленных программ. При обращении к отладчику пользователь может задать информа- цию о том, в каких местах отлаживаемой программы или при появ- лении каких событий в ней (например, в момент присваивания ука- занной переменной нового значения) он хочет выполнить те или иные вспомогательные действия и какие именно (вывести на печать зна- чения тех или иных переменных, присвоить им некоторые новые
568 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 1» значения и т. д.). Выполняя это задание на отладку, отладчик вызы- вает в решение отлаживаемую задачу и осуществляет с ней взаимо- действие типа управления: устанавливает в отлаживаемой программе прерывания в соответствии с заданием на отладку, берет на себя об- служивание этих прерываний (которое заключается в выполнении действий, указанных в задании на отладку, выдаче диагностических сообщений и т. д.), продолжает выполнение отлаживаемой програм- мы, снимая в случае необходимости одни (уже ненужные) и устанав- ливая другие прерывания и т. п. 10.4. Обеспечение диалога с ЭВМ 10.4.1. Две формы общения человека с машиной. Основное достоинство ЭВМ заключается в их универсальности и высоком быстродействии, что позволяет с их помощью за короткие промежутки времени решать самые разнообразные и сложные за- дачи. Однако с точки зрения практической работы весьма существей- ное значение имеют также оперативность доступа к ЭВМ и удобства ее использования. По причинам, изложенным в главе 9, ЭВМ используются главным образом в закрытой форме об- щения (в закрытом режиме), при котором пользователь не имеет непосредственного контакта с машиной. Чтобы выполнить с помощью ЭВМ ту или иную работу, пользователь в этом случае должен сфор- мулировать свое задание на языке управления заданиями, отпер- форировать все управляющие операторы и другие данные (тексты программ, исходные данные для них и т. д.), соответствующим об- разом собрать колоду перфокарт и передать ее в группу эксплуата- ции ЭВМ. Все поступающие таким образом задания пользователей пропускаются для их исполнения через машину специальными ли- цами — операторами ЭВМ. Затем свой комплект перфокарт вместе с результатами выполнения задания на машине пользователь может получить в определенном месте. Такой способ использования ЭВМ при выполнении многих ви- дов работ создает для пользователей большие неудобства. Возьмем, например, такую типичную работу, как отладка программ. Отладка, как мы знаем, обычно распадается на целый ряд этапов, а выпол- нение каждого из них требует одного или нескольких сеансов ра- боты на ЭВМ. Получив с машины результаты очередной отладки, пользователь должен приготовить новую колоду перфокарт, пред- ставляющую собой очередное задание на отладку, опять передать ее на обработку и ждать результатов. Вся эта процедура в лучшем случае занимает несколько часов, а иногда и несколько дней. Но если задержка на день или два в получении результатов счета по программе, изготовление которой длилось недели или даже месяцы, вряд ли вызовет у пользователя слишком острую реакцию, то ана-
to.41 > ОБЕСПЕЧЕНИЕ ДИАЛОГА С ЭВМ / 569 логичные затраты времени на внесение в программу какого-либо не- большого изменения и проверку его правильности покажутся ему совершенно нетерпимыми. К тому же такая форма доступа к машине может сильно растянуть общие календарные сроки изготовления программ. , Изготовление (и, в частности, отладка) программ существенно ускорилось, если бы пользователь имел непосредственный контакт с машиной, работая с нею в форме диалога — так, чтобы пропустив, например, свою алгол-программу через транслятор и получив сооб- щения об обнаруженных синтаксических ошибках, пользователь мог тут же внести в программу необходимые исправления и снова пропустить исправленную программу через транслятор для полу- чения сообщений о других ошибках, если таковые имеются, и т. д., до тех пор, пока не будут выявлены и устранены все синтаксические ошибки. При такой форме общения с ЭВМ пользователь мог бы во многих случаях устранить все синтаксические ошибки в своей про- грамме за один небольшой сеанс работы на ЭВМ, на что при закрытой форме общения требуются дни или даже недели, причем подавляю- щая часть этого времени уходит просто на ожидание. Аналогично обстоит дело и с отладкой оттранслированной прог- раммы: если бы пользователь мог в процессе отладки видеть и ана- лизировать промежуточные результаты, выдаваемые машиной с помощью отладчика, то он мог бы формулировать отладчику зада- ние на очередной шаг отладки на основании этого анализа и тем са- мым вести процесс отладки более целенаправленно и эффективно. Ясно, что такая возможность могла бы существенно ускорить про- цесс отладки. Непосредственный контакт с машиной в ряде случаев оказывает- ся весьма полезным и при решении задач, ибо участие человека в самом процессе решения может повысить эффективность использо- вания ЭВМ, например в тех случаях, когда алгоритм решения ка- кой-либо части задачи неизвестен или весьма сложен, и поэтому целесообразно использовать интуицию и опыт человека. Пусть, например, решается трансцендентное уравнение F(x, К)= О, зависящее от параметра К, причем надо подобрать такую после- довательность значений Ki, К»,. . ., К„, чтобы соответствующие им решения Xi, Х2, . . ., Хт образовывали таблицу с более или менее постоянным заданным шагом. Сформулировать алгоритм выбора нужных значений К, (»=1,2, .. ., т) достаточно сложно, поэтому здесь можно поступить следующим образом: запрограммировать ре- шение уравнения F(x, К)=0, используя один из известных числен- ных методов (например, путем деления отрезка пополам), а подбор Kt возложить на человека. Задав несколько разных значений К и получив соответствующие им решения X, человек начинает «чувст- вовать», как надо изменять значения К, чтобы получать подходя- щие Значения X, и тем самым он может активно и эффективно участ-
Б70 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 1» вовать в процессе решения задачи наряду с ЭВМ. Очевидно, что в этом случае ’ закрытая форма общения с ЭВМ была бы весьма неудобна. В связи с этими обстоятельствами развитые операционные систе- мы обеспечивают другую форму работы с ЭВМ — диалоговую. При этом пользователю предоставляется возможность непосредственного общения с машиной с помощью специального внешнего устройства, называемого терминалом. Для общения пользователя с операцион- ной системой при работе в форме диалога предусматривается спе- циальный язык, на котором пользователь формулирует свои ука- зания операционной системе. Этот язык выполняет ту же роль, что и язык управления заданиями, но поскольку он ориентирован на диалоговую форму общения, то для удобства пользователя этот язык выбирается более близким к обычному разговорному языку. Пользователь, работающий за терминалом, имеет возможность вызвать для исполнения любую задачу, хранящуюся в архиве системы, или установить связь с одной из уже решающихся задач. Установление связи означает, что человек и задача могут обмени- ваться любыми сообщениями в произвольные моменты времени — как по инициативе задачи, так и по инициативе человека. Разумеет- ся, пользователь должен быть обеспечен и другим набором услуг со стороны операционной системы, аналогичных тем, которыми обеспечиваются и задачи. При этом ОС может быть организована так, что все эти услуги оказывает пользователе, не непосредственно она сама, а специальная задача — операционная подсистема, кото- рую он должен вызвать с терминала. Функция этой подсистемы за- ключается в том, чтобы расшифровывать указания пользователя и переводить их в соответствующие директивы основной ОС. Следует отметить, что инициатором диалога всегда является че- ловек, а руководителем диалога — сначала ОС, а затем — опера- ционная подсистема. Такой подход позволяет человеку начать ра- боту за терминалом практически без предварительного изучения сколько-нибудь подробных инструкций по работе в форме диалога, поскольку ему, как правило, все время приходится лишь отвечать на вопросы, задаваемые ему подсистемой — эти ответы и будут яв- ляться указаниями о том, что она должна делать в данный момент. Обычно к ЭВМ можно подключать десятки и даже сотни терми- налов, с помощью которых многие пользователи могут одновременно вести диалог со своими виртуальными машинами. Но поскольку в реальной машине имеется только один процессор, то ОС моде- лирует одновременную работу многих виртуальных машин — за счет того, что она делит время работы физического процес- сора между пользователями, одновременно работающими за терми- налами.
40.41 ОБЕСПЕЧЕНИЕ ДИАЛОГА С ЭВМ ' 571 10.4.2. Терминалы. Существуют различные типы устройств, которые используются в качестве терминалов. Эти устройства в основном характеризуют •следующие свойства: , — способ отображения информации на устройстве; — способ задания информации человеком; — способ управления расположением отображаемой инфор- мации; — средства коррекции (т. е. внесения исправлений). Наиболее распространенными типами терминалов являются те- летайпы и алфавитно-цифровые дисплеи (видеотоны). Телетайп — это электрифицированная пишущая машинка, свя- занная с ЭВМ. Управление работой телетайпа может осуществляться как ручными действиями человека, путем нажатия клавиш на кла- виатуре телетайпа, так и вычислительной машиной, т. е. программ- ным путем. / Информация на телетайпе отображается в виде символов, печа- таемых на рулоне бумаги, иногда с использованием двух- или трех- цветной ленты. Поскольку телетайп имеет специализированное на- значение, то его набор символов несколько шире, чем у обычной пи- шущей машинки — он близок к наборам, имеющимся на стандарт- ных устройствах подготовки данных для ЭВМ. При нажатии каждой Рис. 10.5, клавиши соответствующий ей символ печатается на рулоне бумаги и одновременно код этого символа передается в специальный ре- гистр, доступный для чтения процессором программным путем. В свою очередь, процессор также может занести в этот регистр код некоторого символа, и тогда телетайп печатает этот символ. Поскольку на одном и том же рулоне бумаги печатаются как символы, набираемые человеком на клавиатуре, так и символы, пе- редаваемые машиной, нужен какой-то контроль согласованности их действий. При симплексной связи телетайпа с ЭВМ, схема которой имеет вид, изображенный на рис. 10.5, процессор на время своей ра- боты с телетайпом блокирует клавиатуру, так что человек не может утопить никакую клавишу до тех пор, пока процессор не снимет блокировку.
672 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. К» Более совершенной является дуплексная связь (рис. 10.6). В этом случае печать символов осуществляется только про- цессором. При нажатии какой-либо клавиши код соответствующего символа принимается процессором, а затем — под его управлением— этот символ печатается на бумаге. Как видно, при дуплексной свя- зи можно осуществлять визуальный контроль не только правиль- ности нажатия нужной клавиши человеком, но и правильности приема процессором кода набранного символа. К тому же при вы- Рис. 10.6, соком быстродействии процессора человек не замечает разницы во времени между нажатием клавиши и печатью символа. Для управления расположением печатаемых на бумаге симво- лов используются управляющие клавиши «перевод строки» (ПС) и «возврат каретки» (ВК); на некоторых телетайпах есть управляю- щая клавиша, совмещающая оба эти действия. Каждой управляю- щей клавише также соответствует определенный код, который при нажатии клавиши передается в машину, а из нее — на устройство отображения. Коррекция обычно осуществляется следующим образом. Про- грамма, входящая в состав ОС и обслуживающая терминалы, на- капливает поступающие с терминала символы, образующие строку, а когда набор строки заканчивается (нажата клавиша В К), передает эту строку на обработку другим программам. В связи с этим могут быть предусмотрены три вида коррекции: отмена символа, отмена слова (до первого пробела слева или до начала строки) и отмена строки. Для указания нужной коррекции обычно резервируются некоторые символы, редко используемые в обычных текстах, или некоторые их комбинации. Например, можно условиться отмену символа указывать буквой Щ, отмену слова — буквой Э, а отмену строки — парой символов =ВК. Если же эти символы должны вхо- дить в набираемый текст, то принимается некоторая искусственная форма их представления (например, взятие в апострофы). Програм- ма, которая принимает поступающую с терминала информацию и осуществляет ее первичную обработку, выявляет эти особые симво- лы и производит указанную корректировку, передавая на после- дующую обработку уже скорректированную строку. Например набор на клавиатуре текста Х=1ШЩ:=5.1; 'КОММЕ* Э'СОММЕМТ'
10.41 ОБЕСПЕЧЕНИЕ ДИАЛОГА G ЭВМ 573. эквивалентен набору текста Х:=5.1; 'COMMENT1 (поскольку первая буква Щ означает отмену (аннулирование} предшествующего символа 1, то второй букве Щ предшествует сим- вол =, который она и отменяет). Несколько строк в этом случае отменять нельзя — такое редак- тирование текста осуществляется уже с помощью специальной про- граммы — редактора текстов. Алфавитно-цифровой дисплей. В этом типе терминала для ото- бражения информации используется экран телевизионного типа. Экран, как правило, бывает рассчитан на 15—20 строк примерна по 80 позиций в строке (чтобы строка соответствовала одной полной перфокарте), и в каждой позиции может высвечиваться любой из до- пустимых символов. Здесь набор символов обычно аналогичен или несколько шире, чем на телетайпе. Для задания информации человеком также используется клави- атура, связь которой с экраном обычно осуществляется через про- цессор. Для управления расположением символов на экране и для целей коррекции используется так называемый курсбр — специальный зна- чок (А или "), высвечиваемый на экране: очередной передаваемый с терминала символ высвечивается в той позиции, на которую ука- зывает курсор, после чего курсор продвигается вперед на одну по- зицию (все это обеспечивается аппаратурой). Для управления поло- жением курсора со стороны человека имеются специальные управ- ляющие клавиши f и при нажатии которых производится сдвиг курсора на одну позицию влево или вправо, на одну строку вверх или вниз, так что с их помощью можно и управлять располо- жением символов на экране, и осуществлять коррекцию. Правда,, возможности коррекции зависят от ОС: если программа приема и первичной обработки накапливает одну строку, то коррекция до- пускается только внутри строки; если же она накапливает весь экран, то возможности коррекции шире. Преимущество дисплея по сравнению с телетайпом состоит & наглядности и обозримости передаваемой информации, а также в бесшумности работы. Недостатком дисплея является то, что на нем. не остается печатного документа, протоколирующего ход работы. В связи с этим к дисплею часто подключают и печатающее устройст- во (без собственной клавиатуры), с помощью которого все содержи- мое экрана в любой момент можно напечатать на рулоне бумаги, и тем самым можно обеспечить протоколирование работы на дисплее. 10.4.3. Простой пример диалога с ЭВМ. Чтобы получить представление о характере работы в этом режи- ме, приведем пример диалога пользователя с ОС, ведущегося с тер- минала, в качестве которого используется телетайп.
674 4 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 Этот пример с точностью до несущественных деталей соответст- вует реальному диалогу на одной из конкретных ОС (ОС ИПМ АН СССР для машины БЭСМ-6). Для удобства ведения диалога на некоторых телетайпах, как уже отмечалось, используются двухцветные ленты: сообщения, по- ступающие от системы, печатаются красным цветом, а сообщения, передаваемые пользователем,— черным. В приводимом ниже при- мере сообщения, поступающие от системы, мы будем предварять символом *. Пример будет снабжен комментариями, при этом сам диалог будет приведен с помощью заглавных букв. Для согласования действий ОС и пользователя принято следую- щее соглашение: когда ОС закончила печатать свое сообщение и ждет указаний пользователя, она осуществляет возврат каретки с переводом строки — это и является приглашением пользователя к работе. Нажатием этой же клавиши завершает свое очередное сооб- щение системе и пользователь. Для начала работы за терминалом пользователю нужно нажать на клавиатуре телетайпа какую-либо клавишу — первый по по- рядку символ, поступивший с телетайпа, ОС трактует как пригла- - шение к диалогу. Допустим, что в режиме диалога пользователь хочет задать и вы- полнить написанную на алголе программу суммирования шести Произвольных чисел. В данной системе основные символы алгола типа begin представ- ляются в виде последовательности букв, взятой в апострофы: 'BEGIN'. В ней также используются стандартные процедуры вво- да/вывода с именами INPUT и OUTPUT, с помощью которых мож- но вводить (выводить) как вещественные, так и текстовые значения по каналу с указанным номером. Тип значения задается с помощью фактического параметра — указателя типа: взятая в двойные апо- строфы буква Е означает вещественное значение, представленное в виде числа с плавающей точкой, а буква Т означает текстовое зна- чение. С помощью одного оператора можно ввести или вывести несколько значений разных типов. Приняв с телетайпа первый символ, ОС начинает вести диалог, печатая сообщения: ♦ 25.3.79 16.29 (печатается дата и время начала работы за терминалом) ♦ОС К ВАШИМ УСЛУГАМ Рассматриваемая ОС реализует режим диалога на фоне решения задач в закрытом режиме; при этом ОС ведение диалога возлагает на специализированную подсистему с именем СЕРВИС, которую и нужно вызвать для работы, для чего пользовать должен передать следующее сообщение:
tO.4j ОБЕСПЕЧЕНИЕ ДИАЛОГА С ЭВМ 675 ДАЙ СЕРВИС * ЖДИТЕ Базовая ОС сообщила, что заказ пользователя принят — происхо- дит вызов подсистемы СЕРВИС и подготовка ее к работе; большин- ство последующих сообщений с терминала ОС будет передавать для обработки этой подсистеме. * НУЖНА ЛИ ИНСТРУКЦИЯ? Это сообщение поступило уже от СЕРВИСА — подсистема включи- лась в работу и выясняет степень подготовленности пользователя к работе с нею; если пользователь ответит «нет», система приступает к непосредственному диалогу; при любом другом ответе пользовате- ля СЕРВИС печатает на телетайпе краткую инструкцию, достаточ- ную для того, чтобы пользователь мог продолжить диалог; после печати инструкции или при получении ответа «нет» система продол- жает диалог: НЕТ * ИМЯ РАБОТЫ: Согласно инструкции в ответ на этот запрос пользователь должен указать имя задачи — системной или своей, хранящейся в архиве, выполнение которой и представляет собой очередной этап работы; некоторые виды работ выполняет сама подсистема (например, запись в архив текста, передаваемого с телетайпа) — в этом случае пользо- ватель указывает пустое имя, т. е. просто нажимает клавишу «воз- врат каретки». АЛГОЛ Пользователь сообщил, что он хочет выполнить программу с име- нем АЛГОЛ — это имя присвоено транслятору с алгола. * ВИД РАБОТЫ: Система различает несколько видов работы: изменение паспорта за- дачи, счет по вызванной программе, трансляция, которая из-за ее типичности выделена в специальный вид работы. ТРАНСЛЯЦИЯ Пользователь сообщил, что речь идет о трансляции. * ИНФОРМАЦИЯ: Текст подлежащей трансляции программы может находиться в ар- хиве — в таком случае в своем ответе, начинающемся с трех косых черт ///, пользователь указывает имя файла, представляющего этот текст; иное начало ответа пользователя на этот запрос означает» что текст программы поступает с терминала.
4>76 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 'BEGIN' 'ARRAY' А[0:5]; 'REAL' В; 'INTEGER'K; 'INPUT (1, "Е", А); В:=0; 'FOR' К:=0 'STEP' 1 'UNTIL '5 'DO' М: В:=В+А[К1; OUTPUT (1, "Е", В) 'END' • Строка, состоящая из одной точки, является признаком конца дан- ных, передаваемых с телетайпа; приняв эту последнюю строку, сис- тема записывает принятый текст алгол-программы в файл, с кото- рого транслятор берет свой входной набор данных, т. е. подлежа- щий трансляции текст. * УКАЗАНИЯ: Система запрашивает указаний о том, что делать с результатами вы- полнения программы, в данном случае — с оттранслированной программой: отпечатать ее на АЦПУ, вывести на перфокарты, за- писать в архив, отпечатать на терминале и т. д. Если пользователь не знает, что он должен ответить на очередной запрос системы, он в качестве ответа может передать строку, состоящую из одного знака вопроса — в этом случае система отпечатает на терминале до- полнительную инструкцию, объясняющую возможные ответы и их смысл, после чего пользователь передает системе соответствующий ответ: ЗАП .. К250 • Пользователь сообщил, что оттранслированную программу надо за- писать под именем К250 в общую библиотеку; поскольку указания могут состоять из нескольких предложений, то строка, содержащая одну точку, служит признаком конца указаний — после этого си- стема включает в работу транслятор с алгола; согласно данным ра- нее указаниям оттранслированная программа будет записана в об- щую библиотеку под именем К250. * КОНЕЦ ЗАДАЧИ АЛГОЛ * ИМЯ РАБОТЫ: Система сообщает о завершении очередного этапа работы и спраши- вает у пользователя, что делать дальше; отсутствие диагностичес- ких сообщений свидетельствует о том, что синтаксических ошибок в транслируемой программе не обнаружено. К250 Пользователь сообщил, что следующий этап работы состоит в вы- полнении программы с именем К250, находящейся в общей библио- теке — эта программа была получена на предыдущем этапе. * ВИД РАБОТЫ: СЧЕТ —1.20 Пользователь просит выполнить указанную ранее программу с
10.4] ОБЕСПЕЧЕНИЕ ДИАЛОГА (} ЭВМ 57Т - именем К250, заказывая для этого 1 минуту 20 секунд процессорно- го времени. * ДАННЫЕ: Поскольку сейчас речь идет не о трансляции, а о выполнении обыч- ной программы, то система просит сообщить, откуда взять исходные данные для счета; ответ пользователя аналогичен ответу на запрос ИНФОРМАЦИЯ для транслятора. 0.1, 0.2, 0.3, 0.4, 0.5, 0.6; • ^сходные данные, которые будут вводиться в программе по опера- тору ввода, пользователь задал с терминала. ♦ УКАЗАНИЯ: Система просит указаний о том, что делать с результатами выполне- ния программы. • При отсутствии указаний система накопит все полученные резуль- таты и затем выведет их на терминал. * ПУСК ИЛИ ОТЛАДКА: Программу система может выполнять как в режиме счета, так и в режиме отладки по указанию пользователя. ПУСК На запрос системы о режиме выполнения программы пользователь задал режим счета. * КОНЕЦ ЗАДАЧИ К250 ♦ 2.1000000 Система сообщила о том, что выполнение задачи К250 закончено, и отпечатала полученный результат. * ИМЯ РАБОТЫ: Система спрашивает, что делать , на следующем этапе. КНЦ Пользователь сообщает, что он закончил работу за терминалом; это сообщение до СЕРВИСА уже не дошло — его перехватила ОС и отключила СЕРВИС от обслуживания данного терминала. ♦ ВЫ ОКОНЧИЛИ РАБОТУ В 16.43. ВСЕГО ХОРОШЕГО. Этим сообщением ОС заканчивает сеанс связи с данным терминалом и снимает его с обслуживания — для следующего сеанса надо на- чать работу опять нажатием какой-либо клавиши на терминале. Если собрать все сообщения пользователя, то мы увидим, что полученный текст напоминает задание для выполнения той же рабо- ты в закрытом режиме. Однако так получилось из-за того, что в данном случае пользователь, во-первых, хорошо знал правила ра- боты с системой и, во-вторых, не сделал ни одной ошибки ни в алгол- программе, ни в указаниях системе. На практике же дело обычно 1» Э. 3. Любинский и др.
Б78 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 обстоит иначе — вот тогда-то и сказываются преимущества работы в режиме диалога. Эти преимущества мы увидим при рассмотрении более полного примера в конце этого раздела. 10.4.4. Основные виды услуг при диалоговой форме общения. Современная операционная система предоставляет пользовате- лю целый комплекс услуг, которые превращают терминал в доста- точно комфортабельное рабочее место программиста. В этот комп- лекс обычно входят: архивное обслуживание, редактирование про- граммных текстов, возможность решения задач в форме диалога, отладка, пошаговая трансляция и настольные вычисления. Некото- рые из этих видов обслуживания аналогичны тому, что делается и в закрытом режиме, хотя предоставляются в более удобной форме. Другие же в принципе возможны только в диалоговом режиме. 10.4.4.1. Архивное обслуживание. Архив необходим в любой более или менее развитой системе — без него система похожа на собесед- ника, который каждый раз забывает все, о чем шла речь в предыду- щих разговорах. Архив предназначен для того, чтобы в течение длительного времени хранить некоторые тексты (программы, дан- ные и т. д.) и предоставлять пользователю возможность работать с этими текстами, ссылаясь на них с помощью имен. Архивная служба ОС выполняет указания пользователя вида ПИСАТЬ ((имя), (откуда)), ЧИТАТЬ ((имя), (куда)), ЗАБЫТЬ ((имя)), смысл которых нам уже известен. При этом имена тек- стов могут быть как простыми, так и составными (типа ОТДЕЛ- СОТРУДНИК-ИМЯ). Система ведет каталог имен хранимых текстов, причем составные имена позволяют заводить каталог, имеющий вид иерархического списка, что ускоряет поиск нужного текста. Такой каталог удобен и для обеспечения конфиденциаль- ности доступа: каждое имя текста может сопровождаться указа- нием права доступа (для чтения, для записи, для выполнения), причем нужный «ключ» (запрет или разрешение) можно присвоить любому уровню. Так, поскольку в рассмотренном выше примере диалога пользователь записал оттранслированную программу в об- щую библиотеку под именем К250, то после этого он (и любой дру- гой пользователь) может многократно использовать эту программу как в текущем, так и в последующих сеансах работы. 10.4.4.2. Редактирование программных текстов. Возможность редактирования программ, хранящихся в архиве, с использованием терминала избавляет пользователя от необходимости перебивать перфокарты, формировать новые колоды перфокарт и т. д., а порой и вообще не пользоваться ими. Мы уже знаем из главы 6 (раздел 6.5), что редактирование текс- тов включает в себя пять основных элементов. Г. Определение объекта редактирования. В режиме диалога текст, подлежащий редактированию, может быть взят из архива
10.4) ОБЕСПЕЧЕНИЕ ДИАЛОГА С ЭВМ 579 или введен с терминала. Некоторые системы допускают и смешан- ный способ, когда одни части текста берутся из архива, а другие вводятся с терминала. 2°. Определение места редактирования. Как и в закрытом режи- ме, оно может производиться при помощи номеров строк. Однако идентификация строк по их номерам вызывает ряд трудностей, по- скольку по ходу редактирования одни строки могут удаляться, а новые добавляться, так что номера строк могут изменяться и поэто- му при редактировании нетрудно ошибиться; если же нумерацию строк в процессе редактирования не изменять, то нельзя ссылаться на вновь добавляемые строки. Поэтому обычно редактор изменяет нумерацию строк по указаниям пользователя типа СМЕНИТЬ НОМЕРА. При редактировании с помощью дисплея весьма популярен спо- соб указания места редактирования с помощью курсора. В начале редактирования курсор указывает на первую строку текста, а с использованием специальных управляющих клавиш курсор можно продвигать вперед и назад по тексту на заданное число строк. Мо- жет использоваться и комбинация этих двух способов: сначала кур- сор устанавливается на строку с заданным номером, а затем продви- гается в нужном направлении. Довольно часто применяется также контекстный способ, при котором используются указания типа «всюду А заменить на В»; «взять строку, которая начинается с Л:= и часть этой строки, рас- положенную между М: и -,КОН заменить на (Л+В)/2» и т. д. 3°. Собственное редактирование. Для этого используются две основные операции ВСТАВИТЬ и УДАЛИТЬ, с помощью которых можно провести любое редактирование, причем вставляемая строка может задаваться с терминала. Однако для удобства работы часто используются и другие операции, например ЗАМЕНИТЬ. Особенно наглядно и удобно выполняется редактирование с по- мощью алфавитно-цифровых дисплеев. Помимо управляющих кла- виш, позволяющих перемещать курсор в любую позицию экрана, такие дисплеи обычно снабжаются и другими управляющими кла- вишами, позволяющими раздвигать строки, образовывать пустые места внутри строк, раздвигая содержащиеся в них символы, не- посредственно заменять одни символы на другие и т. д. При этом важно, что программист все время видит результаты своих дейст- вий, а диалоговый радактор отслеживает нажатия всех таких кла- виш и дублирует производимые на экране изменения в тексте ре- дактируемой программы. Некоторые дисплеи позволяют вести редактирование текста ав- тономно от ЭВМ. Такие дисплеи имеют свою собственную память, содержимое которой постоянно отображается на экране, и с помощью клавиатуры дисплея можно непосредственно вносить изменения в эту память (причем все изменения в ней тут же находят свое отра- 19*
580 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 1» жение и на экране). После того как по одной из директив система передаст на дисплей запрошенный текст (или его часть), произво- дится его редактирование в памяти дисплея без участия ЭВМ, а по окончании редактирования этот текст по другой директиве можно передать для запоминания в архив системы. В этом случае основные функции редактора будут заключаться в выдаче на дисплей и в приеме от него отдельных частей текста. 4°. Документирование. Помимо изменения текста система долж- на выдавать пользователю по его указаниям отчет о проделанной работе: протокол ведения редактирования, информацию о состоя- нии текста по ходу редактирования, окончательный текст или от- дельные его части. В случае дисплея промежуточное документиро- вание часто оказывается излишним, поскольку на экране пользо- ватель все время видит текущее состояние редактируемого текста (или определенной его части, если текст полностью не помещается на экране), зато выдача заключительного протокола работы стано- вится особенно желательной, для того чтобы впоследствии можно было точно установить, когда и какие изменения производились в файле. 5°. Сохранность объекта редактирования. Отредактированный текст по указаниям пользователя может быть сохранен в архиве для последующего его использования. 10.4.4.3. Решение задач. Пользователю предоставляется возмож- ность вызывать для исполнения как специальные задачи, постоянно хранящиеся в системе (редакторы программных текстов, транслято- ры, загрузчики и т. д.), так и свои собственные задачи, давая при этом указания о том, откуда брать входные наборы данных для них (из архива, с перфокарт, с терминала) и куда помещать выходные наборы данных (в архив, на АЦПУ, на терминал и т. д.). При этом за один сеанс работы на терминале можно — как и с использованием языка управления заданиями в закрытом режиме — выполнить це- лую последовательность шагов задания. Однако — в отличие от за- крытого режима — теперь пользователю нет необходимости обяза- тельно заранее планировать и формулировать все шаги задания. Поскольку исходные данные для программы, выполняемой на оче- редном шаге, пользователь может задавать с терминала, и полу- чаемые результаты ему могут тут же выдаваться на терминал, то. каждый очередной шаг задания пользователь может формулировать в зависимости от полученных ранее результатов. Отсюда видно, например, насколько удобно решать в режиме диалога упоминавшуюся ранее задачу о подборе нужной последов вательности значений Ki параметра К при решении уравнения F(x, Х)=0, с тем чтобы получить нужную последовательность ре- шений Xf этого уравнения: задав с терминала очередное значение К и получив на терминал решение X, пользователь смотрит, подхо- дит ли ему это решение, и если нет, то — анализируя полученные
10.41 ОБЕСПЕЧЕНИЕ ДИАЛОГА С ЭВМ 58| ' ранее пары {Kt, Xt) — прикидывает, как надо скорректировать это значение К, чтобы получить нужное решение X; если же очередное нужное значение X получено, он переходит к получению следующего значения X, опять подбирая соответствующее значение К.. Очевид- но, что решение подобного рода задачи в закрытом режиме потребо- вало бы многих сеансов работы на ЭВМ (с подготовкой для каждого из них нового задания) и растянулось бы на длительный период времени. 10.4.4.4. Отладка. Отладка программ — это традиционный вид работы, при выполнении которой особенно желателен непосредст- венный диалог человека с машиной. Суть этого Диалога состоит в том, что в определенные моменты времени (после выполнения опре- деленных команд программы) человек останавливает свою виртуаль- ную машину, смотрит и анализирует текущее содержимое интере- сующих его ячеек памяти и (или) регистров процессора и в зависи- мости от результатов этого анализа принимает решение о том, что делать дальше: продолжать выполнение программы с прерванного места или повторить вычисления с некоторой команды, на какой команде или при какой ситуации сделать следующую приостановку выполнения программы и т. д. Таким образом, очередной шаг про- цесса отладки обычно зависит от результатов предыдущего шага. Поскольку эти результаты, как правило, заранее неизвестны (ошиб- ки в программе могут быть самые разные и появиться в разных ее местах), а выясняются уже в процессе выполнения программы маши- ной, то закрытый режим порождает большие трудности при отлад- ке, так как каждая непредвиденная ситуация заставляет готовить новый план отладки и готовить новое задание для его реализации. Для ведения отладки в режиме диалога предусматривается спе- циальный диалоговый отладчик, который воспринимает и реализует указания пользователя по ведению отладки, задаваемые с терминала. Диалоговый отладчик дает возможность пользователю формули- ровать очередной шаг отладки по ходу дела в зависимости от ре- зультатов предыдущих шагов отладки, что обеспечивает большую гибкость в ведении отладки и эффективность каждого сеанса работы на машине с помощью терминала. Как мы уже отмечали ранее, для современных отладчиков зада- ние на отладку формулируется не в терминах адресов ячеек памяти, а в терминах тех имен объектов программы, которые использовались при ее составлении на исходном языке программирования. Такая возможность обеспечивается за счет того, что трансляторы сохра- няют информацию об исходных обозначениях, которая и исполь- зуется отладчиком. Обычно отладчик способен выполнять следующий набор опера- ций отладки: ПУСК. (Л) — начать выполнение отлаживаемой программы с команды с адресом А (с оператора, помеченного меткой А);
682 ОПЕРАЦИОННЫЕ СИСТЕМЫ (Гл. 10 ПРОД (Л) — продолжить выполнение программы с команды с адресом А (с оператора, помеченного меткой Л), а если Л не указа* но — то с прерванного места; СТОП (В) — прервать выполнение программы при выполнении соотношения В. В простейших случаях этими соотношениями могут быть: КРА=А (выполнилась команда с адресом Л (первая команда оператора с меткой Л)); ЗАП—А (произведена запись по адресу Л (переменной Л присвоено новое значение)); ВЫБ=А (произведена выборка из ячейки Л (выбрано значение переменной Л)). Для задания вспомогательных действий, которые должен вы- полнить отладчик в момент прерывания выполнения программы, служат отладочные команды типа ВЫВОД (Л, N, Р) и ЗАПИСЬ (Л, Р, S). По первой из них выводится содержимое У ячеек памяти, начиная с ячейки с адресом Л (с ячейки, отведенной для объекта с именем Л); параметр Р определяет формат, по которому должно быть выведено каждое машинное слово, например: Р—Д—десятич- ное число, Р—В — слово в восьмеричной записи, Р=К — слово в формате команды, Р=Т — текст и т. д. По второй команде в ячейку Л (в ячейку, начиная с которой размещен объект с именем Л) за- писывается параметр S в формате Р. Например, отладочная команда ВЫВОД (2100, 5, К) задает вывод пяти последовательных команд, начиная с команды с адресом 2100, команда ЗАПИСЬ (X, Д, 20.5) задает действие Х:=20.5, а команда ЗАПИСЬ (6500, Т, ШАГ) предписывает записать в ячейку 6500 машинное слово, представляю- щее последовательность букв ШАГ. При использовании исходных обозначений (вместо адресов ячеек) могут возникнуть и некоторые осложнения. Например, при отлад- ке программы, которая первоначально была написана на алголе, от- ладочная команда ВЫВОД (X, 1, Д) может оказаться неоднознач- . ной, если переменная X описана в нескольких блоках. В таких слу- чаях предусматривается задание уточняющей информации в виде метки (или порядкового номера) блока, в котором описана интере- сующая нас переменная. 10.4.4.5. Открытая и пошаговая трансляция. Наряду с «закры- тыми» трансляторами, выполняемыми в закрытом режиме, сущест- вуют и открытые трансляторы, используемые в режиме диалога. Исходную программу для такого транслятора можно ввести с тер- минала и сюда же получить диагностические сообщения об обнару- женных транслятором ошибках. При наличии таких ошибок их мож- но исправить с терминала и снова вызвать для работы транслятор. Еще большие удобства с точки зрения выявления и устранения синтаксических ошибок в составленной программе предоставляют пошаговые трансляторы. Здесь общение пользователя и транслятора производится по поводу каждой фразы языка: после ввода с терми- нала очередной фразы пошаговый транслятор тут же обрабатывает ее и в случае выявления в ней ошибок выдает на терминал диагности-
10.4] ОБЕСПЕЧЕНИЕ ДИАЛОГАС ЭВМ 583 ческие сообщения. Получив такое сообщение, пользователь может внести исправления или набрать с терминала эту фразу заново и заставить транслятор повторить обработку этой фразы. к Конечно, пошаговый транслятор работает медленнее, но зато существенно упрощается и ускоряется работа пользователя по от- ладке синтаксиса своей программы. Так что замедление работы транслятора окупается дополнительными удобствами для пользо- вателей и сокращением общего числа трансляций, необходимых для получения правильной программы. 10.4.4.6. Настольные вычисления. ЭВМ, конечно, предназначены прежде всего для решения сложных задач с большим объемом вы- числений. Между тем в своей повседневной работе у пользователей часто возникает необходимость выполнять и несложные вычисления типа вычислений по формулам. В частности, такая потребность до- вольно часто возникает при подготовке тестов для отладки программ. Обычно для этих целей используются настольные клавишные полуавтоматы, умеющие выполнять четыре основные арифметические операции. Наличие же терминала позволяет использовать ЭВМ и для подобного рода вычислений, причем так же просто, как и настоль- ный полуавтомат. Для этих целей в системе программирования ча- сто предусматривается достаточно простой и удобный для подобного рода вычислений язык программирования и соответствующий ин- крементальный транслятор. Связь с таким транслятором осуществ- ляется с терминала, причем транслятор обеспечивает не только пе- ревод каждой фразы языка, но и — по указанию пользователя — ее немедленное последующее выполнение с выдачей на терминал полученного результата. Набор допустимых операций во входном языке для инкрементального транслятора обычно намного богаче, чем у клавишного полуавтомата. Обычно он допускает использова- ние достаточно сложных формул, вычисление элементарных функ- ций и несложные циклы. Поскольку подобного рода вычисления производятся машиной на фоне решения других задач и к тому же требуют очень мало вре- мени работы процессора, то это практически не снижает общей про- изводительности ЭВМ, а наличие такой возможности позволяет не иметь клавишных полуавтоматов, которые обычно используются для несложных расчетов. 10.4.5. Развернутый пример диалога с ЭВМ. Для иллюстрации различных возможностей, предоставляемых пользователю при работе с ЭВМ в режиме диалога, рассмотрим еще один пример. Пусть имеется трансцендентное уравнение, зависящее от пара- метра: F(x, £)=cos4-—Лх=0. ОТ
584 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл 10 Нужно найти значения kit kt и k9 такие, чтобы соответствующие им решения Xi, х9 их# с точностью до 0.01 принимали значения 1,1.5 и 2. Для решения этой задачи составим программу на алголе, пред- назначенную для решения уравнения при некотором. задаваемом значении k методом деления отрезка пополам, который заключается в следующем. Допустим, что на отрезке [а0, 5«] имеется единствен- ный корень х уравнения f(x)—O. Тогда У1=[(а9) и у9=[(Ьд имеют разные знаки. Возьмем середину отрезка х0=(ав+&в)/2 и вычислим у»=/(х0). Если уо=О, то х=х0. Если г/о¥=О, то в качестве нового от- резка [ai, М, на котором находится искомый корень, примем ту по- ловину исходного отрезка, .на концах которого значения функции /(х) имеют разные знаки. Полученный отрезок [аь М снова делим пополам, т. е. берем точку x1=(a14-bt)/2, вычисляем t/x=f(xi) и т. д. Этот процесс прекращается, когда либо для очередного х{ окажется x/f=0 (и тогда х=Х(), либо длина очередного отрезка [ль будет меньше заданной точности е, с которой должен быть найден корень: поскольку xc[at, bt], то любую точку этого отрезка можно принять в качестве приближенного значения х корня, поскольку | х—х|<8. Для определенности в качестве х условимся брать середину этого отрезка. Заметим, что решение нашего уравнения эквивалентно нахож- дению такого значения х, при котором пересекаются графики функ- ций y=kx и у = cosy. Очевидно, что при любом £>0 имеется един- ственное решение, принадлежащее отрезку [0, л], так что можно при- нять ао=О, 6в=3.1 (»л). Интересующую нас тройку значений kt будем подбирать в режиме диалога с ЭВМ. Алгоритм решения этой задачи составим так, чтобы его было удобно использовать в этом режиме. Если обозначить через Т оче- редное априорное значение корня, для которого подбирается значе- ние параметра К, а через X — решение уравнения F(x, К)=0 (ко- торое.будем находить с точностью 8=0.001), то неформально этот алгоритм можно описать следующим образом: 1°. Т:=1. 2°. Печать текста К= (что является сигналом о том, что дальше следует ввод значения К). 3°. Ввод значения К, задаваемого с терминала. 4°. Задание начальных границ отрезка, на котором находится корень уравнения F(x, К)=0. 5°. Нахождение с точностью 8=0.001 корня X, соответствующего заданному значению К. 6°. Вывод найденного значения корня, предварив его текстом 7.а Если |Х—Т|>0.01, то переход к п. 2° (на котором, исходя из полученного значения X, подбирается очередное значение К).
10.41 ОБЕСПЕЧЕНИЕ ДИАЛОГА С.ЭВМ '585 8°. Печать окончательно подобранного значения К и соответст- вующего ему корня X. 9°. Т^Т+О.б; при Т^2 переход к п. 2°. - 10°. Стоп. При выполнении этого алгоритма все выдачи будем делать на терминал. Приводимый ниже диалог также с точностью до несущественных деталей отражает реальный диалог, проведенный в ОС ИПМ АН СССР на машине БЭСМ-6. Поскольку из протокола диалога доста- точно очевидно, какие строки выдает на печать система, а какие пе- чатает пользователь, работающий за терминалом, в качестве кото- рого используется телетайп, мы не будем делать каких-либо спе- циальных отметок об этом. Итак, для начала работы пользователь нажимает какую-либо клавишу на терминале, после чего система ставит этот терминал на обслуживание и начинает диалог: 06.03.79. 16.10 ОС К ВАШИМ УСЛУГАМ ДЛЯ РАБОТЫС ЗАДАЧЕЙ НАБЕРИТЕ СЛОВО'ДАЙ" И НАЗВАНИЕ ЗАДАЧИ ДАЙ СЕРВИС В ответ на запрос системы дана заявка на задачу СЕРВИС, которая и будет вести диалог. ЖДИТЕ НУЖНА ЛИ ИНСТРУКЦИЯ? НЕТ ИМЯ РАБОТЫ: Сейчас мы хотим ввести с терминала алгол-программу и записать ее в архив. Эту работу выполняет сам СЕРВИС; в системе принято соглашение о том, что в этом случае надо указать пустое имя рабо- ты, т. е. просто осуществить перевод строки, после чего система продолжает диалог: ИНФОРМАЦИЯ: Если текст, подлежащий обработке, вводится с терминала, то в от- вет на этот запрос надо начать набор текста (в данном случае нашу алгол-программу) на терминале. Чтобы проиллюстрировать более или менее реальную ситуацию, в первоначальной алгол-программе допущены некоторые ошибки. Итак, в ответ на последний запрос системы задаем с терминала текс* алгол-программы: 'BEGIN' 'REAL' А, В, К, X, Т, Р, YI, Y2, Y; 'PROCEDURE' F (X, С); 'VALUE' X; 'REAL' X, С; C:=COSX/2—КхХ; Т:=1;
586 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 М: OUTPUT (1, "'Г, *К=*); INPUT (1, "Е", К); А:=0; В:=3.1; F(A1, Yl); F(B, Y2); ПОВТ:Х:=(А+В)/2; F(X, Y); L:'IF' Y=0 'THEN' 'GO TO' KOH; 'IF' SIGN (Y1)=SIGN(Y) 'THEN' 'BEGIN' A:=X; Y1:=Y'END' 'ELSE' 'BEGIN' B:=X; Y2:=Y'END'; 'IF' B—A>0.001 'THEN' 'GO TO' ПОВТ; X:=(A—B)/2; KOH: OUTPUT (1, "T", "X=', "E", X); P:=T—X; 'IF' ABS (P)>0.01 'THEN' 'GO TO' M; OUTPUT (2, «Е», К, X); T:=T+0.5; 'IF' T<2.05 'THEN' 'GO TO' M 'END' Напомним, что точка является признаком конца текста. УКАЗАНИЯ: Введенную программу запишем под именем ПРИМЕР в библиотеку БИБОТЛ: ЗАП БИБОТЛ ПРИМЕР ИМЯ РАБОТЫ: Теперь выведем на печать введенную нами программу, для чего об- ратимся к услугам редактора текстов: EDITOR ИНФОРМАЦИЯ: Система спрашивает, что должен делать редактор и с каким текстом. Редактору можно задать целую последовательность указаний о том, что он должен сделать — признаком конца этой последователь- ности служит указание ВСЕ. В частности, редактору можно дать указание вида ПЕЧ N К, что означает отпечатать К строк текста, начиная со строки с номером N; если N=K=0, это означает печать всего текста: ПЕЧ 0 0 ВСЕ Вслед за указанием последовательности действий надо определить объект редактирования, т. е. задать информацию о том, откуда взять подлежащий редактированию текст. Этот текст можно про- честь из библиотеки, хранящейся во внешней памяти, ввести с тер- минала, либо часть текста прочесть из библиотеки и к нему доба- вить текст, вводимый с терминала. В нашем случае речь идет о тексте алгол-программы, который был записан в библиотеку: ЧТЕ БИБОТЛ ПРИМЕР
10.4] ОБЕСПЕЧЕНИЕ ДИАЛОГА С ЭВМ ' Stfl Получив всю необходимую информацию для работы редактора, СЕРВИС вызывает редактор текстов и передает ему полученную ин? формацию. После того как редактор закончит работу, об этом пе- чатается сообщение и выдается результат его работы: КОНЕЦ ЗАДАЧИ EDITOR 0001 'BEGIN' 'REAL' А, В, К, X, Т, Р, YI, Y2, Y; 0002 'PROCEDURE' F (X, С); 'VALUE' X; 'REAL' X, С; 0003 C:=COS X/2—КхХ; 0004 T:=l; 0005 М: OUTPUT (1, "Г, *К="); INPUT (1, "Е", К}; 0006 А:=0; В:=3.1; F (Al, YI); F (В, Y2); 0007 ПОВТ: Х:=(А+В)/2; F (X, Y); ' 0008 L: 'IF' Y=0 'THEN' 'GO TO' KOH; 0009 'IF' SIGN (Y1)=SIGN (Y) 'THEN' 'BEGIN' A:=X; Y1:=Y 'END' 0010 'ELSE' 'BEGIN' B:=X; Y2:=Y 'END'; ООП 'IF' B—A>0.001 'THEN' 'GOTO' ПОВТ; X:=(A—B)/2; 0012 KOH: OUTPUT (I, "T", "X=", "E", X); P:=T—X; 0013 'IF' ABS(P)>0.01 'THEN' 'GO TO' M; 0014 OUTPUT (2, "E", К, X); T:=T+0.5; 0015 'IF' T<2.05 'THEN' 'GO TO' M 'END' КОЛИЧЕСТВО СТРОК В ТЕКСТЕ — 0015 ИМЯ РАБОТЫ: Теперь попросим оттранслировать и выполнить нашу программу; для этого вызовем для работы транслятор с алгола, скажем, что от- транслированную программу надо записать в общую библиотеку под именем ПРОГР, что эту программу затем будем выполнять в ре- жиме отладки: ALGOL 'ПРОГР' ОТЛ ИНФОРМАЦИЯ: Здесь требуется указать, откуда взять текст, подлежащий трансля- ции. Наша программа записана в библиотеке БИБОТЛ: ЧТЕ БИБОТЛ ПРИМЕР СЕРВИС включает в работу транслятор с алгола и по окончании его работы выдает сообщение: КОНЕЦ ЗАДАЧИ ALGOL :ПРИ ТРАНСЛЯЦИИ ОБНАРУЖЕНЫ ОШИБКИ Далее выдается распечатка от транслятора с диагностическими сооб- щениями об обнаруженных синтаксических ошибках: В ТЕКСТЕ ВАШЕГО ЗАДАНИЯ ДОПУЩЕНЫ ОШИБКИ ₽=== = = = = = = = = = == = = = = = = =5]?=55=Se=;=^S=3 С: =COS X/ НЕТ (ПОСЛЕ СТАНДАРТНОЙ ФУНКЦИИ 2—КхХ;
588 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 1в А:=0; В: =3.1; F(A1, НЕОПИСАННЫЙ ИДЕНТИФИКАТОР Yl); F(B, Y2); ИМЯ РАБОТЫ: Действительно, в нашей программе были допущены ошибки: на ал- голе надо писать не cos х/2, a cos (х/2); кромё того, при первом обращении к процедуре вместо А ошибочно записано А1. Для ис- правления Обнаруженных ошибок снова воспользуемся редактором текстов: EDITOR ИНФОРМАЦИЯ: Поручим редактору строки с номерами 3 и 6 заменить на новые строки, задаваемые с терминала. Исправленный текст поручим за- писать под тем же именем в библиотеку БИБОТЛ, а для проверки сделанных исправлений попросим отпечатать первые 6 строк текста исправленной программы: ЗАМ 3 1 C:=COS (Х/2)—КхХ; ЗАМ 6 1 А:=0; В:—3.1; F (A, Yl); F (В, Y2); ЗАП БИБОТЛ ПРИМЕР ПЕЧ 1 6 ВСЕ ЧТЕ БИБОТЛ ПРИМЕР По окончании работы редактора печатается сообщение об этом фак- те и на терминале печатается запрошенная часть программы: КОНЕЦ ЗАДАЧИ EDITOR 0001 'BEGIN' 'REAL' А, В, К, X, Т, Р, Yl, Y2, Y; 0002 'PROCEDURE' F (X, С); 'VALUE' X; 'REAL' X, С; 0003 C:=COS (X/2)—КхХ; 0004 Т: = Г 0005 М: OUTPUT (1, Т, "К=У INPUT (1, *Е", К); 0006 А:=0; В:=3.1; F (A, Yl); F (В, Y2); КОЛИЧЕСТВО СТРОК В ТЕКСТЕ — 0015 ИМЯ РАБОТЫ: Повторим попытку оттранслировать и выполнить исправленную алгол-программу: ALGOL 'ПРОГР' ОТЛ ИНФОРМАЦИЯ: ЧТЕ БИБОТЛ ПРИМЕР
JO.U ОБЕСПЕЧЕНИЕ ДИАЛОГА' С ЭВМ 689 КОНЕЦ ЗАДАЧИ ALGOL УКАЗАНИЯ: Выдача этих сообщений означает, что синтаксических ошибок в ал* гол-программе обнаружено не было и что СЕРВИС приступает к выполнению оттранслированной программы, которая уже записана в ' общую библиотеку под именем ПРОГР (взятие этого имени в апостро- , фы при обращении.к транслятору как раз и является указанием записать оттранслированную программу в общую библиотеку). Те- перь запрашиваются указания о том, откуда брать данные, вводимые по операторам ввода (с перфокарт, непосредственно с терминала я т. д.), а также куда и как выводить данные по операторам вывода (непосредственно на терминал или на терминал, но с предвари- тельным накапливанием всех выдаваемых данных (этот режим считается стандартным), на АЦПУ и т. д.). Мы попросим ввод по каналу 1 (значение К) осуществлять непосредственно с терминала; вывод по каналу 1 (текст К=, текст Х== и значение X) тоже осу- ществлять непосредственно на терминал, а вывод по каналу 2 (окон- чательные результаты) осуществлять стандартным способом — на терминал по окончании решения задачи: НВ — IN1 НП —OUT 1 ПУСК ИЛИ ОТЛАДКА: , Для отладки надо указать метку, по которой мы хотим остановить- ся, и дать указание ПУСК, чтобы программа выполнялась с начала. Попросим сначала остановиться по метке L: KPAL ПУСК К= В программе выполнился оператор вывода с меткой М — результат его выполнения (вывод текста) отпечатан на терминале, поскольку для первого канала был задан непосредственный вывод на печать. Далее в программе должен выполняться оператор ввода значения К, но поскольку для первого канала был задан непосредственный ввод, то система осуществляет перевод строки, приглашая задать с терминала вводимое значение. Выполнение программы приостанав- ливается до тех пор, пока на терминале не будет набрано число и осуществлен перевод строки: 1.2 После ввода этого значения возобновляется выполнение программы. ОСТАНОВ L Система сообщила, что выполнение программы приостановлено в заданной точке. Теперь можно задавать указания о выводе на тер-
590 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. I минал значений интересующих нас величин (ВЫВД означает вывод десятичных чисел): ВЫВД А 0 В этот момент времени значение А действительно должно быть рав- но нулю. Выведем еще значения В, X и Y: мы ожидаем получить В=3.1, Х=1.55 и Y«—1.14 (это значение вычислено раньше вруч- ную): ВЫВД В .3100000+01 ВЫВД X .1550000+01 ВЫВДУ - —.1145578+01 Полученные значения совпали с ожидаемыми. Теперь проверим еще одно приближение — для этого продолжим выполнение программы с остановкой опять по метке L (поскольку этот останов был уже за- дан ранее, то снова устанавливать его не нужно): ПРОД ОСТАНОВ L Выведем снова значения А, В, X и Y: ВЫВД А 0 ВЫВД В .1550000+01 ВЫВД X .7750000+00 ВЫВД Y —.4143237—02 Как видно, приближение к корню идет нормально. Теперь заставим программу выполниться до получения решения с заданной точно- стью и остановимся по метке КОН: КРА КОН ПРОД ОСТАНОВ КОН Посмотрим на найденный корень: ВЫВД X —.3783500—03 Корень почему-то получился отрицательный, чего не должно быть. Процесс приближения к корню мы проверили, а после него выпол- нялся только оператор Х:=(А—В)/2. Анализируя этот оператор, . убеждаемся, что он ааписан неправильно — на самом деле он дол-
10.4] ОБЕСПЕЧЕНИЕ ДИАЛОГА С ЭВМ БЭ1 жен быть записан в виде Х:=(А4*В)/2! Выполнять дальше эту про* грамму не имеет смысла, поэтому дадим команду отладки «прекра- тить выполнение программы»: ПРЕК КОНЕЦ ЗАДАЧИ ПРОГР ИМЯ РАБОТЫ: Результатов, выводимых по второму каналу, получено еще не было, поэтому нет и распечатки результатов, которые обычно выдаются вслед за сообщением об окончании задачи. Как обычно, система спрашивает, что делать дальше. Теперь нам надо внести исправление в исходную программу с помощью редактора текстов: EDITOR ИНФОРМАЦИЯ: ЗАМ 11 1 'IF' В—А>0.001 'THEN' 'GO ТО' ПОВТ; Х:=(А+В)/2; ЗАП БИБОТЛ ПРИМЕР ПЕЧ 11 1 ВСЕ ЧТЕ БИБОТЛ ПРИМЕР • По окончании выполнения этого задания редактором получим рас- печатку: КОНЕЦ ЗАДАЧИ EDITOR ООП 'IF' В—А>0.001 'THEN' 'GOTO' ПОВТ; Х:=»(А+В)/2; КОЛИЧЕСТВО СТРОК В ТЕКСТЕ — 0015 ИМЯ РАБОТЫ: Теперь оттранслируем исправленную программу. Будем надеяться, что ошибок в ней больше нет, поэтому оттранслированную програм- му попросим выполнить в режиме счета: ALGOL 'ПРОГР' СЧЕТ ИНФОРМАЦИЯ: ЧТЕ БИБОТЛ ПРИМЕР УКАЗАНИЯ: НВ —IN1 НП — OUT1 По окончании выполнения этого задания получим распечатку: КОНЕЦ ЗАДАЧИ ALGOL Отсутствие сообщений об ошибках свидетельствует о том, что трансляция прошла нормально и теперь начинается решение задачи в режиме диалога: мы будем задавать значение К, анализировать получаемое решение X и в зависимости от этого задавать следующее значение К:
ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл Н> к=» 1.0 Х= .9002552+00 К= 0.8 Х= .1074326+01 К= 0.9 Х= .9804797+00 К= 0.875 Х= .1002426+01 Первая пара значений К и X подобрана; переходим к подбору такого значения К, чтобы получить Х=1.5: К= 0.5 Х= .1478478+01 К= 0.45 Х= .1570812+01 К= 0.49 Х= .1495884+01 Теперь будем подбирать значение К для получения Х=2: К= 0.25 Х= .2059727+01 К= 0.30 Х= .1916685+01 К= 0.27 Х = .2000694+01 КОНЕЦ ЗАДАЧИ ПРОГР .8750000+00 .1002426+01 .4900000+00 .1495884+01 .2700000+00 .2000694+01 В-качестве результатов решения задачи ПРОГР отпечатаны иско- мые пары значений К и X. « ИМЯ РАБОТЫ: КНЦ
' 10.5) ОРГАНИЗАЦИОННЫЕ ФУНКЦИИ 595 Имя работы КНЦ означает, что мы кончили работу за терминалом. В отрет система печатает сообщение ВЫ ОКОНЧИЛИ РАБОТУ В 18.25. ВСЕГО ХОРОШЕГО. и снимает данный терминал с обслуживания. Как видно, за один сеанс работы на ЭВМ с помощью терминала можнЬ выполнить целую последовательность работ различного вида, причем часто очередной вид работы определяется результа- тами предыдущей работу — в таких случаях при закрытом режиме каждый раз пришлось бы готовить новое задание и пропускать его через машину. Очевидно, что решение подобной задачи в закрытом режиме растянулось бы примерно на неделю, если не более (счи- тая, что в день можно сделать один выход на машину). При этом заметим, что в течение нашей работы за терминалом машинного времени мы израсходовали очень мало, а в остальное время ЭВМ не простаивала, а была занята решением других задач. 10.5. Организационные функции 10.5.1. Режимы использования ЭВМ. Возникновение операционных систем, как мы знаем, было свя- зано с проблемой эффективного использования оборудования, для решения которой в ЭВМ стали предусматривать мультипрограм- мный режим работы. Однако наличие такого режима само по себе еще не решает указанной проблемы — оно предоставляет лишь возможности для ее решения. Чтобы реализовать эти возможности, надо обеспечить постоян- ное наличие выгодной смеси задач (т. е. такой смеси, которая наи- более полно загружает работой отдельные устройства машины) и систематическое ее пополнение по мере завершения выполнения той или иной задачи. Возложение этих функций на человека — оператора ЭВМ — неизбежно привело бы к необходимости частых остановок машины для выполнения оператором тех или иных ручных действий за пультом управления, что также влекло бы за собой простои обору- дования. К тому же задача планирования выгодной смеси слишком сложна для того, чтобы человек мог решать ее достаточно быстро и эффективно. В связи с этим упомянутые выше функции возла- гаются на операционную систему. Чтобы избежать простоев оборудования при пополнении смеси, а также для целей эффективного планирования смеси, ОС предва- рительно вводит с перфокарт и записывает на свои ресурсы внешней памяти целую группу задач, подлежащих решению на ЭВМ. Эту группу задач называют пакетом (хотя здесь суть дела точнее от- ражал бы термин «пачка» или «набор», тем более что термин «пакет» употребляется и в другом смысле — например, пакетами называют
594 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 комплексы программ, предназначенные для решения задач опреде- ленного класса). Вводимый в машину пакет предварительно фор- мируется оператором и кладется в читающее устройство. После того как пакет задач введен, его дальнейшая обработка осуществ- ляется операционной системой, как правило, уже без вмешательства оператора. Такой режим использования ЭВМ называется режимом пакет- ной обработки (или просто пакетным режимом}. Основная цель, которая ставится перед пакетным режимом,— обеспечить наиболее эффективное использование оборудования, т. е. выполнить все за- дания пакета за минимальное время. Достижению этой цели и подчинены все действия операционной системы. При обработке пакета ОС выполняет следующие функции. а) Планирование смеси, т. е. определение порядка, в котором задачи пакета должны включаться в набор одновременно решаемых машиной задач. Такое планирование ведется исходя из соображений наиболее полной загрузки работой всех устройств машины. С этой - точки зрения выгодной является такая смесь, в которой присутст- вуют и задачи с большим счетом, которые обеспечивают загрузку процессора, и задачи с частыми обменами между оперативной и внешней памятью, и задачи, обеспечивающие загрузку устройств ввода и вывода. Чтобы ОС могла эффективно выполнять эту свою функцию, в паспорте каждой задачи обычно предусматривается задание ин- формации о ее специфике (преимущественно счет, частые обмены, большой ввод или вывод и т. д.) — эта информация и используется ОС при планировании смеси. Вообще говоря, возможны два принципа планирования смеси: глобальный и локальный. В первом случае ОС заранее анализирует паспорта всех задач пакета и составляет наиболее оптимальный план обработки пакета, а затем реализует этот план. Однако для такого планирования нужна достаточно точная информация о каж- дой из задач, в том числе и о времени ее решения. Между тем на практике эта информация задается весьма приближенно — обычно указывается лишь верхняя граница требуемого времени (для борьбы с возможными зацикливаниями), а любые отклонения от указанного в паспорте времени неизбежно будут нарушать состав- ленный план. К этому же приводит и возникновение в решаемых задачах всяких непредвиденных ситуаций, из-за которых решение некоторых задач приходится прекращать преждевременно. Поэтому на практике чаще используется локальное планирование: по окон- чании решения какой-либо задачи, входящей в смесь, ОС решает, какую (какие) из оставшихся в пакете задач надо включить в смесь, чтобы эта новая смесь оказалась наиболее выгодной. Возможность получения выгодной смеси, естественно, зависит от числа оставшихся в пакете задач. В связи с этим некоторые ОС
10.5] ОРГАНИЗАЦИОННЫЕ ФУНКЦИИ 595 предусматривают возможность систематического пополнения па- кета по ходу его обработки за счет включения в него новых задач, вводимых с перфокарт. б) Планирование и управление выполнением задач смеси. Сюда относится: распределение между задачами смеси внешних устройств и непосредственное управление работой этих устройств при выпол- нении заказов на обмены; обработка прерываний; определение по- рядка, в котором задачам предоставляется физический про- цессор. При решении всех этих вопросов ОС также исходит из основной цели, стоящей перед пакетным режимом. В частности, ОС стре- мится делать как можно меньше переключений процессора с за- дачи на задачу и тем самым свести к минимуму те дополнительные действия, которые приходится выполнять при каждом таком пере- ключении. С этой точки зрения рассмотренная в главе 9- дисцип- лина обслуживания задач по их срочности явно невыгодна — в пакетном режиме ОС стремится держать каждую задачу в фазе счета возможно дольше, если только это не влечет за собой дополни- тельных простоев оборудования. в) Связь с оператором. Режим пакетной обработки существенно сократил число ручных действий оператора ЭВМ, связанных -со сменой решаемых задач, а также снял с него обязанности планиро- вания порядка выполнения отдельных задач. С этой точки зрения функции оператора сводятся к тому, чтобы собрать комплект перфо- карт, образующих пакет, и положить его в читающее устройство. Зато у него появились более ответственные функции, связанные с общим управлением вычислительной системой и ведением диалога с ОС по ходу обработки пакета, что требует более высокой квалифи- кации оператора. ОС при обработке пакета поддерживает регулярную связь с опе- ратором при помощи специального внешнего устройства, анало-х гичного телетайпу. На этом устройстве ОС ведет протокол выпол- нения задач пакета, печатает заявки оператору на установку нуж- ных лент на магнитофоны, печатает сообщения о сбоях в работе, устройств и т. д. Оператор информирует ОС о выполнении посту- пивших от нее заявок и, в свою очередь, может передавать свои ука- зания операционной системе: снять ту или иную задачу с решения, исключить задачу из пакета, ускорить решение некоторой задачи, отключить от вычислительной системы то или иное внешнее уст- ройство (например, по причине его неисправности) или подключить его и т. д. Для связи между оператором и ОС разрабатывается спе- циальный язык, удобный для оператора и понятный операционной системе. До сих пор мы говорили об эффективном использовании обору- дования ЭВМ. Однако при эксплуатации ЭВМ возникает и другая, не менее важная проблема — обеспечение наибольших удобств
696 ОПЕРАЦИОННЫЕ СИСТЕМЫ ' [Гл. 10 для пользователей. Эта проблема особенно важна в связи с тем, что современная ЭВМ — в силу ее высокой производительности —• может обслуживать очень большое число пользователей. ' Как видно, пакетный режим соответствует специфике закрытой формы общения с машиной, хотя в некоторых случаях он все же создает определенные неудобства для пользователей. Например, решаемые задачи бывают более срочные и менее срочные. Ясно, что первые из них надо решать максимально быстро, тогда как решение вторых можно и отложить на некоторое время. При пакетном же режиме ОС может самые срочные задачи на самом деле выполнить в последнюю очередь, если такой порядок выполнения задач по- зволил добиться более эффективного использования оборудования. Для пользователя немаловажное значение имеет также и характер выполняемой работы. Так, если речь идет об очередном этапе отладки программы, то пользователь, естественно, хотел бы полу- чить ее результаты как можно быстрее, а задержка с получением результатов даже на не очень продолжительное время может со- здать определенные неудобства для данного пользователя. Подобного рода неудобства частично могут быть устранены за счет приписывания каждой задаче некоторого приоритета, опреде- ляющего степень ее срочности. Этот приоритет (который задается числом, причем обычно большей срочности соответствует и большее число) либо указывается в паспорте задачи, либо определяется самой операционной системой (например, как величина, обратно пропорциональная запрашиваемому времени,— с тем чтобы самые короткие задачи имели более высокий приоритет). Если в ОС предусмотрена система приоритетов, то планирова- ние порядка выполнения задач заключается в нахождении разум- ного компромисса между стремлением обеспечить наиболее полную загрузку оборудования и необходимостью быстрее завершать ре- шение задач с высоким приоритетом. Что касается диалоговой формы общения человека с машиной, при которой пользователь и ЭВМ (точнее — вычислительная си- стема) постоянно обмениваются сообщениями, то здесь пакетный режим совершенно не подходит. При рассмотрении этой формы общения мы отмечали, что к ЭВМ обычно подключается большое число терминалов, с помощью которых многие пользователи одно- временно ведут диалог с ЭВМ. Очевидно, что такая форма работы имеет смысл только в том случае, если система будет достаточно быстро реагировать на каждый запрос каждого из работающих за терминалами пользователей. Для обеспечения эффективности диалоговой формы общения операционная система применяет другой режим использования ЭВМ, называемый режимом разделения времени, а операционная система, реализующая этот режим, называется системой разделе- ния времени (СРВ).
10.51 ОРГАНИЗАЦИОННЫЕ ФУНКЦИИ 697 Главная цель, которая ставится перед этим режимом, заклю- чается в обеспечении максимальных удобств для пользователей, общающихся с ЭВМ в режиме диалога. А это значит, что необхо- димо обеспечить максимально быструю реакцию системы на каж- дый запрос, поступающий с терминалов,— так чтобы у каждого пользователя создавалось впечатление, что с машиной работает он один. Для достижения этой цели ОС должна применять иную дисцип- лину обслуживания пользователей по сравнению с пакетным режи- мом. Если в пакетном режиме система включает в смесь только такое число задач, которое обеспечивает эффективное использова- ние оборудования, то теперь в смесь должно включаться как можно больше задач, запросы на общение с которыми поступают с терми- налов. Порядок включения задач в смесь также определяется не соображениями эффективности, а порядком. поступления заказов на обслуживание. При выполнении задач смеси ОС делит время работы физического процессора на отдельные, равные между собой интервалы («кванты»), в течение которых она поочередно передает физический процессор для отображения тех виртуальных машин, с которыми работают пользователи. Такое деление времени работы физического процессора фак- тически приводит лишь к соответствующему снижению быстро* действия виртуальных машин по сравнению с реальной. Но если . число одновременно работающих за терминалами пользователей не слишком велико, то при том высоком быстродействии, которым обладают современные ЭВМ, получаемое быстродействие виртуаль- ных машин вполне приемлемо для пользователей. По сути дела режим разделения времени — это возрождение первого этапа использования ЭВМ (пользователь, за пультом уп- равления машины), но на более высоком уровне — как за счет более удобного средства общения с машиной, так и за счет того, что теперь при всяких задержках со стороны человека «простаивает» только виртуальная машина данного пользователя, а реальная ЭВМ в это время занимается обслуживанием других пользователей. Из сказанного выше ясно, что те удобства, которые предостав- ляет пользователям система разделения времени, обеспечиваются за счет снижения эффективности использования оборудования, - поскольку в режиме разделения времени процессор приходится постоянно переключать с одной задачи на другую. Между тем довольно часто такие переключения оказываются и бесполезными, например из-за того что пользователь, работающий за терминалом, еще не успел проанализировать очередное полученное из машины сообщение и не принял решения относительно того, что ему делать дальше. Подобные ситуации могут привести к простоям уже реаль- ной машины, причем вероятность таких простоев тем больше, чем меньше пользователей, работающих за терминалами.
598 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 10 Чтобы свести к минимуму такие простои и в максимальной мере использовать преимущества каждого из рассмотренных выше ре- жимов, некоторые ОС реализуют режим разделения времени на фоне решения задач пакета: на время простоя виртуальных машин ОС переключает процессор на решение задач пакета в пакетном режиме. Такие операционные системы называются совместимыми системами разделения времени, поскольку они совмещают оба ре- жима использования ЭВМ. 10.5.2. Административные функции. На операционную систему обычно возлагаются и некоторые функции по учету, контролю и регулированию работы пользова- телей, которые она выполняет под руководством администрации вычислительного центра. Набор таких функций обычно сущест- венно зависит от специфики данного вычислительного центра. Операционная система может, например: — вести учет лиц, имеющих право работать на данной вычисли- тельной системе, и не допускать к выполнению посторонних задач, даже в том случае, если они каким-либо.образом попали на машину; — по указанию администрации ВЦ изменять состав пользова- телей; — вести учет израсходованных пользователем ресурсов и ока- занных ему услуг (расходование машинного времени, перфокарт; бумаги на АЦПУ, количество и длительность использования выде- ленных ему магнитных лент, время работы за терминалом, исполь- зование программ, хранящихся в библиотеке системы и т. д.), не допуская расходования ресурсов сверх установленного лимита; — выписывать пользователям счета на оплату за израсходо- ванные ресурсы и оказанные услуги, если это предусмотрено в дея- тельности ВЦ; — обеспечивать выполнение графика работы на машине от- дельных пользователей и (или) подразделений, если такой график установлен администрацией ВЦ; — по запросам администрации выдавать на печать различного рода сведения, имеющиеся в распоряжении ОС (текущий состав пользователей, статистические данные, собираемые ОС в процессе ее функционирования, и т. д.). 10.5.3. Обеспечение надежности функционирования вычисли- тельной системы. Одна из важнейших функций ОС заключается в повышении надежности работы вычислительной системы. Ведь современная ЭВМ — это весьма сложный механизм, в состав которого входит -довольно много различных устройств. Многие из них содержат в себе сложные электрические схемы, большое количество отдель-
10.51 ОРГАНИЗАЦИОННЫЕ ФУНКЦИИ ggg ных узлов, деталей, физических приборов и т. д. Некоторые уст- ройства, чтобы быть работоспособными, требуют поддержания определенных параметров электропитания и окружающей их среды — температуры, чистоты воздуха и т. п. Естественно, что в работе столь сложной и чувствительной аппаратуры возможны и случайные сбои, и устойчивые отказы в работе, которые приводят к нарушению правильности функционирования ЭВМ и тем самым к получению неверных результатов вычислений, а то и к полному нарушению ее работоспособности. Операционная система должна исключить возможность выдачи неверных результатов, связанных со случайными сбоями в работе аппаратуры. Более того, перед современной ОС ставится задача обеспечения постоянной и правильной деятельности вычислитель- ной системы, несмотря на сбои или даже отказы в работе отдельных устройств или их узлов, так что надежность вычислительной си- стемы должна быть значительно выше, чем надежность самой ЭВМ. Если же неисправность носит характер аварии (поломка уст- ройства, обрыв магнитной ленты и т. д.), то задача ОС — спасти по возможности больший объем информации, связанной с этим уст- ройством, с тем чтобы ее не приходилось воспроизводить заново, и сделать это по возможности без прекращения деятельности' вы- числительной системы в целом, чтобы пользователи, чьи про- граммы и данные не были затронуты этим отказом, вообще его не почувствовали. Функции операционной системы, связанные с повышением на- дежности и поддержанием непрерывной работоспособности вычис- лительной системы, можно разбить на три основные группы. Г. Обеспечение или повышение достоверности результатов, получаемых с помощью вычислительной системы, т. е. своевре- менное обнаружение сбоев или отказов в работе аппаратуры. Усилия, затрачиваемые на эти цели, определяются теми послед- ствиями, которые могла бы повлечь за собой выдача из ВС невер- ных результатов. Наиболее надежными, но зато и наиболее доро- гими формами контроля за правильностью результатов, являются либо дублирование устройств в ЭВМ (или даже ЭВМ в целом), которые параллельно и независимо друг от друга выполняют одну и ту же работу, либо — что применяется чаще — повторное про- ведение на данной ЭВМ одних и тех же вычислений с целью до- биться двух- или трехкратного совпадения результатов. Трехкрат- ного совпадения добиваются в наиболее ответственных ситуациях (например, при управлении медицинскими приборами во время операции); обычно же ограничиваются двухкратным совпадением. Если при каждом повторении проводить все необходимые вычисле- ния от начала и до конца, то при большом времени счета и доста- точно большой вероятности сбоев для получения совпадающих ре- зультатов придется делать довольно много повторений. Поэтому
600 ОПЕРАЦИОННЫЕ СИСТЕМЫ [Гл. 1» такой контроль может потребовать много времени и обойтись слишком дорого. В связи с этим на практике обычно применяется метод «контрольных точек» — фиксация промежуточного состояния задачи во внешней памяти через определенные интервалы времени и повторение вычислений, производимых в каждом интервале, с переходом к следующему интервалу только при получении двух совпадающих результатов. Ясно, что чем меньше интервал, тем быстрее можно получить совпадающие результаты (т. е. тем де- шевле обходится обнаружение каждого сбоя), однако при слишком малых интервалах будут слишком велики расходы на запись про- межуточных состояний задачи во внешнюю память. Поэтому вели- чина интервала выбирается с учетом степени надежности конкрет- ной ЭВМ. ОС обычно специально контролируют сохранность материала во внешней памяти и правильность выполнения операций обмена, применяя метод контрольного суммирования. Суть этого метода состоит в том, что передаваемый во внешнюю память массив данных снабжается «контрольной суммой» (одним или несколькими машин- ными словами), которая вычисляется по такому правилу, чтобы по возможности любые изменения в этом массиве приводили к из- менению его контрольной суммы. При чтении массива из внешней памяти его контрольная сумма вычисляется заново и сравнивается с той, которая была прочитана вместе с массивом. В современных ЭВМ широко применяются и аппаратные спо- собы контроля хранения и передачи данных, базирующиеся на принципе «нечет» (или «чет»). При этом способе каждое машинное слово дополняется одним «контрольным» разрядом. При каждой записи слова в память аппаратура записывает в этот разряд цифру 0 или 1 — так, чтобы общее количество единиц в этом «расширен- ном» слове было нечетным, а при каждом чтении из памяти прове- ряет на нечетность общее количество единиц в прочитанном расши- ренном слове. Нарушение нечетности числа единиц свидетельствует об ошибке в хранении или в передаче слова, и в этом случае возбуж- дается (внутреннее) прерывание. Надежность аппаратуры в современных ЭВМ такова, что для задач не выше средней ответственности аппаратные методы контроля вполне достаточны, и поэтому для таких задач повторение вычисле- ний, как правило, не применяется. Иногда контроль правильности получаемых результатов можно предусмотреть в самой программе, используя определенные конт- рольные соотношения, которым теоретически должны удовлетво- рять эти результаты (если, например, производится расчет хими- ческой реакции, то можно проверить, удовлетворяют ли получен- ные результаты закону сохранения масс). Такие методы контроля обычно обходятся гораздо дешевле (например, подставить найден- ное решение в уравнение и убедиться в том, что Оно удовлетво-
lo.sl ОРГАНИЗАЦИОННЫЕ ФУНКЦИИ 601 ряется с заданной степенью точности, значительно проще и быст- рее, чем решать это уравнение второй раз). Несколько сложнее обстоит дело с отказами (под этим термином обычно понимается нарушение работоспособности устройства, при- ‘ водящее к устойчивому получению неправильного результата). В этом случае рассмотренный выше метод повторения неприменим, ибо каждый раз будет получаться совпадающий, но неверный ре- зультат. Иногда отказ обнаруживается достаточно просто (например, обрыв магнитной ленты). В других случаях для его выявления применяются тесты. По аналогии с тестами для отладки программ, это специальные программы, которые в определенном порядке загружают работой все устройств? ЭВМ й их отдельные узлы и сравнивают получаемые при этом результаты с заранее извест- ными. Многие операционные системы в процессе своей работы систе- матически применяют те или иные методы контроля: периодически вызывают для работы тесты, чтобы убедиться в отсутствии отказов аппаратуры, организуют повторение вычислений по достижении контрольных точек, контролируют правильность всех обменов с внешней памятью (если отсутствует аппаратный контроль или он считается недостаточным) и т. д. 2°. Борьба с последствиями сбоев и отказов. Наиболее типичными являются следующие приемы, которые используются операцион- ными системами для этой цели. а) Систематическое копирование всех магнитных лент — с тем чтобы в случае отказа в работе какой-либо из них (например, в слу- чае обрыва) можно было продолжить работу с ее копией. б) Повторение операций, при выполнении которых произошел сбой. Если, например, при чтении очередной зоны магнитной . ленты обнаружилось несовпадение контрольных сумм, то ОС по- вторяет (в случае необходимости многократно) это чтение, пока операция чтения не будет выполнена правильно. в) Попытка выполнить операцию с использованием другого подходящего устройства, если на первоначально используемом устройстве произошел отказ (например, вывод на печать с помощью - другого АЦПУ, вместо вывода на перфокарты осуществить вывод на перфоленту, > ввести данные через другое устройство ввода и т. д.). г) Повторение вычислений с ближайшей контрольной тойки после устранения отказа, который не позволил закончить очеред- ной этап вычислений. 3°. Профилактика неисправностей. Операционная система пред- принимает меры не только к выявлению сбоев и отказов в работе аппаратуры и к ликвидации их последствий, но и по предупрежде- нию возможных сбоев или отказов, например:
602 ОПЕРАЦИОННЫЕ СИСТЕМЫ (Гл. 10 — собирает статистику сбоев по отдельным устройствам и со* общает ее инженерному персоналу через телетайп оператора для принятия соответствующих мер; — отключает от вычислительной системы ненадежно работаю- щие устройства и передает их в распоряжение инженеров для про- верки и ремонта без приостановки работы системы в целом; — вводит в состав системы запасные устройства, если таковые имеются, вместо неисправных; — предоставляет инженерному персоналу набор услуг для проверки отдельных устройств с помощью самой ЭВМ, например возможность пускать специальные тесты для проверки работы устройств в критических режимах, задаваемых инженерами. Таким образом, современная операционная система существенно расширяет возможности физической ЭВМ, делая ее более надеж- ной и эффективной, более удобной для инженерного и администра- тивного обслуживания и более комфортабельной для пользователей.
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ Автокод 447 — бланковый 450 — безбланковый 450 Автомат машины Тьюринга 18 Адрес 202 — абсолютный 338 — внутренний 338, 453 — исполнительный 264 — косвенный 267 — непосредственный 266 — переменный 263, 314 — постоянный 263 — прямой 266 Алгол 43 — -программа 107 Алгоритм 9, 13, 15 — самоприменимый 37 Алгоритмическая неразрешимость 36 Алгоритмический язык 40 Алфавит 15, 44, 49, 406, 449 Анализ лексический 373 — синтаксический 374 Ассемблер 449, 492 База 270 Байт 324 Билистинг 474 Бит 201 Блок 87 Блок-схема 130 Буква 15, 49, 449 Буквальная константа 463 Вершина дерева 180 — стека 139 Ветвь дерева 180 Виртуальная машина 515, 531 — память внешняя 534 --- оперативная 531 Виртуальные устройства ввода/вывода 534 Вход модуля 356 Вхождение слова 29 Выражение 56, 65, 84 — адресное 456 — арифметическое 73, 409 Выражение именующее 78 — логическое 73, 410 — простое арифметическое 65—68 ---именующее 78 ---логическое 68—72 — условное 72—76 Граничная пара 102, 103 Двоичное дерево 180 --- сбалансированное 185 Диагностическое сообщение 473 Дизъюнкция 50, 69 Директива 521 Диспетчер 514 Дисплей 573 Дихотомия 178 Доверение ресурсов 564 Заголовок макроопределения 479 — модуля-спецификации 427 — подпрограммы 426 — процедуры 122 — функции 425 — цикла 93, 94 Загрузка 353 Загрузчик 353 Задание 551 Запись 156, 431 Защита памяти 521 — файлов 540 Звено 145 — заглавное 147, 157 Знак операции 50 ---отношения 69 Значение имени 453 Идентификатор 61, 450 — процедуры 112 Имена 452 — внешние 356, 471 — внутренние 356 — входные 356 — общие 363, 471 Импликация 50 Индекс 62
604 ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ Индекс звена 146 — приведенный 193, 320 Интерпретация 341 Интерфейс 108, 126, 493 Информационная сеть 165 Информационное поле задачи 518 Клетка программы машины Тьюринга 19 Ключ записи 174 — - защиты памяти 523 { — отладки 384 Код операции 203, 457 — — мнемонический 457 — преобразования 432 Кодировка 16 Команда 201 — автокода 457 — ассемблеру 464 — машине 201 Комментарии 125, 404, 450 Композиция алгоритмов 25, 42 Компьютер 199 Конечное звено поиска 181 Константа автокода 460 --- буквальная 463 Контекст 348 Концепция умолчания 103 Конъюнкция 50, 69 Корень дерева 180 Курсбр 573 Лента машины Тьюринга 18 Листинг 473 Литерал 463 Логическое значение 71 Макроассемблер 501 Макровызов 479 Макрогенератор 500 Макрокоманда 479, 480 Микрооперация 479 Макроопределение 479 — системное 488 Макроподстановка 480 Макросредства 478 Мантисса числа 220 Массив 62, 101 Математическое обеспечение ЭВМ 347, 530 Машина Поста 35 — Тьюринга 17 — — универсальная 28, 152 —электронно-вычислительная 198 ------- виртуальная 515 Машинная программа 202 Машинное слово 201 Машинный язык 202 Металингвистические формулы 46 Метка 78, 411 Мнемокод 457 Модуль 349, 402, 423 — абсолютный 352 — -блок данных 423, 427, 438 — главный 403, 437, 439 — загрузочный 352 — подпрограмма 404, 426, 433 — пользователя 352 — -процедура 404 — спецификаций 427 — -функция 425, 437 Мультипрограммный режим 512 Набор машинных операций 200 Нормальная форма Бэкуса (БНФ) 44 Нормальный алгоритм 29 Область применимости алгоритма 16 Обмен сообщениями 565 Обращение к подпрограмме 327 Общий блок данных 363, 421 Объект внешний 356 — внутренний 356 — глобальный 108 — локальный 107 Объявление 404, 417' — внешних имен 420, 472 — внутренней функции 424 — массивов 418 — начальных данных 422 — общих объектов 421 — спецификаций 418 — типа 419 — формата 431 — эквивалентности 420 Ограничитель 50 — параметра 65, 112 Операнд 57 — арифметический 59 — логический 71 Оператор 41, 56, 76, 404, 410 — безусловный 87 — ввода/вывода 84, 428 — возврата 416 — вызова подпрограммы 415 х — основной 76 — останова 417 — паузы 416 — перехода 78, 411 — предписания 411 — присваивания 56, 76, 410 — продолжения 413 — процедуры 83, 84 — пустой 76, 86
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ Оператор составной 87—90 — терминальный 414 — управления 411 -----программой 416 — условный 86—88, 412 -----арифметический 412 -----логический 413 -----неполный 86 — форматного вывода 429 — цикла 87, 93—96, 413 Описание 101 — массивов 103 — процедуры 101, 112, 117 — типа 102 Описатель поля 431 Основная гипотеза теории алгоритмов 27, 35 Отладка программы 378, 521 Отладчик 378, 385 Отношение 69 Очередь 134—138 Ошибки полуфатальные 375 — синтаксические 379 — содержательные 379 — устранимые 375 — фатальные 376 Пакет задач 549, 593 Пакетный режим 594 Память машины 203 -----внешняя 204, 503 -----внутренняя 204, 503 -----вспомогательная 204 — — оперативная 204 Параметр динамический 350 — ключевой 481 — позиционный 480 — статический 351 — фактический 63, 65, 111 — формальный 111, 125 — цикла 93 Паспорт задачи 515 — модуля 352 Переключатель 79 Переменная 42, 61, 409 — периода генерации 476 — простая 58, 61 — с индексами 58, 62 Подпрограмма 326 — стандартная 337 — фортрана 404 Порядок числа 220 Прерывание 516 — внешнее 519 — внутреннее 519 Применимость алгоритма 16, 21, 30 Принцип локализации 107—111 605 Принцип программирования сверху вниз 126-»—131 ---снизу вверх 131 — произвольного доступа к ячейкам памяти 199 — хранимой программы 199 — фон-Неймана 199 Приоритет задач 596 Присваивание 42 Причина прерывания 517 Программа 19, 199 — выполнимая (фортрана) 403 — машинная 202 ---абсолютная 275 ---в символических обозначениях 275, 277 Программное обеспечение ЭВМ 530 Прокрутка 29, 385 Процедура 111 — -подпрограмма 423 — рекурсивная 122—125 — -функция 423 Процессор 203 Разделитель 50, 450 — поля 431 Ранг выражения 66, 67, 71 — операции 66, 71 Регистр 205 — адреса команды 206 — второго аргумента 207 — индексный 263 — команды 206 — особой ситуации 207 — первого аргумента 207 — прерываний 517 — признака результата 206 — страничной приписки-525 — сумматора 207 Редактор межмодульных связей 372 — программных текстов 394 Режим использования ЭВМ пакетный 594 --------разделения времени 596 — работы ЭВМ диспетчера 520 --------обычный 520 --------пользователя 520 --------привилегированный 520 Релокация программ 524 Самоопределенная величина 455 Сегмент массивов 103 Семантика 44 Синтаксис 44 Синтаксический анализатор 373 Система вычислительная 347 — команд 282 ’
бое ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ Система операционная 52$ ----разделения времени 596 ----совместимая 598 — программирования 347 ----многоязыковая 367 — счисления 212 ---- восьмеричная 214 ---- двоичная 213 ---- позиционная 212 ----смешанная 214 * ----шестнадцатеричная 214 Системный программист 347 Слово входное 16, 21 — выходное 16, 21 — ключевое 407 — машины 201, 282 — пустое 15 — состояния процессора 518 Смесь задач 549 Совокупность значений 122 — спецификаций 117, 122 — фактических параметров 64, 84 Спецификатор 117 Спецификация 116—120 — повторений 434 — формата 431 Список 156 — ассоциативный 165 — вывода 429 ----простой 430 ----с циклом 430 — граничных пар 103 — двунаправленный 160—162 — иерархический 164 — кольцевой 162—164 — левой части оператора присваива- ния 77 — однонаправленный 157—160 — параметров 64, 112, 122 — цикла 93, 94 Справка звена 146, 157 Стек 138—143, 257 Страница памяти 528 Строка 144 — в алголе 118 — заключительная в фортране 437 — начальная 406 — -продолжение 406 Структурное программирование 82, 132 Схема алгоритма 32, 100, 127—129 Сцепление 146 Счетчик команд 238 — повторений 434 — размещения 454 Таблица 174 — перемешанная 187—192 Таблица распределения памяти 370 Телетайп 571 Тело звена 146 — макроопределения 479 — модуля 352, 425 — процедуры 112 — цикла 93, 413 Терминал 57 > Тест 380 Тип 57 — вещественный 57 — логический 57 — текстовый 462 — целый 57 Транслятор 347, 373 — закрытый 582 — инкрементальный 583 — открытый 582 — пошаговый 582 Тэр 226 Указатель переключателя 79 Управляющая процедура 553 Управляющий оператор 551 Условие 73 Условное ассемблирование 474 Устройство арифметическое 203 — ввода 204 — внешнее 204^ 510 — вывода 204 — управления 203 Файл 535 — адресный 536 — индексно-последовательный 538 — индексный 537 — последовательный 537 Фактор блокирования записей 538 Форма общения с ЭВМ 568 — диалоговая 570 — закрытая 568 Формат выдачи 430 — команды 202 Формула подстановки 29 ---завершающая 30 Функция 63, 120 — внешняя 424 — внутренняя 424 — встроенная 423 — расстановки 187 ХЭШ-таблица 190 Цепочка 146, 157 Цикл 308 — итерационный 310 — с известным числом повторений 312
ПРЕДМЕТНЫЙ УКАЗАТЕЛЬ €07 Число 59, 60, 407 — нормализованное 220 — с плавающей точкой 219 фиксированной точкой 220 Элемент массива 62, 102 — списка цикла 93 — — — типа пересчета 95 перечисления 95 Экстракод 521 Электронно-вычислительная машина (ЭВМ) 198 — безадресная (стековая) 255 — двухадресная 240 — дробно-адресная 249 — одноадресная 245 — с переменным форматом команды — трехадресная 238 — четырехадресная 228 прогрессии 95 Язык алгоритмический 40 — ассемблера 449 — загрузки 353 — машинно-ориентированный 447 — машины 202 — программирования 41, 447 — управления заданиями 551 Ячейка памяти 201 — рабочая 231
Эдуард Зиновьевич Любимский, Виктор Владимирович Мартынюк, Николай Павлович Трифонов ПРОГРАММИРОВАНИЕ М., 1980 г., 608 стр. с илл. Редактор Af. И. Любимова Техн, редактор И, Ш. Аксельрод Корректоры Т, С. Вайсберг, С. Н. Макарова ИБ № 11551 Сдано в набор 28.02.80. Подписано к печати 12.11.80. Т-17839. Бумага 60Х90‘/1в. Тип. № 1. Литературная гарнитура. Высокая печать. У слови. печ. л. 38. Уч.-изд. л. 41,63. Тираж 60 000 экз. Заказ Ml 1438. Цена книги 1 р. 50 к. Издательство «Наука» Главная редакция физико-математической литературы 117071, Москва, В-71, Ленинский проспект. 15 Ордена Октябрьской Революции и ордена Трудового Красного Знамени Первая Образцовая типография имени А. А. Жданова Союзполиграфпрома при Государственном комитете СССР по делам издательств, полиграфии и книжной торговли. Москва, М-54, Валовая, 28 Отпечатано в типографии № 4 издательства «Наука», Новосибирск, 77, Станиславского, 25. Тип. зак. № 454.

j