C++ Программирование в среде С++ Builder 5

         

Проблема раздельной компиляции


Когда-то давно программа для машины вроде БЭСМ-4, написанная на языке Алгол-60 или FORTRAN, состояла из одного-единственного файла, а точнее, являлась просто одной “колодой” перфокарт. Также и первые версии языка Pascal для PC (например, Turbo Pascal 2) допускали, по существу, компиляцию только программ, состоящих из единственного исходного файла. Компилятор “видел” сразу весь текст программы и мог непосредственно проконтролировать, например, количество и тип фактических параметров в вызове процедуры, — соответствуют ли они тем, что указаны в заголовке ее определения (формальным параметрам). Компилятор транслировал программу сразу в машинный код (исполняемый файл).

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

Компилятор C/C++ генерирует стандартные объектные файлы с расширением .obj. (Их формат определен фирмой Intel и не зависит от конкретной операционной системы.) Файлы эти содержат машинный код, который снабжен дополнительной информацией, позволяющий компоновщику разрешать ссылки между объектными модулями. Так, в начале файла формируются две таблицы: таблица глобальных символов (это имена объектов, определяемых в данном файле, на которые могут ссылаться другие модули программы) и таблица внешних ссылок (имена объектов в других файлах, к которым обращается данный модуль). Пользуясь информацией этих таблиц, компоновщик модифицирует код, подставляя в него соответствующие адреса.

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


В языке Turbo Pascal (и позднее — в Delphi) эта проблема была решена благодаря определению специального формата промежуточных файлов. Эти “объектные” файлы (в Delphi они имеют расширение ,dcu) содержат, помимо всего прочего, информацию о параметрах процедур и функций, об определяемых в модуле типах данных (классах) и т.д.

C/C++ был задуман как максимально универсальный язык, и потому он не может использовать нестандартный формат объектных файлов. Это сделало бы невозможным, например, создание файлов подпрограмм, которые могли бы включаться в программы, написанные на других языках. Возможность раздельной компиляции обеспечивается в C/C++ применением заголовочных файлов, присоединяемых к компилируемым исходным файлам на этапе препроцессорной обработки. Заголовки содержат прототипы функций, объявления типов и другую информацию, необходимую для раздельной компиляции исходных модулей программы.

Заголовочные файлы (они имеют расширение .h или .hpp) подключаются к компилируемому файлу исходного кода (.с или .срр) с помощью директивы препроцессора #include, за которой следует имя заголовочного файла в кавычках или угловых скобках, например:

#include <stdlib.h>

#include "myfile.h"

Препроцессор заменяет директиву #include содержимым указанного файла; после завершения препроцессорной обработки полученный текст передается компилятору, который транслирует его в объектный код.



Во многих системах программирования на C/C++ препроцессор составляет единое целое с компилятором (это верно и для C++Builder). Тем самым ускоряется компиляция исходных файлов, и нет необходимости создавать промежуточный файл, содержащий обработанный препроцессором код. Однако в C++Builder имеется отдельный препроцессор срр32.ехе, запускаемый из командной строки. Вы можете им воспользоваться, если нужно просмотреть текст, получаемый в результате препроцессорной обработки; это может быть полезно, например, при отладке макросов препроцессора. Директивы препроцессора будут подробно рассматриваться в 4-й главе.

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


Содержание раздела