Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Программирование на языке Си

..pdf
Скачиваний:
15
Добавлен:
12.11.2023
Размер:
17.16 Mб
Скачать

Глава 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

-

компоновщик (редактор связей).

Компилятор Си позволяет за одни вызов выполнить всю це­ почку преобразования исходного модуля в исполняемую про­ грамму. Для задания последовательности преобразований в команде вызова компилятора сначала указываются ключи ком­ пилятора, а затем ключи и параметры компоновщика.

Формат команды сс, вызывающей компилятор языка Си, предусматривает задание следующих параметров (в форматах команд операционных систем принято помещать необязатель­ ные элементы в квадратные скобки [ ]):

сс[-ключи] исходные модули [ключикомпоновщика] [объектные модули] [библиотеки]

где ключи - однобуквенные параметры, задающие режимы работы

компилятора. Перед каждым ключом должен стоять знак минус ('-'). После некоторых ключей могут указываться дополнительные параметры. Приведем некоторые (наи­ более часто употребляемые) ключи команды сс:

Глава 9. Подготовка и выполнение программ

449

-с - транслировать исходный модуль в объектный;

-р - провести только препроцессорную обработку исходного модуля;

-s - транслировать исходный модуль в модуль на языке ас­ семблера;

-о имя исполняемой программы —при необходимости тран­ слировать и задать произвольное (отличное от стан­

дартного "a.out ") имя для исполняемой программы; исходные модули - полные имена (с расширением "с") одного

или нескольких исходных модулей; объектные модули - полные имена (с расширением "о") тех

модулей, которые будут использованы при построении исполняемой программы;

ключи компоновщика - задают режимы работы компоновщика (для нас представляет интерес ключ -1, определяющий имя библиотеки объектных модулей):

- \библиотека_объектных_модулей

Примечание. Способ указания имени библиотеки объектных модулей объясняется ниже в разделе "Библиотеки объектных модулей" (9.1.2).

Если программа состоит из одного исходного модуля, то для построения исполняемого модуля достаточно выполнить ко­ манду (% - приглашение от операционной системы):

%сс main.с

Исходный модуль будет последовательно преобразован (см. рис. 9.2) в модуль на языке ассемблера, объектный модуль, ис­ полняемый модуль. Исполняемый модуль получит стандартное имя a.out. При повторном вызове компилятора языка Си коман­ дой сс и указании в качестве параметра команды имени другого исходного модуля вновь полученный исполняемый модуль так­ же будет иметь имя a.out, но будет соответствовать другому исходному (только что обработанному) модулю.

Для того чтобы определить произвольное имя исполняемого модуля, необходимо в команде вызова компилятора указать ключ -о и сразу за ним через пробег задать имя исполняемого модуля:

2 9 - 3 1 2 4

450

Программирование на языке Си

%сс -о begin main.с

Построение исполняемого модуля можно провести в два эта­ па с промежуточным получением объектного модуля:

%сс -с main.с

%сс -о begin main.о

В первой строке применен ключ -с, в результате чего про­ цесс обработки исходного модуля прервется, когда будет полу­ чен объектный модуль (main.о).

Во второй строке определено имя исполняемого модуля begin и в качестве параметра команды сс указано имя объект­ ного модуля (main.о), полученного на предыдущем этапе.

Для построения исполняемого модуля разработанной в гла­ ве 8 программы сортировки на основе бинарного дерева необ­ ходимо в простейшем случае перечислить при вызове команды сс в качестве параметров имена всех исходных модулей функ­ ций, из которых состоит программа сортировки:

%сс -о tree tree.с add_node.c new_node.c print.с

Однако для того чтобы при внесении изменений в исходные тексты отдельных функций не транслировать повторно и те ис­ ходные модули, в которые не вносились изменения, целесооб­ разно получить объектные модули, а затем построить исполняемую программу из полученных объектных модулей.

Таким образом, для построения исполняемого модуля разра­ ботанной в главе 8 программы сортировки на основе бинарного дерева необходимо:

1)подготовить объектные модули всех функций (символ '%'

вначале строки является подсказкой операционной системы):

%сс -с tree.с

%сс -с add_node.c %сс -с new_node.c %сс -с print.с

В результате выполнения приведенных команд транслятор, вызываемый 4 раза, создаст четыре объектных модуля: free.o, add node.o, new_node.o, print.o;

Глава 9. Подготовка и выполнение программ

451

2) построить из полученных в результате трансляции объект­ ных модулей исполняемый модуль:

%сс -о tree tree.о add_node.o new_node.o print.о

Напомним, что ключ -о и следующий за ним параметр tree задают имя исполняемой программе, отличное от a.out. Далее следует список объектных модулей, участвующих в построении исполняемого модуля программы сортировки.

При внесении изменений в один из модулей, например в мо­ дуль add_node.c, необходимо получить обновленный вариант объектного модуля add_node.o и собрать новый вариант испол­ няемого модуля.

%сс -с add_node.с

%сс -о tree tree.о add_node.o new_node.o print.о

При внесении изменений в несколько модулей необходимо вести учет этих изменений либо транслировать заново все вхо­ дящие в программный комплекс модули. Только в этом случае можно быть уверенным в том, что построенный исполняемый модуль учитывает все последние изменения в функциях и го­ ловном модуле.

9.1.1. Команда make

Команда make позволяет документировать взаимосвязи ме­ жду модулями и освобождает пользователя от рутинной работы по слежению за изменениями в модулях.

Команда make при вызове обновляет (перестраивает) испол­ няемый модуль, причем транслируются только те функции, в исходные тексты которых были внесены изменения.

Исходными данными для команды make служит файл опи­ саний зависимостей модулей. Описания зависимостей модулей и соответствующих действий хранятся в так называемом таке- файле, который по умолчанию называется makefile или Makefile. В этом случае он может не указываться при вызове команды make. При выборе для та£е-файла (файла зависимо-

29*

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 осуществляет лишь те минимальные действия, без кото­

Соседние файлы в папке книги