Содержимое заголовочного файла

Файлы заголовков (C++)

Имена программных элементов, таких как переменные, функции, классы и т. д., должны быть объявлены, прежде чем их можно будет использовать. Например, нельзя просто писать x = 42 без предварительного объявления «x».

Объявление сообщает компилятору, является ли элемент int , a double , функцией class или другим элементом. Кроме того, каждое имя должно быть объявлено (прямо или косвенно) в каждом cpp-файле, в котором он используется. При компиляции программы каждый CPP-файл компилируется независимо в единицу компиляции. Компилятор не имеет сведений о том, какие имена объявляются в других единицах компиляции. Это означает, что если вы определите класс или функцию или глобальную переменную, необходимо предоставить объявление этого объекта в каждом дополнительном cpp-файле, который его использует. Каждое объявление этого элемента должно быть точно одинаковым во всех файлах. Небольшая несогласованность вызовет ошибки или непреднамеренное поведение, когда компоновщик пытается объединить все единицы компиляции в одну программу.

Чтобы максимально сокращать возможности ошибок, в C++ принято соглашение об использовании файлов заголовков для хранения объявлений. Объявления можно сделать в файле заголовка, а затем использовать директиву #include в каждом cpp-файле или другом файле заголовка, для которого требуется это объявление. Директива #include вставляет копию файла заголовка непосредственно в CPP-файл перед компиляцией.

В Visual Studio 2019 функция модулей c++ 20 появилась в качестве улучшения и в конечном итоге заменяет файлы заголовков. Дополнительные сведения см. в разделе Общие сведения о модулях в C++.

Пример

В следующем примере показан общий способ объявления класса и его использования в другом исходном файле. Начнем с файла заголовка, my_class.h . Он содержит определение класса, но обратите внимание, что определение не завершено; Функция-член do_something не определена:

Затем создайте файл реализации (обычно с расширением CPP или аналогичным модулем). Мы вызываем файл my_class. cpp и предоставим определение для объявления члена. Мы добавляем #include директиву для файла «my_class. h», чтобы объявление my_class, вставленное в этот момент в cpp, было включено в объявление для std::cout . Обратите внимание, что кавычки используются для файлов заголовков в том же каталоге, что и исходный файл, а угловые скобки используются для заголовков стандартной библиотеки. Кроме того, многие заголовки стандартной библиотеки не имеют h или любого другого расширения файла.

В файле реализации при необходимости можно использовать using оператор, чтобы не указывать каждое упоминание «my_class» или «cout» с «N::» или «std::». Не помещайте using операторы в файлы заголовков!

Теперь можно использовать my_class в другом cpp файле. Мы #include заголовочный файл, чтобы компилятор запрашивает объявление. Все компиляторы должны иметь представление о том, что my_class — это класс, имеющий открытую функцию-член с именем do_something() .

После того как компилятор завершит компиляцию каждого CPP-файла в OBJ-файлы, он передает OBJ-файлы компоновщику. Когда компоновщик объединяет объектные файлы, обнаруживается только одно определение для my_class; Он находится в OBJ-файле, созданном для my_class. cpp, и сборка выполняется.

Включить условия

Как правило, файлы заголовков содержат директиву include или, #pragma once чтобы убедиться, что они не вставляются несколько раз в один CPP-файл.

Что следует разместить в файле заголовка

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

  • встроенные определения типов в пространстве имен или глобальной области
  • невстроенные определения функций
  • определения неконстантных переменных
  • агрегатные определения
  • безымянные пространства имен
  • Директивы using

Использование using директивы не обязательно приведет к ошибке, но может вызвать проблему, так как она переводит пространство имен в область каждого CPP-файла, который напрямую или косвенно включает этот заголовок.

Пример файла заголовка

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

C++/Header file

Заголовочный файл (англ. header file , или подключаемый файл) в C++ — файл, содержимое которого автоматически добавляется препроцессором в исходный текст в том месте, где располагается некоторая директива ( #include в Си).

Содержание

  • 1 Preamble
  • 2 Расширения заголовочный файлов
  • 3 Общие сведения
  • 4 Назначение
  • 5 Сравнение с прямым получением заголовков из откомпилированного модуля
    • 5.1 Преимущества
    • 5.2 Недостатки
  • 6 См. также
  • 7 Ссылки
  • 8 Литература

Preamble

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

Расширения заголовочный файлов

По-умолчанию используется расширение «.h»; иногда для заголовочных файлов языка C++ используют расширение «.hpp». Позже было принято, что заголовочные файлы всех стандартных библиотек языка C++ пишутся без расширений. Ранее им давались самые различные расширения: «.h», «.h++», «.hpp», «.hxx», «.hh». Неродным (пользовательским, сторонним) заголовочным файлам принято по-прежнему давать расширение «.h».

Общие сведения

Заголовочный файл в общем случае может содержать любые конструкции языка программирования, но на практике исполняемый код (за исключением inline-функций в C++) в заголовочные файлы не помещают. Например, идентификаторы, которые должны быть объявлены более чем в одном файле, удобно описать в заголовочном файле, а затем его подключать по мере надобности. Подобным же образом работает модульность и в большинстве ассемблеров.

По сложившейся традиции, в заголовочных файлах объявляют функции стандартной библиотеки Си и Си++.

В других языках (например, в Паскале) применяется развитая система модулей. Но и в них заголовочные файлы имеют определённую ценность. Дело в том, что два файла (основной и заголовочный) сливаются в одну единицу трансляции, и поэтому заголовочный файл может содержать директивы препроцессора, незаконченные синтаксические конструкции.

Назначение

В современных языках программирования программы составляются из модулей, компилируемых по отдельности. В связи с этим возникает вопрос: как указать, что подпрограмма или переменная X определена в модуле Y ? Для этого существует несколько решений, в Си применено такое.

В одной из единиц компиляции (то есть с -файле) описывается функция, например:

Чтобы на неё можно было ссылаться из других единиц компиляции, требуется объявить её при помощи прототипа функции, то есть:

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

Заголовочный файл является одним из решений этой проблемы. В заголовочном файле модуля объявляется каждая функция, объект и тип данных, являющиеся частью интерфейса вызова модуля — например, в этом случае заголовочный файл может содержать только объявление функции add . Каждый исходный файл, ссылающийся на функцию add , должен использовать директиву #include для подключения заголовочного файла:

Чтобы избежать повторного включения одного и того же кода, используются директивы #ifndef, #define, #endif .

Списки инициализированных констант в заголовочном файле выбираются препроцессором для замены их значением этих констант во включаемом файле. Включаемые функции заголовочного файла обрамляются директивами макрозащиты препроцессора для избежания их дублирования во включающем файле (возникновение такой ситуации возможно при классовом или файловом наследовании):

Кроме конструкции #ifndef — #endif иногда применяется нестандартная #pragma once :

Заголовочные файлы облегчают поддержку — при изменении определения должно быть обновлено лишь одно объявление (то, которое находится в заголовочном файле). К исходному файлу также можно подключать заголовочный файл, содержащий определения, используемые в исходниках. Это позволяет компилятору сверять, совпадает ли объявление в h -файле с определением в c -файле:

Обычно заголовочные файлы применяются только для более чёткого определения интерфейса и обычно содержат комментарии, поясняющие способы использования компонентов, объявленных в файле. В приведённом примере использованные подпрограммы выделены в отдельные исходные файлы, которые должны компилироваться отдельно (исключением в языках Си и C++ являются встраиваемые функции, которые зачастую включаются в заголовочный файл из-за того, что в большинстве случаев использования не получается правильно раскрыть встраиваемую функцию без обращений к их определению во время компиляции).

Сравнение с прямым получением заголовков из откомпилированного модуля

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

Преимущества

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

Если модуль правильно написан, с помощью условной компиляции можно отключить часть его функциональности. Например, в данном случае мы отказываемся от прикомпоновывания к программе огромной библиотеки STL:

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

Если программист исправил реализацию функции в c -файле, не тронув заголовка, это не вызовет каскадной перекомпиляции всех модулей, которые используют данный заголовок.

Заголовочный файл позволяет задать то, что невозможно задать с помощью модулей — подстановки с помощью #define , директивы компилятора, незаконченные синтаксические конструкции…

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

Читайте также  Генератор для одометров

Недостатки

Заголовочные файлы намного медленнее — чтобы откомпилировать 10 c -файлов, к каждому из которых подключён длинный h -файл, компилятору придётся пройти по заголовку 10 раз. Чтобы справиться с этой проблемой, во многих компиляторах используют предварительно откомпилированные заголовки.

Заголовочные файлы вместе с некоторыми объектами языка C++ (константы, inline -функции, шаблоны, static -переменные) образуют тяжеловесные конструкции.

Программист должен синхронно менять заголовки функций в двух местах. Если вдруг он изменил c -файл, забыв сделать то же с h -файлом, компоновщик выдаст расплывчатое сообщение об ошибке без номера строки. Особенно это заметно в C++, где одна и та же функция может иметь разный набор аргументов, и проверка на уровне компилятора не срабатывает. Если программист случайно оставит конструкцию в h -файле незаконченной, ошибка будет совсем в другом c — или h -файле.

Проектам из языков семейства Си свойственны сложные схемы сборки проекта. Ведь (как минимум в стандартном C++) надо включить в проект библиотеку — либо в виде CPP-файлов, либо в откомпилированном виде. Даже если (например, в Visual C++) для этого есть директивы препроцессора, библиотеку всё равно придётся собрать.

Урок №21. Заголовочные файлы

Обновл. 26 Мар 2021 |

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

Файлы .cpp не являются единственными файлами в проектах. Есть еще один тип файлов — заголовочные файлы (или «заголовки»), которые имеют расширение .h . Целью заголовочных файлов является удобное хранение набора объявлений объектов для их последующего использования в других программах.

  1. Заголовочные файлы из Cтандартной библиотеки C++
  2. Пишем свои собственные заголовочные файлы
  3. Угловые скобки (<>) vs. Двойные кавычки («»)
  4. Почему iostream пишется без окончания .h?
  5. Можно ли записывать определения в заголовочных файлах?
  6. Советы

Заголовочные файлы из Стандартной библиотеки C++

Рассмотрим следующую программу:

Результат выполнения программы:

В этой программе мы используем cout, который нигде не определяем. Откуда компилятор знает, что это такое? Дело в том, что cout объявлен в заголовочном файле iostream. Когда мы пишем #include , мы делаем запрос, чтобы всё содержимое заголовочного файла iostream было скопировано в наш файл. Таким образом, всё содержимое библиотеки iostream становится доступным для использования.

Как правило, в заголовочных файлах записываются только объявления, без определений. Следовательно, если cout только объявлен в заголовочном файле iostream, то где же он определяется? Ответ: в Стандартной библиотеке С++, которая автоматически подключается к вашему проекту на этапе линкинга.

Подумайте о последствиях отсутствия заголовочного файла iostream. Каждый раз, при использовании cout, вам бы приходилось вручную копировать все предварительные объявления, связанные с cout в верхнюю часть вашего файла! Хорошо ведь, что можно просто указать #include , не так ли?

Пишем свои собственные заголовочные файлы

Теперь давайте вернемся к примеру, который мы обсуждали на предыдущем уроке. У нас было два файла: add.cpp и main.cpp.

Примечание: Если вы создаете все файлы заново, то не забудьте добавить add.cpp в свой проект, чтобы он был подключен к компиляции.

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

И здесь нам на помощь приходят заголовочные файлы. Достаточно просто написать один заголовочный файл и его можно будет повторно использовать в любом количестве программ. Также и вносить изменения в такой код (например, добавление еще одного параметра) гораздо легче, нежели чем шерстить по всем файлам в поисках используемых функций.

Написать свой собственный заголовочный файл не так уж и сложно. Заголовочные файлы состоят из двух частей:

Директивы препроцессора — в частности, header guards, которые предотвращают вызов заголовочного файла больше одного раза из одного и того же файла (об этом детально на следующем уроке).

Содержимое заголовочного файла — набор объявлений.

Все ваши заголовочные файлы (которые вы написали самостоятельно) должны иметь расширение .h .

Чтобы использовать этот файл в main.cpp, вам сначала нужно будет подключить его к проекту.

main.cpp, в котором мы подключаем add.h:

add.cpp остается без изменений:

Когда компилятор встречает #include «add.h» , он копирует всё содержимое add.h в текущий файл. Таким образом, мы получаем предварительное объявление функции add().

Примечание: При подключении заголовочного файла, всё его содержимое вставляется сразу же после строки #include . .

Если вы получили ошибку от компилятора, что add.h не найден, то убедитесь, что имя вашего файла точно «add.h». Вполне возможно, что вы могли сделать опечатку, например, просто «add» (без «.h») или «add.h.txt» или «add.hpp».

Если вы получили ошибку от линкера, что функция аdd() не определена, то убедитесь, что вы корректно подключили add.cpp к вашему проекту (и к компиляции тоже)!

Угловые скобки (<>) vs. Двойные кавычки («»)

Вы, наверное, хотите узнать, почему используются угловые скобки для iostream и двойные кавычки для add.h. Дело в том, что, используя угловые скобки, мы сообщаем компилятору, что подключаемый заголовочный файл написан не нами (он является «системным», т.е. предоставляется Стандартной библиотекой С++), так что искать этот заголовочный файл следует в системных директориях. Двойные кавычки сообщают компилятору, что мы подключаем наш собственный заголовочный файл, который мы написали самостоятельно, поэтому искать его следует в текущей директории нашего проекта. Если файла там не окажется, то компилятор начнет проверять другие пути, в том числе и системные директории.

Правило: Используйте угловые скобки для подключения «системных» заголовочных файлов и двойные кавычки для ваших заголовочных файлов.

Стоит отметить, что одни заголовочные файлы могут подключать другие заголовочные файлы. Тем не менее, так делать не рекомендуется.

Почему iostream пишется без окончания .h?

Еще один часто задаваемый вопрос: «Почему iostream (или любой другой из стандартных заголовочных файлов) при подключении пишется без окончания «.h»?». Дело в том, что есть 2 отдельных файла: iostream.h (заголовочный файл) и просто iostream! Для объяснения потребуется краткий экскурс в историю.

Когда C++ только создавался, все файлы библиотеки Runtime имели окончание .h. Оригинальные версии cout и cin объявлены в iostream.h. При стандартизации языка С++ комитетом ANSI, решили перенести все функции из библиотеки Runtime в пространствo имен std, чтобы предотвратить возможность возникновения конфликтов имен с пользовательскими идентификаторами (что, между прочим, является хорошей идеей). Тем не менее, возникла проблема: если все функции переместить в пространство имен std, то старые программы переставали работать!

Для обеспечения обратной совместимости ввели новый набор заголовочных файлов с теми же именами, но без окончания «.h». Весь их функционал находится в пространстве имен std. Таким образом, старые программы с #include не нужно было переписывать, а новые программы уже могли использовать #include .

Когда вы подключаете заголовочный файл из Стандартной библиотеки C++, убедитесь, что вы используете версию без .h (если она существует). В противном случае, вы будете использовать устаревшую версию заголовочного файла, который уже больше не поддерживается.

Кроме того, многие библиотеки, унаследованные от языка Cи, которые до сих пор используются в C++, также были продублированы с добавлением префикса c (например, stdlib.h стал cstdlib). Функционал этих библиотек также перенесли в пространство имен std, чтобы избежать возможность возникновения конфликтов имен с пользовательскими идентификаторами.

Правило: При подключении заголовочных файлов из Стандартной библиотеки С++, используйте версию без «.h» (если она существует). Пользовательские заголовочные файлы должны иметь окончание «.h».

Можно ли записывать определения в заголовочных файлах?

C++ не будет жаловаться, если вы это сделаете, но так делать не принято.

Как уже было сказано выше, при подключении заголовочного файла, всё его содержимое вставляется сразу же после строки с #include. Это означает, что любые определения, которые есть в заголовочном файле, скопируются в ваш файл.

Для небольших проектов, это, скорее всего, не будет проблемой. Но для более крупных это может способствовать увеличению времени компиляции (так как код будет повторно компилироваться) и размеру исполняемого файла. Если внести изменения в определения, которые находятся в файле .cpp, то перекомпилировать придется только этот файл. Если же внести изменения в определения, которые записаны в заголовочном файле, то перекомпилировать придется каждый файл, который подключает этот заголовок, используя директиву препроцессора #include. И вероятность того, что из-за одного небольшого изменения вам придется перекомпилировать весь проект, резко возрастает!

Читайте также  Подключение ноутбучного сканера отпечатка пальца к компьютеру

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

Советы

Вот несколько советов по написанию собственных заголовочных файлов:

Всегда используйте директивы препроцессора.

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

Не определяйте функции в заголовочных файлах.

Каждый заголовочный файл должен выполнять свое конкретное задание и быть как можно более независимым. Например, вы можете поместить все ваши объявления, связанные с файлом А.cpp в файл A.h, а все ваши объявления, связанные с B.cpp — в файл B.h. Таким образом, если вы будете работать только с А.cpp, то вам будет достаточно подключить только A.h и наоборот.

Используйте имена ваших рабочих файлов в качестве имен для ваших заголовочных файлов (например, grades.h работает с grades.cpp).

Не подключайте одни заголовочные файлы из других заголовочных файлов.

Не подключайте файлы .cpp, используя директиву препроцессора #include.

Поделиться в социальных сетях:

include и заголовочный файлы

Заголовочный файл (иногда головной файл, англ. header file), или подключаемый файл — в языках программирования Си и C++ файл, содержащий определения типов данных, структуры, прототипы функций, перечисления, макросы препроцессора. Имеет по умолчанию расширение .h; иногда для заголовочных файлов языка C++ используют расширение .hpp. Заголовочный файл используется путём включения его текста в данный файл директивой препроцессора #include. Заголовочный файл в общем случае может содержать любые конструкции языка программирования, но на практике исполняемый код (за исключением inline-функций в C++) в заголовочные файлы не помещают. Например, идентификаторы, которые должны быть объявлены более чем в одном файле, удобно описать в заголовочном файле, а затем его подключать по мере надобности.

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

По традиции заголовочные файлы имеют расширение .h, а файлы, содержащие определения функций или данных, расширение .c. Иногда их называют «h-файлы» или «с-файлы» соответственно. Используют и другие расширения для этих файлов: .C, cxx, .cpp и .cc. Принятое расширение вы найдете в своем справочном руководстве.

Для включения файлов из стандартных каталогов (обычно каталоги с именем INCLUDE) надо вместо кавычек использовать угловые скобки . Если имя_файла — в угловых скобках, то препроцессор разыскивает файл в стандартных системных каталогах. Если имя_файла заключено в кавычки, то вначале препроцессор просматривает текущий каталог пользователя и только затем обращается к просмотру стандартных системных каталогов. Например:

Включение из стандартных каталогов имеет то преимущество, что имена этих каталогов никак не связаны с конкретной программой (обычно вначале включаемые файлы ищутся в каталоге /usr/include/CC, а затем в /usr/include). К сожалению, в этой команде пробелы существенны:

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

Укажем, что может содержать заголовочный файл:

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

Один Заголовочный Файл (.h)

Проще всего решить проблему разбиения программы на несколько файлов поместив функции и определения данных в подходящее число исходных файлов и описав типы, необходимые для их взаимодействия, в одном заголовочном файле, который включается во все остальные файлы. Для программы калькулятора можно использовать четыре .c файла: lex.c, syn.c, table.c и main.c, и заголовочный файл dc.h, содержащий описания всех имен, которые используются более чем в одном .c файле.

Множественные Заголовочные Файлы (.h)

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

Заголовочный файл

Материал из Seo Wiki — Поисковая Оптимизация и Программирование

Заголовочный файл (иногда головной файл, англ. header file ), или подключаемый файл — в языках программирования Си и C++ файл, содержащий определения типов данных, структуры, прототипы функций, перечисления, макросы препроцессора. Имеет по умолчанию расширение .h; иногда для заголовочных файлов языка C++ используют расширение .hpp. Заголовочный файл используется путём включения его текста в данный файл директивой препроцессора #include .

Заголовочный файл в общем случае может содержать любые конструкции языка программирования, но на практике исполняемый код (за исключением inline функций в C++) в заголовочные файлы не помещают. Например, идентификаторы, которые должны быть объявлены более чем в одном файле, удобно описать в заголовочном файле, а затем его подключать по мере надобности.

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

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

Преимущества использования

В большинстве современных языков программирования, корректность функционирования программ зависит от ее собственных компонентов (подпрограмм), а эти компоненты могут распространяться в виде файлов, компилируемых отдельно. Если подпрограмму необходимо использовать где-то еще, где она не определена, возникает необходимость в использовании предварительного объявления или прототипа функции. Например, функция определена следующим образом в одном исходном файле:

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

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

Заголовочный файл является решением этой проблемы. В заголовочном файле модуля объявляется каждая функция, объект и тип данных, являющиеся частью интерфейса вызова модуля — например, в этом случае заголовочный файл может содержать только объявление функции add . Каждый исходный файл, ссылающийся на функцию add , должен использовать директиву #include для подключения заголовочного файла:

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

Обычно заголовочные файлы применяются только для более чёткого определения интерфейса и обычно содержат комментарии, поясняющие способы использования компонентов, объявленных в файле. В приведенном примере использованные подпрограммы выделены в отдельные исходные файлы, которые должны компилироваться отдельно (исключением в языках Си и C++ являются встраиваемые функции, которые зачастую включаются в заголовочный файл из-за того, что в большинстве случаев использования не получается правильно раскрыть встраиваемую функцию без обращений к их определению во время компиляции).

Альтернативные варианты

Заголовочные файлы — не единственное решение проблем доступа к идентификаторам, объявленным в различных файлах. У них есть недостаток, заключающийся в необходимости вносить изменния в двух местах (исходный и заголовочный файлы) каждый раз при изменении определения. Некоторые более новые языки (такие как Java) вместо заголовочных файлов используют схемы наименования, позволяющие компилятору использовать исходные файлы, связанные с интерфейсами и использованиями классов.

Компоновка, библиотеки и заголовочные файлы

При создании компилятора С или С++ решались две задачи. Во-первых, разрабатывался сам компилятор. Во-вторых, создавалась библиотека функций. Поскольку в библиотеке Borland содержится очень много функций, можно не сомневаться, что ее создание потребовало от программистов серьезных усилий. (Одно только описание этих функций занимает несколько сотен страниц!) Любая С или С++ программа опирается на эти функции при выполнении множества задач. Ввиду столь фундаментальной роли библиотеки для выполнения программ важно познакомиться с описанием ее работы. В частности, необходимо разобраться в работе компоновщика, понять, чем библиотеки отличаются от объектных файлов и какова роль заголовочных файлов. Эти вопросы и рассматриваются ниже.

Читайте также  Как провести проводку для точечных светильников?

Результатом работы компилятора является перемещаемый объектный файл, а результатом работы компоновщика — файл, готовый к исполнению. Компоновщик выполняет двойную роль. Во-пер­вых, он физически объединяет указанные в списке связей файлы в один программный файл. Во- вторых, он решает проблему внешних ссылок и обращений к памяти. Внешняя ссылка делается каждый раз, когда в коде одного файла упоминается код из другого файла. Это случается либо при вызове функции, либо при упоминании глобальной переменной. Например, при установлении связей между двумя приведенными ниже файлами нужно реализовать обращение файла Two к переменной count. Именно компоновщик «сообщает» файлу Two, где в памяти располагается count.
Файл 1
int count;
extern void display(void);
int main(void)
<
count = 10;
display();
return 0;
>
Файл 2
#include
extern int count;
void display(void)
<
printf(«%d», count);
>

Подобным же образом компоновщик «сообщает» файлу One, где располагается функция display, чтобы ее можно было вызвать.
При формировании объектного кода файла Two компоновщик на место адреса count подстав­ляет «пустую» команду, поскольку он не знает, в каком месте памяти расположена count. При­мерно то же происходит при компилировании файла One. Адрес display() неизвестен, поэтому используется «пустая» команда. Такой прием обеспечивает возможность создания перемещаемо­го кода (relocatable code). После связывания файлов компоновщиком «пустые» команды замеща­ются относительными адресами.

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

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

Компоновщик (LINKER)

Результатом работы компилятора является перемещаемый объектный файл, а результатом работы компоновщика — файл, готовый к исполнению. Компоновщик выполняет двойную роль. Во-пер­вых, он физически объединяет указанные в списке связей файлы в один программный файл. Во- вторых, он решает проблему внешних ссылок и обращений к памяти. Внешняя ссылка делается каждый раз, когда в коде одного файла упоминается код из другого файла. Это случается либо при вызове функции, либо при упоминании глобальной переменной. Например, при установлении связей между двумя приведенными ниже файлами нужно реализовать обращение файла Two к переменной count. Именно компоновщик «сообщает» файлу Two, где в памяти располагается count.

Файл 1
int count;
extern void display(void);
int main(void)
<
count = 10;
display();
return 0;
>

Файл 2
#include
extern int count;
void display(void)
<
printf(«%d», count);
>

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

При формировании объектного кода файла Two компоновщик на место адреса count подстав­ляет «пустую» команду, поскольку он не знает, в каком месте памяти расположена count. При­мерно то же происходит при компилировании файла One. Адрес display() неизвестен, поэтому используется «пустая» команда. Такой прием обеспечивает возможность создания перемещаемо­го кода (relocatable code). После связывания файлов компоновщиком «пустые» команды замеща­ются относительными адресами.

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

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

Библиотечные файлы в сравнении с объектными файлами

Хотя библиотеки и похожи на объектные файлы, между ними имеется существенное различие: при обращении к библиотеке к программе добавляется не весь код, имеющийся в библиотеке. Когда компоновщик обрабатывает программу, состоящую из нескольких объектных файлов, пол­ный код каждого объектного файла становится частью результирующей исполняемой програм­мы. Это происходит независимо от того, будет ли в действительности использоваться этот код. Иначе говоря, компоновщик при формировании программы просто объединяет вместе все объект­ные файлы. С библиотечными файлами дело обстоит иначе.

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

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

Заголовочные файлы

Многие библиотечные функции работают со своими особыми типами данных и со структурами, к которым программа должна иметь доступ. Эти структуры и типы определяются в заголовочных файлах, поставляемых с компилятором, и они (заголовочные файлы) должны включаться (с по­мощью #include) в каждый файл, использующий функции, на которые они ссылаются. Кроме того, у всех библиотечных функций имеются прототипы, определенные в заголовочном файле. Это сделано по двум причинам. Во-первых, в (С++ все функции должны иметь прототипы. Во-вторых, хотя в С создание прототипов и не является обязательным, их использование настоятель­но рекомендовано, поскольку оно обеспечивает средство для более тщательного контроля типов. Включая в С-программу заголовочные файлы, которые соответствуют стандартным функ­циям программы, можно обнаружить потенциальные ошибки несовпадения типов. Например, включение string.h (заголовочного файла для работы с функциями обработки строк) в нижесле­дующий код приведет к выдаче при компилировании предупреждения:

#include
char s1[20] = «hello «;
char s2[] = «there.»;
int main(void)
<
int p;
p = strcat(s1, s2);
return 0;
>

Поскольку функция strcat() в заголовочном файле объявлена как возвращающая указатель на char, то компилятор может зарегистрировать как потенциальную ошибку присвоение целочис­ленной переменной р значения этого указателя.

Необходимо помнить: хотя в С включение многих заголовочных файлов технически не является обязательным (хотя и рекомендуется), они должны включаться во все С++-программы. В последу­ющих разделах в описании каждой функции будет указываться ее заголовочный файл.

Несколько из наиболее употребительных заголовочных файлов приведены в таблице. В таблице помечены файлы, определенные стандартом ANSI С и используемые языком С++.

Таблица: Наиболее употребительные заголовочные файлы

ALLOC.Н Функции динамического выделения памяти
ASSERT.H Определяет макрос assert() (ANSI С)
BIOS.H Функции ROM-BIOS
CONIO.H Функции для работы с экраном
CTYPE.H Функции для работы с символами (ANSI С)
DIR.H Функции для работы с каталогами
DOS.H Функции интерфейса DOS
ERRNO.H Определяет коды ошибок (ANSI С)
FCNTL.H Определяет константы, используемые функцией ореn()
FLOAT.H Определяет зависящие от реализации переменные с плавающей точкой (ANSI С)
FSTREAM.H Файл определений ввода/вывода (С++)
GRAPHICS.Н Графические функции
IO.Н UNIX-подобные процедуры ввода/вывода
IOMANIP.H Определяет манипуляторы ввода/вывода (С++)
IOSTREAM.H Определяет классы потоков ввода/вывода (С++)
LIMITS.Н Определяет различные, зависящие от реализации, пределы (ANSI С)
LOCALE.Н Функции, зависящие от стран и языков (ANSI С)
МАТН.Н Разные определения, используемые математической библиотекой (ANSI С)
PROCESS.H Функции spawn() и ехес()
SETJMP.H Нелокальные переходы (ANSI С)
SHARE.H Совместное использование файлов
SIGNAL.H Определяет величины сигналов (ANSI С)
STDARG.H Списки аргументов длин переменных (ANSI С)
STDDEF.H Определяет некоторые общеупотребительные константы (ANSI С)
STDIO.H Объявления для стандартных потоков ввода/вывода (ANSI С)
STDLIB.H Различные определения (ANSI С)
STRING.H Обработка строк (ANSI С)
STRSTREA.H Классы потоков ввода/вывода на базе массивов
TIME.H Функции системного времени

Макросы в заголовочных файлах

Многие библиотечные функции в действительности являются вообще не функциями, а скорее параметризованными макроопределениями, содержащимися в заголовочном файле. Последствия этого, в общем-то, незначительны, однако эта разница будет подчеркиваться при обсуждении таких «функций». Если по каким-либо причинам необходимо избежать использования стандартного макроса, можно отменить его определение, используя препроцессорную директиву #undef.