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

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

Заголовочный файл (иногда головной файл, англ. header file), или подключаемый файл — в языках программирования файл, механически «вставляемый» препроцессором в исходный текст в том месте, где располагается некоторая директива ({$I file.inc} в Паскале, #include <file.h> в Си).

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

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

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

Назначение[править | править вики-текст]

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

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

 int add(int a, int b)
 {
     return a + b;
 }

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

 int add(int, int);
 
 int triple(int x)
 {
     return add(x, add(x, x));
 }

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

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

 /* File triple.c */
 #include "add.h"
 
 int triple(int x)
 {
     return add(x, add(x, x));
 }

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

 /* File add.h */
 #ifndef ADD_H
 #define ADD_H
 
 int add(int, int);
 
 #endif /* ADD_H */

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

 /* File add.h */
 #pragma once
 
 int add(int, int);

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

 /* File add.c */
 #include "add.h"
 
 int add(int a, int b)
 {
     return a + b;
 }

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

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

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

Преимущества[править | править вики-текст]

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

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

// unit.h
#ifndef __UNIT_H__
#define __UNIT_H__
 
#ifndef UNIT_STL_UNUSED
  #include <iostream>
  void dump(std::ostream& os);
  void dump() { dump(std::cout); }
#endif
 
void run();
 
#endif
// main.cpp
 
#define UNIT_STL_UNUSED
#include "unit.h"
 
int main()
{
  run();
  return 0;
}

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

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

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

Недостатки[править | править вики-текст]

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

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

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

Примечания[править | править вики-текст]

В некоторых языках (например, Java) вообще не требуется изменять код одновременно в двух местах.

См. также[править | править вики-текст]

Ссылки[править | править вики-текст]

Литература[править | править вики-текст]

  • Подбельский В. В. Глава 8. Препроцессорные средства // Язык Си++ / рец. Дадаев Ю. Г.. — 4. — М.: Финансы и статистика, 2003. — С. 263-280. — 560 с. — ISBN 5-279-02204-7, УДК 004.438Си(075.8) ББК 32.973.26-018 1я173.