![](/user_photo/_userpic.png)
книги / Программирование на языке Си
..pdfГлава 9 ПОДГОТОВКА И ВЫПОЛНЕНИЕ
ПРОГРАММ
Упрощенная схема подготовки программ, существующая во многих операционных системах, изображена на рис. 9.1 (см. также §2.1, рис. 2.1):
Рис. 9.1. Упрощенная схема подготовки исполняемой программы
Текст программы, набранный в любом текстовом редакторе и сохраненный в файле в виде последовательности кодов сим волов, называется исходным модулем. Имена файлов, как пра вило, имеют сложную структуру: кроме собственно имени файла (произвольно назначаемого пользователем) указывается расширение, задающее тип файла. Файлы, содержащие тексты программ и функций на языке Си, обычно имеют расширение "с". В тех операционных системах, где на уровне файловой сис темы не различаются строчные и прописные буквы (MS-DOS или Windows), расширение может иметь и такой вид - "С". В любом случае имя файла от его расширения отделяется точкой.
Исходные модули обрабатываются компилятором, в резуль тате чего получается промежуточный модуль, называемый объ ектным модулем. Объектный модуль состоит из двух основных частей: тела модуля, представляющего собой про грамму в кодах команд конкретной ЭВМ, и заголовка, содер жащего внешние имена (имена переменных, используемых в
444 |
Программирование на языке Си |
данном модуле, но определенных в других модулях). Эта ин формация необходима для построения из набора объектных мо дулей программы или программной системы, готовой к выполнению.
Объектные модули обрабатываются компоновщиком, кото рый строит исполняемую программу, содержащую только ко манды ЭВМ. В зависимости от операционной системы испол няемую программу могут называть исполняемым файлом, ехемодулем, загрузочным модулем и т.п. При построении испол няемой программы к объектным модулям, полученным из ис ходных модулей программной системы, подключаются объект ные модули из одной или нескольких библиотек, содержащих, например, объектные модули функций ввода-вывода, вспомога тельных библиотечных функций (обработка строк, массивов, выделение динамической памяти и т.д.).
Существование промежуточного объекта (модуля) в цепочке построения готовой к выполнению программы позволяет:
•сделать систему программирования гибкой и удобной в эксплуатации за счет предварительной разработки библио тек объектных модулей, содержащих множество вспомога тельных функций;
•разрабатывать и использовать собственные библиотеки объектных модулей, содержащие функции, общие для от дельных частей программной системы;
•при модификации программной системы перестраивать
только части, подвергшиеся доработке.
На различных этапах разработки программы или программ ной системы программистом может применяться разная техно логия сборки готовой к выполнению программы, например:
•главная функция и все подготовленные программистом вспомогательные функции программной системы находят ся в одном текстовом файле;
•главная функция и вспомогательные функции находятся в разных текстовых файлах, но собираются в один исходный модуль с помощью препроцессорных команд ^include;
•главная функция и вспомогательные функции находятся в разных текстовых файлах, транслируются отдельно, и ис
Глава 9. Подготовка и выполнение программ |
445 |
полняемая программа собирается компоновщиком из объ ектных модулей;
•вспомогательные функции транслируются отдельно, отла живаются на тестовых примерах, и из них формируют лич ную библиотеку объектных модулей, которая применяется для сборки программной системы.
Пример размещения текста программы в нескольких файлах приведен в §8.1. Программа для исследования псевдослучайной последовательности может быть собрана в один файл с помо щью директив #include. Рассмотрим вариант такого же по строения программной системы с объединением всех исходных модулей в один файл с помощью препроцессорных директив #include на примере работы с базой данных о сотрудниках предприятия из §8.2.
Исходный модуль, объединяющий все функции и объекты программы, будет иметь следующую структуру:
•определения внешних переменных, т.е. объектов, исполь зуемых во вспомогательных функциях (массив структур st типа struct person, управляющие переменные);
•главная функция и вспомогательные функции:
• main() |
{.... |
.} |
/* |
главная функция */ |
• init() |
{.... . |
} |
/* |
инициализировать базу данных */ |
• find() |
{.... . |
} |
/* |
поиск в списке занятых элементов */ |
• fr() |
{.... . |
} |
/* |
возвратить освобожденный элемент в |
|
|
|
|
список свободных элементов */ |
• delete() |
{.... . |
} |
/* |
удалить сведения о сотруднике *7 |
• input() |
{.... . |
} |
/* |
ввод данных о новом сотруднике */ |
• print() |
{.... . |
} |
/* |
печать содержимого базы данных */ |
• save() |
{.... . |
} |
/* |
сохранить базу данных в файле *1 |
• load() |
{.... . |
} |
/* |
загрузить базу данных из файла */ |
Собрать в один файл все модули программы можно с помо щью препроцессора, если включить в текст основной функции набор препроцессорных директив:
/* Определения внешних переменных, т.е. объектов, используемых во вспомогательных функциях (массив структур st типа struct
446 |
Программирование на языке Си |
person, управляющие переменные) */ |
|
#include |
иinit.с" |
♦include |
"find.с" |
♦include |
"fг .с" |
♦include |
"delete.с" |
♦include |
"input.с" |
♦include |
"print.c" |
♦include |
"save.c" |
♦include |
"load.c" |
main() |
|
{ ..................................)
В фазе препроцессорной обработки компилятором будет со бран модуль, содержащий исходные тексты всех функций про граммной . системы. Способ препроцессорной сборки более удобен по сравнению со способом размещения исходных моду лей в одном файле, так как каждая функция находится в отдель ном файле, что облегчает модификацию текстов функций. Однако оба эти способа являются технологичными только для малых (или очень малых) программных систем.
Даже небольшая программная система, если она хорошо спроектирована, часто содержит значительное количество функций и текстов определений и описаний внешних (глобальных) объектов, размещенных в отдельных файлах. Не обходимо заметить, что сопряжение функций по данным с по мощью внешних переменных (объектов) или с помощью пара метров, передаваемых в функции, следует предусмотреть на эта пе разработки исходного текста программной системы.
Развитие любой программной системы приводит к тому, что в итоге ее структура становится сложной, и программисту труд но удержать в голове логическую схему этой структуры. Посто янно. следить за файлами, которые требуют перекомпиляции после внесения изменений, становится невозможным.
Современные операционные системы и системы программи рования предлагают пользователю средства, в той или иной степени решающие задачу автоматизации процедуры поддерж ки разрабатываемой программной системы.
Глава 9. Подготовка и выполнение программ |
447 |
Рассмотрим соответствующие инструментальные средства в таких популярных операционных системах, как UNIX, MS-DOS, Windows.
9.1. П одготовка програм м в операционной
системе UNIX
В UNIX для документирования взаимосвязей между модуля ми и для автоматизации построения программной системы слу жит команда make. Исходной информацией для команды make служит описание взаимосвязей модулей, на основе которого ко манда make собирает программную систему. При дальнейших модификациях текстов отдельных частей системы описание взаимосвязей модулей позволяет команде make перестраивать при сборке программной системы только ту часть системы, ко торая подверглась модификации.
Прежде чем перейти к рассмотрению работы команды make, построим программу сортировки на основе бинарного дерева, описанную в главе 8, традиционным способом, вводя необхо димые команды через командную строку.
Программа сортировки состоит из 4 функций, размещенных в отдельных файлах:
• |
tree.c |
- главный модуль; |
• |
add_node.c |
- функция заполнения новой вершины дерева; |
• |
new_node.c |
- функция создания новой вершины дерева; |
• |
print.c |
- функция обхода и печати содержимого вер |
шин дерева.
Схема подготовки исполняемой программы в UNIX приведе на на рис. 9.2.
От традиционной для почти всех операционных систем схе мы подготовки исполняемых программ схема на рис. 9.2 отли чается тем, что в UNIX трансляция исходного модуля ведется на язык ассемблера и исполняемый модуль, если не указано друго го, имеет стандартное имя a.out. Выбор фиксированного имени объясняется тем, что UNIX в свое время создавалась для удоб ной разработки и отладки программ. В режиме отладки нет не
448 |
Программирование на языке Си |
обходимости хранить промежуточные версии исполняемых программ и вполне можно называть их одним именем'.
Рис. 9.2. Схема подготовки исполняемой программы в UNIX:
*.с |
- исходный модуль на языке Си; |
*.а |
- модуль на ассемблере; |
*.о |
- объектный модуль; |
a.out - стандартное имя исполняемого модуля (исполняемой программы);’
сс- компилятор языка Си;
as |
- |
компилятор языка ассемблера; |
Id |
- |
компоновщик (редактор связей). |
Компилятор Си позволяет за одни вызов выполнить всю це почку преобразования исходного модуля в исполняемую про грамму. Для задания последовательности преобразований в команде вызова компилятора сначала указываются ключи ком пилятора, а затем ключи и параметры компоновщика.
Формат команды сс, вызывающей компилятор языка Си, предусматривает задание следующих параметров (в форматах команд операционных систем принято помещать необязатель ные элементы в квадратные скобки [ ]):
сс[-ключи] исходные модули [ключикомпоновщика] [объектные модули] [библиотеки]
где ключи - однобуквенные параметры, задающие режимы работы
компилятора. Перед каждым ключом должен стоять знак минус ('-'). После некоторых ключей могут указываться дополнительные параметры. Приведем некоторые (наи более часто употребляемые) ключи команды сс:
452 |
Программирование на языке Си |
стей) произвольного имени часто назначают имена, начинаю щиеся с заглавной буквы, так как при просмотре содержимого каталога (в UNIX для просмотра используется команда Is -1) они будут располагаться в верхней части списка модулей каталога, что облегчает поиск и распознавание таке-$ампоъ.
Приведем текст файла зависимостей модулей для программы сортировки на основе бинарного дерева. В простейшем случае таке-§шп будет выглядеть так:
tree: |
tree.о add_node.o new_node.o |
print.о |
c c -о |
tree tree.о add_node.о new_node.о |
print.о |
В первой строке первым параметром перед символом ука зывается имя так называемого целевого файла, а после символа приводится список имен файлов, от которых зависит целевой файл. Эта строка информирует команду make о необходимости выполнить команду, записанную в следующей строке, если ка кой-либо из объектных модулей (файлы *.о) имеет более позд нюю дату модификации по сравнению с целевым файлом tree, являющимся в нашем примере исполняемым модулем програм
мы сортировки.
Если какой-либо из исходных модулей на препроцессорном уровне включает файл с текстом части программы, то описание зависимостей файлов необходимо дополнить соответствующей строкой по следующему образцу:
prog.о а .о : def.h
Здесь указано, что объектные модули prog.o и а.о зависят от включаемого (директивой #include) файла def.h.
Помимо описаний зависимостей, команда make использует в процессе своего выполнения набор так называемых встроенных правил. Одно из основных правил: повторно должны быть от компилированы лишь те исходные модули, дата последней мо дификации которых оказалась старше даты создания соответствующего им объектного модуля. Компилируются* ко нечно, и те исходные модули, для которых соответствующие объектные модули не существуют. Таким образом, команда make осуществляет лишь те минимальные действия, без кото