Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 55
Текст из файла (страница 55)
с, может предположить, что определение Я из этого файла единственное в программе, и спокойно изменить его. Это приведет к трудноуловимой ошибке. Правило ООК нацелено на то, чтобы можно было включать определение класса из единственного файлового источника в разные единицы трансляции. Например: У файл з.Ь: игис! Я Нп! а; сааг Ь ! ); »оЫ !'(5" ) У файл !)!е!.с: ))!пс1иае "з. Ь" гУ используем Г) ) здесь 9.2. Компоновка ()~п)га9е) 26№ // файл /)!е2.с: №№пс1ийе "л.
Ь" ЫГ)Х* р) )/* ... */) или в графической форме: Приведем три ошибочных примера, в которых правило ООВ нарушается; // файл /)!е!. с; ллгисг Я1 ) 1лм а; сваг Ь; ); лггисг Я! Ппг а; сЬаг Ь | ); // еггог: повторное определение Здесь ошибка заключается в том, что нельзя дважды определять структуру в одной и той же единице трансляции.
А в следующем примере ошибка состоит в том, что определения структуры Я2 в разных файлах содержат разные имена членов структуры. //файл 111е!,с: лггис! 52 ( гпг а; сааг Ь; ) г // файл /)1е2.с: лггисг Я2 )№пг а; сьаг ЬЬг ); // еггог В третьем ошибочном примере два определения структуры ЯЗ полексемно идентичны, но смысл лексемы Х в разных файлах разный: // файл ))!е!.с: грреаег ии Х; лиис153 (Ха; сваг Ьг ); // файл /)1е2. с: гуреаег слог Х; лггист 53 ) Х а; слог Ь г ); // епог Проверка согласованности определений классов в разных единицах трансляции выходит за пределы возможностей большинства реализаций С++.
Как следствие, нарушение правила ООК влечет за собой возникновение тонких ошибок. К сожалению, размещение общих определений в заголовочных файлов с последующих их включением директивами №1ис№ив)е не гарантирует отсутствия последней из представленных в примерах форм нарушения правила ООВ. Локальные операторы гуреае2 и макросы могут менять смысл обьявлений из заголовочных файлов: //файл кЬ: лтгисс 5 ) Ро!тм а; сааг Ь | ); // файл/)!е!.с: №йе3гпе Ро1пГ !пг 262 Глава 9. Исходные файлы и программы №!пс!иае "л. й" УУ ... УУ файл 7)1е2.с: с!аев Ро№п!( /* ... *У ) ) №1пс1иае "л. й" У... Наилучшей зашитой от этой проблемы является создание как можно более самодостаточных заголовочных файлов.
Например, если бы класс Рот! был объявлен в л. й, ошибка была бы обнаружена. Определение шаблонов можно включать в разные единицы трансляции до тех пор, пока выдерживается правило 01Ж. Кроме того, если шаблон является экспортируемым (определен в некотором файле с модификатором ехрог(), то в ином файле его можно использовать при наличии одного лишь объявления: УУ файл )) 1е1.сг ехрог! гетр1аге<с!аеа Т> Т пг(се ( Т !) (гегигп !«!! ) УУ файл !)1е2.сг !етр№аге<с!алл Т> Т ппсе (Т !); УУ объявление т! е (№щ!) (гегигп ь«1се (1); ) Ключевое слово ехроггозначает «доступно из другой единицы трансляции» (5) 3.7).
9.2.4. Компоновка с кодом, написанном не на языке С++ Нередко программы, написанные в основном на С++, содержат фрагменты кода на других языках. И наоборот, программы на других языках используют фрагменты на С++. Добиться корректного взаимодействия таких фрагментов между собой непросто, даже для фрагментов на С++, компилируемых разными компиляторами. Действительно, разные языки и разные компиляторы для одного языка могут по-разному использовать регистры процессора для хранения аргументов, по-разному выполнять раскладку стековых фреймов, могут разниться размещением в памяти отдельных символов и строк символов, целых чисел, чисел с плавающей запятой, могут по-разному передавать имена компоновщику, могут различаться глубиной проверки типов, необходимой компоновщику.
Определенную помощь оказывает явное указание соглашений компоновки (!(пйояе сопгепйоп) в объявлениях с модификатором ехгегп. В следующем примере объявляется функция в!геру() стандартной библиотеки языков С/С++ и явно указывается, что компоновку нужно осуществлять по правилам языка С: ехгегп "С" сйаг* ягсру (сйаг*, сопл! сйаг* ); Эффект от такого объявления отличается от эффекта обычного объявления ехгегп сйаг* в!геру (сйаг*, сопя сйак*); только соглашением о вызове функции в!геру О . Директива ех!егп " С" особо полезна из-за тесной связи между языками С и С++. Важно понимать, что С в ехгегп "С" означает именно соглашение о компоновке, а не язык сам по себе. Часто, ех(егп "С" применяется с целью компоновки с процедурами, написанными на языке Рог(гап или ассемблере, которые удовлетворяют соглашениям языка С.
9.2. Компоновка ((пйаое) 263 ехгегп "С" !п(1'(); шгх() ( ге!игал (! ) ) /I ег.ог: неохсиданный аргумент Так как добавление ехгегп "С" к большой группе объявлений утомительно, то предусмотрен механизм группового использования этой директивы: ехгегп "С" ( сйаю * я!геру (сйаг*, сопя( сйаг*); ии я!гетр (соня! сйаг*, сопи сйаг*); 1пг зи(еп (сопя( сйаг* ); р.. ) Эту конструкцию часто называют блоком спеяификояии компопоеки (1(пйояе й(осй) и в нее можно заключить все содержимое заголовочного файла языка С, чтобы сделать его применимым в программах на языке С++. Например: ехяегп "С" ( $1пс1иде <ягггпа. й> Такая техника, преврашаюшая заголовочный файл языка С в заголовочный файл языка С++, на практике используется очень час~о.
Кроме того, применив еше и директивы условной компиляции 57.8.)), можно получить заголовочный файл, одинаково пригодный для С и С++: ((1Яе1 ср(ияр!из ехтегп "С" ( ()епйу сйаг* яггсру (сйаг*, сопя( сйаг*); 1пг зггстр (сопзг сйаг*, сопя! сйаг*); ии я1г!еп (сопя( сйаг*) 1 У,.. $11г(еу ср!ияр1ия ) ()етйу" Предопределенный макрос ср1ияр1ия позволяет исключить конструкции С++, когда файл используется в качестве заголовочного файла С.
Директива ехгегп "С" специфицирует соглашение о компоновке (и только) и не влияет на семантику вызовов функции. В частности, функция, объявленная с ех1егп "С", по-прежнему подчиняется проверке типов и правилам преобразования аргументов, принятых в С++, а не более слабым правилам языка С. Например: Глава 9. Исходные файлы и программы 264 В блоке спецификации компоновки допускаются любые объявления; ехгегп "С" ( !и! я1; ех!егп !п! 82; У определение У объявление (но не определение) Никакого дополнительного влияния на области видимости и время жизни переменных при этом не оказывается, так что я1 по-прехснему является глобальной переменной, причем эта переменная определена, а не просто объявлена. Чтобы объявить переменную (а не определить ее), нужно явным образом использовать модификатор ехгегп.
Например: ех!егп "С" !п! лЗ; () объявление (не определение) Выглядит немного странно, но на самом деле, это просто следствие неизменности смысла объявлений с модификатором ехгегп при добавлении "С" (аналогично неизменности смысла содержимого файла при заключении его в блок спецификации компоновки). Имя, которое должно компоноваться в стиле языка С, можно объявлять в пространстве имен. Это влияет лишь на то, как имя видимо в коде С++, но не на его представление компоновщику. Функция рг1пф ) из пространства имен вв! служит типичным примером: 1!пс!иле <ела!!о> го!й1'() ( вЫ::рг!п)1'("Не!!о, "); рг(п(1("ыог!й! '~п" ) ! ) У о/с (( еггог: нега глобальной рг!пф() Несмотря на обращение в виде вЫ::рг!п~(), вызовется-то по-прежнему старая добрая стандартная функция рг1пб(() языка С (82Е8).
Это позволяет нам заключать библиотеки с С-компоновкой в произвольные пространства имен и не засорять единственное глобальное пространство имен. Подобная гибкость отсутствует для библиотек, содержащих глобальные определения функций с компоновкой в стиле С++. Причина состоит в том, что компоновка С++-сущностей учитывает пространства имен, и объектные файлы в явном виде содержат информацию об этом (то есть используются пространства имен нли нет). 9.2.5. Компоновка и указатели на функции При смешении фрагментов кода на С и С++ часто приходится передавать указатели на функции одного языка функциям другого языка. Если обе реализации двух языков имеют общие соглашения о компоновке и вызове функций, то проблем при этом не возникает.
В общем же случае на это рассчитывать нельзя, так что требуется предпринимать специальные меры, гарантирующие, что вызов функции будет осуществляться надлежащим образом. Когда соглашение о компоновке указывается в объявлении, то его действие распространяется на все типы, имена и переменные в этом объявлении. Это делает 9.3. Применяем заголовочные файлы 265 возможными странные на первый взгляд, но иногда важные комбинации соглаше- ний о компоновке. Например: щге№вг(пг (*ГТ) (соль( юЫ*, сошг юЫ*); Р РТ имеет С+а компоновку ехгвгп "С" ( ~уроде/шг (*СГТ) (сошг юЫ*, сошг юЫ*); юЫ овоп ( юЫ* р, в(гв г и, же г вг, СЕТ стр); ) // СРТ имеет С компоновку //стр имеет С компоновку юЫ Ьоп(иоЫ* р, иге (и, вЬе гт, ГТ стр); //стр имеет С++ компоновку гоЫхвои(юЫ' р, вЬв (и, иге гвг, СРТстр); /'стр имеет С компоновку вхгвгп "С" юЫуюп(юЫ* р, вЬв г л, иге гвг, ГТстр); //стр имеет С++ комку шг сотраге(соим юЫ*, свив( юЫ*); ехгвгп "С" /и! остр (сошг юЫ*, соим юЫ*); //сотрагео имеет С++ ком-ку //сстр0 имеет С компоновку юЫТ(сваг" ю №пгвг) ( Евоп(г,т,1, ьсотраге) ) овои (и, т, 1, ьсслр); //епог // о/г Ьоп (ю вг, 1, ьсотраге); Ьогг(ювг,1, ьсстр) ь //О№ // еп ог Реализации С и С++ с одинаковыми соглашениями о вызовах могут принять случаи, отмеченные комментарием //еггог, как языковые расширения.
9.3. Применяем заголовочные файлы Для иллюстрации практического применения заголовочных файлов я сейчас рассмотрю ряд альтернативных способов выражения физической структуры нашей программы калькулятора 56.1, 58.2). 9.3.1. Единственный заголовочный файл //файл Ыс№и иатвврасе Еггог ( вггисг Хего й/гЫе ( ); вггис( Булгак еп ог Простейшим решением проблемы разбиения программы на несколько файлов является размещение определений в подходящем числе .с файлов и помещение всех нужных для их взаимодействия объявлений типов в единственный заголовочный файл, подлежащий включению во все остальные файлы директивой №/ис!иг№е.
Для программы калькулятора можно использовать пять .с файлов — !ехег.с, рагвег. с, гаЫе. с, еп ог. с и тоги. с — для хранения функций и определений данных, и один заголовочный файл ь(с. й для хранения объявлений имен, используемых более чем в одном . с файле. Вот возможное содержимое файла Ис. Ы Глава 9. Исходные файлы и программы 266 ( сопвгсйаг* р; Купйгх еггог (сола! сваг* а) (р = у; ) ): ) Ипс!иае <ей 1ле> патеврасе Еехег влит Тоаел ва!ие ( Ь/АМЕ, РИ/Я= '+ ', РИТЬ/Т='/', ): Ь/1/МВЕ!1, МОЮ= А'- ', ЯЕ166)'ч'= ' = ', ЕМВ, МШ='*', ЕР=' ( ', ехгегп Тоаеп ва(ие сиге го!и ехгегл аоиЫе иитЬег ва!ие; ехгегп вгй:: вгг(щ вгггие ва1ие( Тайен ва1ие дег гоаеи (); ) иатеврасе Рагвег йоиЫе рг/т (Ьоо! лег) г аоиЫе гели (Ьоо1 еег); аоиЫе ехрг(Ьоо1 бег); // обработна первичны выражений //гсиножение и девенве //сложение и вычитание ив/ла Еехег:: аег гоаеи; ив/пд Еехег;: сиге гой; Иис!иае <тар> ехгегп вЫ:: тир<ей!:: вгг(пд, йоиЫе> гаЫе( латеврасе Вг(вег ( ех1еги !пг по оу еггогв; ехгегл вЫ:: Йгеат* (при(; вой! в)ир (); //файл 1ехег.с: Ипс(иае "ас.