Б. Страуструп - Язык программирования С++. Специальное издание, 3-изд. Бином. 2004 (1160791), страница 53
Текст из файла (страница 53)
Когда единицей компиляции является файл, весь он должен быть заново перекомппяирован при внесении изменений (независимо оттого, насколько они малы) в него пли в другой файл, от которого он зависит. Даже в случае програлгмы скромных размеров количество времени, потраченное на перекомпиляцию, может быть значительно снижено за счет разбиения программы на файлы подходящего размера. Пользовате. гь предоставляет компилятору исходный файл. Сначала производится обработка файла прспроцессором; то есть делаются макроподстановки Я 7,8) и выполняются директивы зг1пс1иг1е, вставляющие код пз заголовочных файлов Я 2.4.1, й' 9.2.1). Результат обработки препроцессором исходного файла называется единицей ~лрансляции.
Она и является тем, над чем работает компилятор, и что, собственно, 242 Глава 9.Исходные файлы и программы описывают правила языка С еж. В этой кш|ге я провожу различие между исходным файлом и единицей трансляции только там, где это необходимо, чтобы отличать то, что видит программист, от того, что получает компилятор. Д.ля того чтобы сделать возможной раздельную ком|гнляцию, програмьшст дол.кен предоставить объявления, дающие информацию о типах, необходимую для ана| ива единицы трансляции отдельно от остальной части программы. Объявления в программе, состоящей из нескольких раздельно компилируемых частей, должны быть согласованы абсолютно так же, как и в программе, состоя|цей пз единственного исходного файла.
В вашей системе должны оыть средства, помогающие убедиться в этом. В частности, компоновщик может обнаружить много несогласованностсй различных типов. Коз|поповщин ()пйег) является программой, которая связывает вместе раздельно откомпилированные части. Компоновщик иногда называют (и неправильно) загрузчиколь Компоновка' может быть полностью завершена до того, как программа запускается на выполнение.
С другой стороны, новый код мо кет быть добавлен к программе («динамически скомпонован е) уже после загрузки. Организацию програмл|ы в виде набора исходных файлов обычно называют у)пзичегкой с|пруюлурой программы. Физическое разбиение программы на различные файлы должно определяться, исходя пз лоп|ческой структуры программы. Те же самые соображения о зависимости, которые помогиот при разделении программы на пространства имен, относятся и к разбиению на исходные файлы. Однако, поп|ческая и физическая структуры программы не обязаны быть идентичными. Например, может оказаться полезным использовать несколько файлов для хранения функций из одного пространства имен, храниты|абор определении пространств имен в одном файле или поместить определение пространства в нескольких файлах Я 8.2.4).
Сначала рассмотрим некоторые технические детали, имеющие отношение к компоновке, и затем обсудим два способа разбиения программы калькулятора (з б.), э 8.2) на файлы. 9.2. Компоновка Нчепа функций, классов, |паблонов, переменных, пространств имен и перечислений должны быть согласованы во всех единицах компиляции, если только этц имена явно не определены как локальные. Задачей программиста является обеспечение то|.о, чтобы каждое пространство имен, класс, функция и т. д. были правильно объявлены в каждой единице транс.ля- ции, в которой они используются, и чтобы все объявления одного и того же объекта были согласованы: В)ап ример, рассмотрим два файла: 0)де)усс тех=К |пей() ! ||" некоторые действия /* ) 0,деус: ех|егл |я| х, пг|д)); ооЫу)) (х =Я, ) Компоновку (йп)сшд плп !)п)саде) по-русски часто также называют «связываипемь яли «редактированном связей ь.— Пр|ояеи.
ред, 9.2. Компоновка 24З Переменная х и функцпя 7Д~, используемые в я() нз !т1е2 с, определены ву!1еге. Ключевое слово ех~егп означает, что объявление х в уг!е2с является (только) обьявлением, а не определением Я гк9). Если бы х была пницпализирована, ех1егп было бы проигнорировано, потому что объявление с инициализацией всегда является определением. Любой объект должен быть определен в программе только один раз. Он может быть объявлен много раз, но типы должны совпадать. Например: О)(1ег.с: !и!х=1; гл!Ь= 1; ех!егл 1п! с; О Яе2.с: !л!х, О означает сп ! х = 0; ех!егл НвиЫе Ь; ех!егл 1л! с; В примере имеется три ошибки: х определена дважды, Ь объявлена дважды с различными типам п и с дважды обьявлена, но не определена.
Ошибки такого типа (огпибки компоновки) не могут быть обнаружены компилятором, который в каждый момент времени рассматривает только один файл. Однако, большинство таких ошибок выявляются компоновщиком. Обратите внимание, что переменная, определенная без инициализации в глобальной области видимости или в пространстве имен, инициализируется по умолчанию. Этого не происходит с локальными переменными Я й.9.5, 9 10зй2) или с объектами, создаваемыми в свободной памяти Я 6.2.б).
Например, следующий фрагмент программы содержит две ошибки: О/!(е(.с: 1лг х, !л 11 () ( ге!игл х; ) ,",',Яе2.с: т1х, !лгд() ( ге!игп7'(); ) Вызов Д) в7г!е2.с является ошибкой, потому что 7'() не была объявлена вЯ!е2.с. Программа не будет скомпонована также и потому, что переменная х определена дважды.
Отметим, что этн ошибки не явля|отса таковыми в С Я В.2.2). Если имеется нмя, которое может быть использовано в единице трансляции, отличной от той, в которой оно было определено, то говорят, что имеет место вневпгяя кам~оновка (ек(егпа!!(п)щйе). Все имена в предыдущих примерак компоновались внешним образом. Про имя, на которое можно ссылаться лишь в той единице трансляции, в которой оно определено, говорят, что оно компонуется внутренним образом. Встроенная Лп)(пе) функция Я 7.1.1, б 10.2.9) должна быть определена — идентичнымп определениями (б 9.2,3) — в каждой единице трансляции, в которой она используется.
Следовательно, следующий пример является не просто образцом плохого вкуса — он вообще недопустим: ОМ(е(.с: т 1гпе 1п!Д!л! 1) ( ге!игл 1, ) 244 Глава 9. (4сходные файлы и программы Яг!е2.с: т1!пе !п1) (!п1 ! )( ге1игп !Я-1, ) К сожалению, эту ошибку трудно обнаружить любой реализации компилятора, и следующая комбинация внешней компоновки и встроенной функции, хотя и выглядит совершенно логично, запрещена, чтобы облегчить жизнь авторам компиляторов: 0,л!е!.с: ех1егп !и!!ле ип д (т1 !); инд (!п1!!(ге1иглд'(!),) уу тииоха: у(! не определена // и зтод единияе тра нсяя или 0Я!е2.с: ех гегп !и!1п е т1 у (1л1 !) ( ге 1игп !и!; ) 11о умолчанию сопз1 Я 5А) и 1урес(е7" (9 4.9.7) подразумевают внутреннюю компоновку. Следовательно.
следующий пример допустим (хотя потенциюгьно может привести к ошибкам): 777)!е !.с! 1уреИе7" !л1 Т, солз1!л1х = 7, 7,!Р!е2.с: 1урес(еу'иоЫ Т, сопз1!лгх = 8, Глобальные переменные, которые являются локальными для одной едцннцы компиляции, являются типичным источником ошибок, и пх лучше избегать. Для обеспечения согласованности лучше помещать глобальные соле! и !л(!пе только в заголовочные файлы Я 9.2.1).
Можно заставить константу компоноваться внешним образом путем ее явного обьявления: 17!)!е!.с: ех1егл соля1 !п1 а = 77; У!!7)!е2.с: ех1егп соля! т1а; иоЫу() ( сои1 «а « '~л', ) В этом примере д() выведет 77. Для того чтобы имена в данной единице компиляции были.локальными, можно воспользоваться неименованными пространствами имен (э 8.2.5). Эффект от применения неименованного пространства напоминает внутреннюю компоновку.
Например: О,(!е !, с: латезрасе ( с!аязХ(!* "/) 245 9.2. Компоновка оо1дД; !лгй 0' 1'1711е2.гс с1аевХ( 1* ... '1); оо1дт !!, 1л1с, '! -. Фукскция !'0 в!!1е1.с не та же самая, что и Ц из !!1е2.с. Иметь имя, локальное в рамках едшшпы компиляции, и одновременно использовать то же имя для сущности с внешней компоновкой где-нибудь еще, означает искать неприятности.
В програмьсах на С н старых программах на С+-' ключевое слово з!а11с используют !что приводит к путанице) для указания <использовать внутрепнкпо компоновку» ! 5 Б23), ! !е пользуйтесь з1а!1с кроме как внутри функций 15 712) и классов !5 1024). 9.2.1. Эаголовочные файлы ите!иде "вклсочлемая комлоленсла" заменяет строку, содержащую и1лс1иг!е, па содержимое файла включаемая компонента. Содержимое этого файла должно быль исходным текстом на С+-", потому что его будет обрабатывать компилятор.
г Для включения библиотечных заголовочных файлов, пользуйтесь угловыми скобками < и > вместо кавычек. Например: О из стандартного котел лги вклклтелллхфаилов /! из тектдцего котел оги и1лс1иде <юзггеат> итс1иде "тулеастег!с" К сожалению, пробелы внутри < > илп" "имеют значение в директиве включения: // не найдет соз1геош> ислс!ис1е< гоз!геат > Может показаться экстравагантной перекомпиляция файла каждый раз при его включенип куда-нибудь, но включаемые файлы, как правило, содерзкат только объявления, а не код, требуюьчий серьезного анализа со стороны компилятора.
Ьолес того, большинство современных реализаций С-»+ обеспечивают некоторую форму предкомпиляции заголовочных файлов с целью минимизации затрат, требуемых для повторной компиляции одного и того же заголовочного файла. Во всех объявлениях типы одних и тех же об ьсктов, фупкцшй, классов и т. д. должны быть согласованы. Следовательно, исходный код, обрабатываемый компилятором и за~ем компоновщиком, должен быть согласован. Одним, далеким от совершенства, но простым методом достижения соглас:ованностн объявлений в различных единицах трапсляцшл является включение (!11лс!иг)е) заголово шых файлов, содержащих информацию об интерфейсе в исходные файлы, в которых содержи.гся исполняемый код и/или определения данных.