Концепция модульного программирования
Так же как и для структурной технологии программирования, концепцию модульного программирования можно сформулировать в виде нескольких понятий и положений:
· Функциональная декомпозиция задачи — разбиение большой задачи на ряд более мелких, функционально самостоятельных подзадач — модулей. Модули связаны между собой только по входным и выходным данным.
· Модуль — основа концепции модульного программирования. Каждый модуль в функциональной декомпозиции представляет собой «черный ящик» с одним входом и одним выходом. Модульный подход позволяет безболезненно производить модернизацию программы в процессе ее эксплуатации и облегчает ее сопровождение. Дополнительно модульный подход позволяет разрабатывать части программ одного проекта на разных языках программирования, после чего с помощью компоновочных средств объединять их в единый загрузочный модуль.
· Реализуемые решения должны быть простыми и ясными. Если назначение модуля непонятно, то это говорит о том, что декомпозиция начальной или промежуточной задачи была проведена недостаточно качественно. В этом случае необходимо еще раз проанализировать задачу и, возможно, провести дополнительное разбиение на подзадачи. При наличии сложных мест в проекте их нужно подробнее документировать с помощью продуманной системы комментариев. Этот процесс нужно продолжать до тех пор, пока вы действительно не добьетесь ясного понимания назначения всех модулей задачи и их оптимального сочетания.
· Назначение всех переменных модуля должно быть описано с помощью комментариев по мере их определения.
· Исходный текст модуля должен иметь заголовок, в котором отражены как назначение этого модуля, так и все его внешние связи. Этот заголовок можно назвать интерфейсной частью модуля. В этой части с использованием комментариев нужно поместить следующую информацию:
· назначение модуля;
· особенности функционирования;
· описание входных аргументов;
· описание выходных аргументов;
· использование внешних модулей и переменных;
· сведения о разработчике для защиты авторских прав.
· В ходе разработки программы следует предусматривать специальные блоки операций, учитывающие реакцию на возможные ошибки в данных или в действиях пользователя. Это очень важный момент, который означает то, что не должно быть тупиковых ветвей в алгоритме программы, в результате работы которых программа «виснет» и перестает отвечать на запросы пользователя. Любые непредусмотренные действия пользователя должны приводить к генерации ошибочной ситуации или к предупреждению о возможности возникновения такой ситуации.
· Из этих положений видно, какое большое значение придается организации управляющих и информационных связей между структурными единицами программы (модулями), совместно решающими одну или несколько больших задач.
· Применительно к языку ассемблера можно рассматривать несколько форм организации управляющих связей:
· Использование механизма макроподстановок, позволяющего изменять исходный текст программы в соответствии с некоторыми предварительно описанными параметризованными объектами. Эти объекты имеют формальные аргументы, что позволяет производить замещение их фактическими аргументами в процессе макрогенерации. Такая форма образования структурных элементов носит некоторый предварительный характер из-за того, что процессы замены происходят на этапе компиляции и есть смысл рассматривать их только как настройку на определенные условия функционирования программы.
· Использование механизма подпрограмм, написанных на ассемблере и структурно входящих в одну программу. В языке ассемблера такие подпрограммы называют процедурами. В отличие от макрокоманд, взаимодействие процедур осуществляется на этапе выполнения программы.
· Использование механизма подпрограмм, написанных на разных языках программирования и соединяемых в единый модуль на этапе компоновки. Эта возможность реализуется благодаря унифицированному формату объектного модуля, однозначным соглашениям по передаче аргументов и единым схемам организации памяти на этапе выполнения.
· Использование механизма динамического (то есть времени выполнения) вызова исполняемых модулей и подключения библиотек dll для операционной системы Windows.
· В качестве основных информационных связей можно выделить следующие:
· Использование общих областей памяти и общих программно-аппаратных ресурсов микропроцессора для связи модулей.
· Унифицированную передачу аргументов при вызове модуля. Эту унификацию можно представлять двояко: на уровне пользователя и на уровне конкретного компилятора.
· Унифицированную передачу аргументов при возврате управления из модуля.
Чуть позже мы подробно рассмотрим процессы, происходящие при передаче аргументов. Сейчас в качестве некоторого итога приведенных выше общих рассуждений перечислим средства языка ассемблера по осуществлению функциональной декомпозиции программы:
· макросредства;
· процедуры;
· средства компилятора ассемблера в форме директив организации оперативной памяти и ее сегментации.
Механизм процедур содержит в себе более глубокие вещи, чем тривиальная передача управления из одной точки программы в другую. В частности, он тесно связан со средствами компилятора, поддерживающими организацию памяти и сегментацию. Поэтому дальнейшее обсуждение будет посвящено более глубокому изучению функциональной декомпозиции программ с использованием механизма процедур и связанных с ним средств компилятора.
Процедуры в языке асемблера
В языке ассемблера для оформления процедур как отдельных объектов существуют специальные директивы PR0C/EN0P и машинная команда ret. Процедуры, так же как и макрокоманды, ценны тем, что могут быть активизированы в любом месте программы. Процедурам, так же как и макрокомандам, могут быть переданы некоторые аргументы, что позволяет, имея одну копию кода в памяти, изменять ее для каждого конкретного случая использования, хотя по гибкости использования процедуры уступают макрокомандам. Особых проблем с разработкой и применением процедур нет. Возможные варианты размещения процедур в программе:
· в начале программы (до первой исполняемой команды);
· в конце (после команды, возвращающей управление операционной системе);
· промежуточный вариант — тело процедуры располагается внутри другой процедуры или основной программы. В этом случае необходимо предусмотреть обход процедуры с помощью команды безусловного перехода jmp;
· в другом модуле.
Главное условие здесь то, чтобы на процедуру не попадало управление. Три первых варианта из этих четырех относятся к случаю, когда процедуры находятся в одном сегменте кода. Что же касается последнего варианта, то он предполагает, что процедуры находятся в разных модулях. А это дает нам возможность говорить уже не об одной программе, а о нескольких. Эти программы должны быть связаны между собой по управлению и по данным. Если мы разберемся с тем, как организовать такую связь, то фактически сможем выполнить функциональную декомпозицию любой большой программы на несколько более мелких. Рассмотрим, как организуется связь по управлению и по данным между программами на ассемблере. Потом мы разберем связь между программами на ассемблере, Pascal и С.
Сначала необходимо отметить один общий для всех этих трех языков момент. Так как отдельный модуль в соответствии с концепцией модульного программирования — это функционально автономный объект, то он ничего не должен знать о внутреннем устройстве других модулей, и наоборот, другим модулям также ничего не известно о внутреннем устройстве данного модуля. Но должны быть какие-то средства, с помощью которых можно связать модули. В качестве аналогии можно привести известную вам организацию связи телевизора и видеомагнитофона через разъем типа «скарт». Связь унифицирована, то есть известно, что один контакт предназначен для видеосигнала, другой — для передачи звука и т. д. Телевизор и видеомагнитофон могут быть разными, но связь между ними одинакова. Та же идея лежит и в организации связи модулей. Внутреннее устройство модулей может совершенствоваться, они вообще могут в следующих версиях писаться на другом языке, но в процессе их объединения в единый исполняемый модуль этих особенностей не должно быть заметно. Таким образом, каждый модуль должен иметь такие средства, с помощью которых он извещал бы транслятор о том, что некоторый объект (процедура, переменная) должен быть видимым вне этого модуля. И наоборот, нужно объяснить транслятору, что некоторый объект находится вне данного модуля. Это позволит транслятору правильно сформировать машинные команды, оставив некоторые их поля незаполненными. Позднее, на этапе компоновки, программа TLINK или программа компоновки языка высокого уровня произведет настройку модулей и разрешит все внешние ссылки в объединяемых модулях.
Для того чтобы объявить о подобного рода видимых извне объектах, программа должна использовать две директивы TASM: extrn и public. Директива extrn предназначена для объявления некоторого имени внешним по отношению к данному модулю. Это имя в другом модуле должно быть объявлено в директиве public. Директива public предназначена для объявления некоторого имени, определенного в этом модуле, и видимым в других модулях.
Синтаксис этих директив следующий:
Здесь имя — идентификатор, определенный в другом модуле. В качестве идентификатора могут выступать:
· имена переменных, определенных директивами типа db, dw и т. д.;
· имена процедур;
· имена констант, определенных операторами = и equ.
Тип определяет тип идентификатора. Указание типа необходимо для того, чтобы транслятор правильно сформировал соответствующую машинную команду. Действительные адреса будут вычислены на этапе редактирования, когда будут разрешаться внешние ссылки. Возможные значения типа определяются допустимыми типами объектов для этих директив:
если имя — это имя переменной, то тип может принимать значения byte, word, dword, pword, fword, qword и tbyte;
если имя —это имя процедуры, то тип может принимать значение near или faг;
если имя — это имя константы, то тип должен быть abs.
Покажем принцип использования директив extrn и public на схеме связи двух модулей Модуль 1 и Модуль 2 (см. листинги 14.1 и 14.2).
Листинг 14.1. Модуль 1
Листинг 14.2. Модуль 2
Рассмотренная нами схема связи — это, фактически, связь по управлению. Но не менее важно организовать информационный обмен между модулями. Рассмотрим основные способы организации такой связи.