Теперь составление списка требований почти закончено. Остался невыясненным лишь вопрос, как производится передача параметров, т. е. передача начальных данных подпрограммам и возвращение результатов.
ПАРАМЕТРЫ ПОДПРОГРАММ
Мы определили параметры подпрограмм как информацию, задаваемую ей основной программой при обращении или возвращаемую основной программе по окончании работы подпрограммы. Параметры, задаваемые при обращении, называются входными, а посылаемые обратно при возврате — выходными.
Пусть, например, подпрограмма ISQRT предназначена для вычисления корней целых чисел. Входным параметром для этой подпрограммы является значение исходного целого, задаваемого в форме полного слова. Выходной параметр в данном случае представляет требуемое значение корня. Вообще в тех случаях, когда подпрограмма имеет всего один входной параметр, его значение при обращении обычно находится в регистре 0. Если аналогично имеется всего один выходной параметр, то он возвращается основной программе через регистр 1.
Предположим, что значение исходного целого находится в ячейке N основной программы. Пусть, кроме того, значение корня мы хотим получить в ячейке SQN. Если основная программа удовлетворяет всем уже известным нам требованиям, то обращение к подпрограмме может выглядеть так:
L 0,N ВХОДНОЙ ПАРАМЕТР ЗАГРУЖАЕТСЯ РЕГИСТР 0
L 15, = V(ISQRT) ЗАГРУЗКА АДРЕСА НАЧАЛА ПОДПРОГРАММЫ ПЕРЕХОД НА ISQRT ЗАПИСЬ РЕЗУЛЬТАТА
В случае одиночных параметров необходимо соблюдать известную осторожность при выполнении восстановления регистров. Обычно используемая для восстановления команда
LM 14,12,12(13)
теперь не подходит, так как при ее выполнении уничтожается результат, полученный подпрограммой и помещенный в регистр 1. Восстанавливать в подобных случаях следует значения всех регистров за исключением регистра 1:
LM 14,0,12(13)
LM 2,12,12(13)
Аналогичная процедура выполняется, когда параметры подпрограммы представляют собой числа, представленные в форме с плавающей точкой с одинарной точностью. Входной параметр засылается в регистр 0, выходной выбирается из регистра 1.
Рассмотрим более общий случай, когда подпрограмма имеет по нескольку входных и выходных параметров. Если бы в нашем распоряжении находилось достаточное число регистров, то можно было бы ими воспользоваться так же, как и в случае одиночных параметров. Исходные данные загружались бы в регистры, результаты возвращались бы тоже в регистры. Однако описанный способ не годится по двум причинам. Во-первых, загрузка параметров в регистры не всегда представляется возможной. Например, параметрами могут являться последовательность, состоящая из 100 записей, или 80-байтовый образ карты. Кроме того, даже если бы это удалось, то пришлось бы использовать такое количество регистров, что составление подпрограммы стало бы значительно белее трудоемким.
Используя определенные соглашения, можно организовать работу с произвольными количествами параметров произвольного формата при условии, конечно, что они представимы в машине. При этом оказывается достаточно всего одного регистра. Соглашение заключается в том, что, за исключением случая одиночных параметров, регистр 1 при входе в подпрограмму содержит адрес начала списка адресов параметров.
Пусть, например, некоторое множество чисел хранится в виде полных слов в области памяти NUMS. Ячейка N содержит информацию о количестве полных слов в области. Мы хотим обратиться к подпрограмме SORT для выполнения сортировки исходных чисел. Отсортированную последовательность нам хочется иметь по адресу SORTED, кроме того, в ячейке МАХ должно быть получено максимальное число. Итак, мы имеем четыре параметра N, NUMS, SORTED и МАХ. Придерживаясь соглашений, мы должны образовать список из этих адресов и поместить адрес начала этого списка в регистр 1 перед обращением к подпрограмме. Чтобы выполнить первое, достаточно в вызывающей программе запасти следующие константы:
ADDRLIST DC A(N)
DC A(NUMS)
DC A(SORTED)
DC A(MAX)
Это же самое можно было сделать и иначе:
ADDRLIST DC A(N,NUMS,SORTED,MAX)
Перед вызовом подпрограммы адрес начала списка помещается в регистр 1:
LA 1,ADDRLIST АДРЕС НАЧАЛА СПИСКА ПАРАМЕТРОВ В РЕГИСТР 1
L 15, = V(SORT)
BALR 14,15
Общая структура получившейся таблицы адресов изображена на рис. 13.10. При определении адресных констант можно пользоваться не DC-командами, а литералами:
LA 1, = A(N,NUMS, SORTED, MAX)
L 15, = V(SORT)
BALR 14,15
Это также приведет к резервированию ассемблером места, необходимого для размещения списка адресов. Во время выполнения адрес начала списка будет помещен командой LA в регистр 1, что и требовалось.
Если подпрограмма не содержит ошибок, то по окончании ее выполнения первые N слов области SORTED будут содержать уже отсортированный список, в ячейке МАХ будет находиться максимальное число.
Рис. 13.10. При входе в подпрограмму регистр 1 содержит адрес таблицы адресов параметров подпрограммы. Числа в квадратных скобках обозначают адреса памяти, по которым хранятся соответствующие данные. При входе в подпрограмму SORT (1)=3000.
Теперь мы перейдем к вопросу использования подпрограммой передаваемых ей параметров. Сразу же отметим, что невозможно написать подпрограмму, не зная формата параметров и предполагаемого порядка их задания. Ошибки, связанные с неверной организацией обмена информацией между вызывающими программами и подпрограммами, вызывают едва ли не наибольшие затруднения при отладке. Подобные вопросы заслуживают внимания особенно в тех случаях, когда используется групповой метод составления крупных программ. Каждому члену группы должны быть известны формат и порядок задания параметров составляемой им подпрограммы.
Возможность доступа подпрограммы к своим параметрам обеспечивается предварительной засылкой адреса начала соответствующего списка в регистр 1. Например, в случае вызова подпрограммы SORT адрес поля NUMS находится во втором слове списка, адрес начала которого хранится в регистре 1. Предположим, что адрес NUMS мы хотим поместить в регистр 3. Это достигается выполнением команды
L 3,4(1)
производящей загрузку второго слова области, адресуемой содержимым регистра 1, в регистр 3 при условии, что вызывающая программа позаботилась о предварительной записи адреса NUMS в соответствующее место списка. Если содержимое памяти соответствует рис. 13.10, то после выполнения приведенной команды мы получим (3)=4600. Аналогично по команде
L 8,12(1)
адрес МАХ (8000 в соответствии с рис. 13.10) будет помещен в регистр 8. Пусть подпрограммой уже определено требуемое максимальное число, и пусть оно находится в регистре 5. Используя команду
ST 5,0(8)
это число можно переписать в ячейку МАХ.
Рассмотрим, наконец, случай, когда само число параметров подпрограммы может быть переменным. В качестве примера можно привести подпрограмму, предназначенную для получения максимума чисел данной группы. Иногда может быть необходимо определить максимум двух чисел, иногда — трех, иногда — пяти. Возникшая проблема имеет достаточно простое решение: количество параметров задается в качестве дополнительного параметра. Адрес ячейки, содержащей значение этого дополнительного параметра, помещается первым в уже знакомом нам списке адресов. Непосредственно за ним следуют адреса действительных параметров.
Организация связей в системе DOS
При работе в системе DOS не всегда так уж необходимо выполнять требования, налагаемые системами OS и VS, хотя большинство программистов предпочитают строго придерживаться общепринятых соглашений. Это правильно, поскольку, во-первых, соблюдение общих соглашений обычно позволяет избежать внесения в программу многих ошибок, а во-вторых, составленная таким образом программа является совместимой с системой OS по крайней мере с точки зрения организации связей.
Рис. 13.11. Структура основной программы в системе DOS. Основная программа не сохраняет и не восстанавливает регистры. Управление возвращается операционной системе с помощью макро EOJ.
Существует, однако, одно довольно значительное отличие организации работы с подпрограммами в двух рассматриваемых системах. Как уже упоминалось ранее, в системах OS и OS/VS основная с точки зрения пользователя программа ничем не отличается по строению от любой подпрограммы. Поэтому основные программы должны удовлетворять всем перечисленным требованиям, касающимся сохранения и восстановления регистров, а также выполнения возврата к вызывающей программе (в данном случае OS) посредством команды BR 14.
В системе DOS основная программа представляет некоторую особенную конструкцию. В частности, она отличается от подпрограмм тем, что не предусматривает средств сохранения регистров. Таким образом, включение в программу команд STM и LM или соответствующих им макро SAVE и RETURN не является необходимым. Общая структура основной программы, предназначенной для выполнения в системе DOS, изображена на рис. 13.11.
В системе DOS прекращение выполнения основной программы осуществляется с помощью макро EOJ. В данном тексте EOJ играет особую роль: она предназначена для облегчения изучения материала тем, кто на самом деле будет программировать в системе OS. В системе DOS EOJ представляет собой макро, действительно помещеннное в системную макробиблиотеку.
Последние замечания
Мы подробно рассмотрели проблемы, возникающие при организации взаимодействия отдельных программ. Их решение, как мы видели, связано с введением некоторых общих требований. Список этих требований приведен в табл. 13.1. Соглашения относительно использования регистров можно найти в табл. 13.2.
Теперь мы можем обходиться и без введенных нами ранее макро INITIAL и EOJ. Попробуйте пропустить одну из ваших прежних программ без карты PRINT NOGEN. В колонке STMT помеченные знаком + появятся макрорасширения соответствующих предложений. Будет хорошо видно, что макро INITIAL и EOJ включают в себя обычные команды обеспечения связей, a RWD, WWD, PLN и RCD являются просто обращениями к подпрограммам.
Таблица 13.1 Взаимодействие вызывающей программы и подпрограммы 1
Функции |
Обычная реализация |
Подпрограмма: S1 Сохранение содержимого регистров (кроме основной программы в системе DOS) S2: Загрузка базового регистра S3: Восстановление регистров перед возвратом управления (кроме основной программы в системе DOS) S4:Переход по адресу, содержащемуся в регистре 14 (Макро EOJ для основной программы в системе DOS) S5:Выполнение функций вызывающей программы, если подпрограмма в свою очередь вызывает другую подпрограмму |
STM 14,12,12(13)
BALR 12,0
USING *,12
LM 14,12,12(13)
BR 14 |
Вызывающая программа: С1:Функции подпрограммы С2:Сохранение содержимого регистра 13 СЗ: Резервирование 18 слов для области сохранения и загрузка ее адреса в регистр 13 С4:3агрузка адреса списка параметров в регистр 1 С5:Загрузка адреса первого байта подпрограммы в регистр 15 С6:3агрузка адреса возврата в регистр 14 С7: Переход по адресу точки входа |
ST 13.SAVEAREA + 4 SA VEAREA DS 18F (в области данных программы) LA 13.SAVEAREA LA 1 ,= A (список параметров) L 15 ,=V (SU B R ) BALR 14,15 или BAL 14,n( 15) где n = смещение точки входа |
Рис. 13.12. Макро INITIAL.
На рис. 13.12 приведены команды из расширения макро INITIAL. Обратите внимание на то, что регистр 13 используется в INITIAL в качестве базового, а также для хранения адреса начала области сохранения.
Таблица 13.2 Использование регистров 13—15, 0 и 1 для связи с подпрограммами
Регистр |
Содержимое |
13 |
Адрес области сохранения содержимого регистров
|
14 |
Адрес возврата
|
15 |
Адрес подпрограммы
|
0 |
Числовой входной параметр
|
1 |
Указатель списка адресов или выходной числовой параметр |