logo search
Подбельский Фомин_Программирование на языке СИ_

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 - стандартное имя исполняемого модуля

(исполняемой программы);

сс - компилятор языка Си;

as - компилятор языка ассемблера;

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

От традиционной для почти всех операционных систем схемы подготовки исполняемых программ схема на рис. 9.2 отличается тем, что в UNIX трансляция исходного модуля ведется на язык ассемблера и исполняемый модуль, если не указано другого, имеет стандартное имя a.out. Выбор фиксированного имени объясняется тем, что UNIX в свое время создавалась для удобной разработки и отладки программ. В режиме отладки нет необходимости хранить промежуточные версии исполняемых программ и вполне можно называть их одним именем.

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

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

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

[объектные_модули] [библиотеки]

где

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

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

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

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

имя дополняемой программы - при необходимости транслировать и задать произвольное (отличное от стандартного "a.out") имя для исполняемой программы; исходные_модули - полные имена (с расширением "с") одного или нескольких исходных модулей;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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