Главная страница Случайная страница Разделы сайта АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Модульное программирование на Ассемблере.
Как мы уже говорили, программа на Ассемблере может состоять из нескольких модулей. Исходным (или входным) программным модулем на Ассемблере называется текстовый файл, состоящий из предложений языка Ассемблер и заканчивающийся специальной директивой с именем end – признаком конца модуля. Среди всех модулей, составляющих программу, должен быть один и только один модуль, который называется головным модулем программы. Признаком головного модуля является параметр-метка у директивы end конца модуля, в учебниках такую метку часто называют именем Start, хотя это, как мы отмечали, несущественно и можно выбрать любое подходящее имя. Эта метка должна быть меткой команды, которая находится в одном из сегментов головного модуля. Именно этот сегмент по определению будет кодовым сегментом и содержать первую выполняемую команду всей программы. Перед началом счёта программы загрузчик установит на начало этого кодового сегмента регистр CS, а в счётчик адреса IP запишет смещение указанной метки начальной команды в сегменте кода. Как уже отмечалось, модули не могут быть абсолютно независимыми друг от друга, так как решают разные части одной общей задачи, и, следовательно, хотя бы время от времени должны обмениваться между собой информацией. Таким образом, между модулями существуют связи. Говорят, что между модулями существуют связи по управлению, если один модуль может передавать управление (с возвратом или без возврата) на программный код в другом модуле. В архитектуре нашего компьютера для такой передачи можно использовать одну из команд перехода. Кроме связей по управлению, между модулями могут существовать и связи по данным. Связи по данным предполагают, что один модуль может иметь доступ к областям памяти (переменным) в другом модуле. Частным случаем связи по данным является и использование одним модулем целочисленной константы, определённой в другом модуле (в Ассемблере такая константа может объявляться, например, директивой эквивалентности equ). Связи между модулями реализуются в виде адресов, для нашей архитектуры это одно число (близкий адрес) или два числа (дальний адрес – значение сегментного регистра и смещения в сегменте).[44] Действительно, чтобы выполнить команду из другого модуля, а также считать или записать значение в переменную, нужно знать месторасположение (адрес) этой команды или переменной. Заметим, что численные значения связей между модулями (значения адресов) невозможно установить на этапе компиляции модуля, так как будущее расположение модулей в памяти во время счёта неизвестно на этапе компиляции. Связи между модулями будем называть статическими, если численные значения этих связей (т.е. адреса) известны до начала счёта программы (до выполнения её первой команды). В отличие от статических, значения динамических связей между модулями становятся известны только во время счёта программы. Вскоре мы приведём примеры статических и динамических связей, как по данным, так и по управлению. На языке Ассемблера связи между модулями задаются с помощью специальных директив. Директива public < список имён модуля> объявляет перечисленные в директиве имена общедоступными, т.е. разрешает использование этих имён в других модулях. В некоторых модульных системах программирования про такие имена говорится, что они экспортируются в другие модули.[45] В Ассемблере вместе с каждым именем экспортируется и его тип. Как мы уже знаем, для имён, использованных в директивах резервирования памяти, тип имени определяет длину области памяти, а для меток их тип равен –1 для близкой метки и –2 для дальней. Остальные имена (имена сегментов, имена констант в директивах эквивалентности и другие) имеют тип ноль. Тип имени в принципе позволяет проводить контроль использования этого имени в другом модуле. Все остальные имена модуля, кроме имён, перечисленных в директивах public, являются локальными и не видны извне (из других модулей). Экспортируемые имена одного модуля не становятся автоматически доступными в других модулях. Для получения доступа к таким именам этот другой модуль должен, с помощью специальной директивы, явно объявить о своём желании использовать общедоступные имена первого модуля. Это делается с помощью директивы extrn < имя: тип>,..., < имя: тип> В этой директиве перечисляются внешние имена, которые используются в этом модуле, но не описаны в нём. Внешние имена должны быть описаны и объявлены общедоступными в других модулях. Вместе с каждым внешним именем объявляется и тип, который должно иметь это имя в другом модуле. Проверка того, что это имя в другом модуле на самом деле имеет такой тип, может проводиться только на этапе сборки из модулей готовой программы, о чём мы будем говорить далее. Таким образом, для установления связи между двумя модулями первый модуль должен разрешить использовать некоторые из своих имён в других модулях, а второй модуль – явно объявить, что он хочет использовать внутри себя такие имена. В языке Ассемблера общедоступные имена называются входными точками модуля, что хорошо отражает суть дела, так как только в эти точки возможен доступ к модулю извне (из других модулей). Внешние имена модуля называются внешними адресами, так как это адреса областей памяти и команд, а также значения констант в других модулях. Все программы, которые мы писали до сих пор, на самом деле состояли из двух модулей, но один из них с именем ioproc мы не писали сами, он поставлялся нам в готовом виде. Этот второй модуль содержит процедуры ввода/вывода, к которым мы обращаемся с помощью наших макрокоманд (inint, outint и других). Теперь настало время написать программу, которая будет содержать два наших собственных модуля, и модуль ioproc (так как без ввода/вывода нам, скорее всего, не обойтись). В качестве примера напишем программу, которая вводит массив A знаковых целых чисел и выводит сумму всех элементов этого массива. Ввод массива и вывод результатов будет выполнять головной модуль нашей программы, а подсчёт суммы элементов массива будет выполнять процедура, расположенная во втором модуле программы. Для иллюстрации использования связей между модулями мы не будем делать процедуру суммирования полностью со стандартными соглашениями о связях, она будет использовать глобальные имена для получения своих параметров, выдачи результата работы и диагностики об ошибке. Текстовый файл, содержащий первый (головной) модуль нашей программы, мы, не долго думая, назовём p1.asm, а файл второго модуля с процедурой суммирования – p2.asm. Ниже приведён текст первого модуля.
; p1.asm ; Ввод массива, вызов внешней процедуры include io.asm
|