10_Модульное программирование (975807), страница 2
Текст из файла (страница 2)
Связипо данным предполагают, что один модуль может иметь доступ по чтению и/или записи к областямпамяти (переменным) в другом модуле. Частным случаем связи по данным является и использованиеодним модулем именованной целочисленной константы, определённой в другом модуле (в нашемАссемблере такая константа может объявляться, например, директивой эквивалентности equ).Связи между модулями реализуются на языке машины в виде адресов, для нашей архитектурыэто одно число (близкий адрес) или два числа (дальний адрес – значение сегментного регистра исмещения в данном сегменте). 1 Действительно, чтобы выполнить команду из другого модуля, а также считать или записать значение в переменную, нужно знать месторасположение (адрес) этой команды или переменной. Заметим, что численные значения связей между модулями (значения адресов) невозможно установить на этапе компиляции модуля.
Так происходит потому, что, во-первых,компилятор "видит" только один этот модуль, и, во-вторых, будущее расположение модулей (ихсегментов) в памяти во время счёта на этапе компиляции, как правило, неизвестно.Связи между модулями будем называть статическими, если численные значения этих связей(т.е. адреса) известны сразу после размещения программы в памяти компьютера, но до начала счётапрограммы (до выполнения её первой команды).
В отличие от статических, значения динамическихсвязей между модулями становятся известны только во время счёта программы. Вскоре мы приведём примеры статических и динамических связей, как по данным, так и по управлению.На языке Ассемблера (явные) статические связи между модулями задаются с помощью специальных директив.
Директиваpublic <список имён модуля>объявляет перечисленные в этой директиве имена общедоступными (public), т.е. разрешает использование этих имён в других модулях. Общедоступными можно сделать только имена переменных, меток (в частности, имён процедур) и целочисленных констант. В некоторых модульных системах программирования про такие имена говорится, что они экспортируются в другие модули. 2 Впринципе, вместе с каждым именем может экспортироваться и его тип. Как мы уже знаем, в Ассемблере для имён, использованных в директивах резервирования памяти, тип имени определяетдлину области памяти, а для меток тип равен –1 , если метка описана в текущем модуле и –2 длявнешней метки, описанной в другом модуле (как описать внешние метки мы узнаем чуть ниже).
Остальные имена (имена сегментов, имена констант в директивах числовой эквивалентности и другие)имеют тип ноль. Тип имени в принципе позволяет проводить контроль использования этого имени вдругом модуле. Все остальные имена модуля, кроме имён, перечисленных в директивах public, являются локальными и не видны извне (из других модулей). 3Экспортируемые имена одного модуля, перечисленные в директиве public, не становятся автоматически доступными в других модулях на Ассемблере.
Для получения доступа к таким именам, этот другой модуль должен, с помощью специальной директивы, явно объявить о своём желании использовать общедоступные имена других модулей. Это делается с помощью директивыextrn <имя:тип>,...,<имя:тип>1Это верно и для случая, когда один модуль использует константу, определённую в другом модуле, таккак константа – это чаще всего тоже целое число (непосредственное значение формата i8 или i16). Кроме того, мы не будем рассматривать связи между модулями с помощью внешних файлов, в этом случае связи задаются уже не целыми числами, а чаще всего текстовыми строками – именами таких файлов.2В некоторых языках имена одного модуля по умолчанию считаются доступными из других модулей, если они описаны или объявлены в определённой части первого модуля. Например, в модуле на языке С общедоступны все имена, описанные вне функций, если про них явно не сказано, что это локальные имена модуля.3Как мы узнаем позже, имена сегментов можно сделать видимыми из других модулей, но другим, болеесложным, способом.4В этой директиве перечисляются внешние имена, которые используются в этом модуле, но неописаны в нём.
Внешние имена должны быть описаны и объявлены общедоступными в каких-тодругих модулях. Вместе с каждым внешним именем объявляется и тип, который должно иметь этоимя в другом модуле. Проверка того, что это имя в другом модуле на самом деле имеет такой тип,может проводиться только на этапе сборки из модулей готовой программы, о чём мы будем говоритьдалее.Таким образом, для установления связи между двумя модулями на Ассемблере первый модульдолжен разрешить использовать некоторые из своих имён в других модулях, а второй модуль – явно объявить, что он хочет использовать внутри себя такие имена. В языке Ассемблера общедоступные имена называются входными точками модуля, что хорошо отражает суть дела, так как тольков эти точки возможен доступ к модулю извне (из других модулей).
Внешние имена модуля называются внешними адресами, так как это адреса областей памяти и команд, а также целочисленныезначения констант в других модулях.Все программы, которые мы писали до сих пор, на самом деле состояли из двух модулей, ноодин из них с именем ioproc.asm мы не писали сами, он поставлялся нам в готовом виде авторомучебника [5]. Этот второй модуль содержит процедуры ввода/вывода, к которым мы обращаемся спомощью макрокоманд (inint, outint и других).
Теперь настало время написать программу, которая будет содержать два наших собственных модуля, а третьим, как и раньше, будет модуль с именем ioproc.asm (так как без ввода/вывода в настоящих программах нам, скорее всего, не обойтись).В качестве примера напишем программу, которая вводит массив A знаковых целых чисел и выводит сумму всех элементов этого массива. Сделаем следующее разбиение нашей задачи на подзадачи-модули. Ввод массива и вывод результатов, а также выдачу диагностики об ошибках будет выполнять головной модуль нашей программы, а подсчёт суммы элементов массива будет выполнятьпроцедура, расположенная во втором модуле программы.
Для иллюстрации использования (статических) связей между модулями мы не будем делать процедуру суммирования полностью со стандартными соглашениями о связях, она будет использовать внешние имена для получения своих параметров, выдачи результата работы и диагностики об ошибке.Текстовый файл, содержащий первый (головной) модуль нашей программы на Ассемблере, мы,не долго думая, назовём p1.asm, а файл с текстом второго модуля, содержащим процедуру суммирования массива, назовём p2.asm.
Ниже приведён текст первого модуля.; p1.asm; Ввод массива, вызов внешней процедурыinclude io.asmStksegment stackdw64 dup (?)StkendsNequ1000Data segment publicAdwN dup (?)public A,N; Входные точкиextrn Summa:word; Внешняя переменнаяDiagn db'Переполнение!',13,10,'$'Data endsCode segment publicassume cs:Code,ds:Data,ss:StkStart:movax,Datamovds,axmovcx,Nsubbx,bx; Индекс массиваL:inint A[bx]; Ввод массива Aaddbx,type Aloop Lextrn Sum:far; Внешнее имяcall Sum; Процедура суммированияoutint Summanewline5; А теперь вызов с заведомой ошибкойmovA,7FFFh; MaxintmovA+2,1; Чтобы было переполнениеcall Sumoutint Summa; Сюда возврата не будет!newlinefinish ; Вообще-то не нуженpublic Error; Входная точкаError:leadx,T; ДиагностикаoutstrfinishCode endsendStart; Это головной модульВ нашем головном модуле три входные точки с именами A,N и Error и два внешних имени:Sum, которое имеет тип дальней метки, и Summa, которое имеет тип слова.
Работу программы подробно рассмотрим после написания текста второго модуля с именем p2.asm.Comment * модуль p2.asmСуммирование массива, контроль ошибок, директиваinclude io.asm не нужна, т.к. нет ввода/вывода.Используется стек головного модуля.В конечном end не нужна метка Start*Data segment publicSumma dw?public Summa; Входная точкаextrn N:abs; Внешняя константаextrn A:word; Внешний массивData endsCode segment publicassume cs:Code,ds:Datapublic Sum; Входная точкаSumproc farpush axpush cxpush bx; Сохранение регистровxorax,ax; ax:=0movcx,Nxorbx,bx; Индекс 1-го элементаL:addax,A[bx]joVoz; возвратaddbx,type Aloop LmovSumma,axVoz: popbxpopcxpopax; Восст. регистровjoErr; Обнаружена ошибкаretErr: addSP,4; Удаление возвратаextrn Error:nearjmpError; В первый модульCode endsendНаш второй модуль не является головным, поэтому в его конечной директиве end нет меткипервой команды программы. Модуль p2.asm имеет три внешних имени A,N и Error и две входные точки с именами Sum и Summa.
Так как второй модуль не производит никаких операций ввода/вывода, то он не подключает к себе файл io.asm по директиве include. Оба наших модуля6используют общий стек объёмом 64 слова, что, наверное, достаточно, так как стековый кадр процедуры Sum невелик (всего 5 слов).Разберём работу нашей программы.
После ввода массива A головной модуль вызывает внешнююпроцедуру Sum. Это статическая связь модулей по управлению, дальний адрес процедуры Sumбудет известен головному модулю до начала счёта. Этот адрес будет расположен в форматеi32=seg:off на месте операнда Sum команды call Sum = call seg:off .Между основной программой и процедурой установлены следующие (нестандартные) соглашения о связях.