Заголовочные файлы и стражи включения C/C++

Материал из Вики ИТ мехмата ЮФУ
Перейти к: навигация, поиск

Любой заголовочный файл C/C++ должен иметь следующую структуру.

#ifndef ИМЯ_ЗАГОЛОВОЧНОГО_ФАЙЛА 
#define ИМЯ_ЗАГОЛОВОЧНОГО_ФАЙЛА 

/* здесь помещается остальной текст заголовочного файла */

/* ИМЯ_ЗАГОЛОВОЧНОГО_ФАЙЛА: */
#endif

Например, заголовочный файл myFunctions.h, в котором размещены объявления функций f и g, будет выглядеть так:

#ifndef MY_FUNCTIONS_H
#define MY_FUNCTIONS_H 

void f(int n);
double g(double a, double b);

/* MY_FUNCTIONS_H: */
#endif

Зачем нужны заголовочные файлы

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

Указанное требование тривиально выполняется в простых случаях наподобие (считается, что компилятор просматривает программу сверху-вниз один раз — что, вообще говоря, неверно для современных компиляторов):

void f(int a) { /* ... */ }

int main() {
    f(5);
}

Однако если программу немного изменить, то требование уже нарушится:

int main() {
    f(5);
}

void f(int a) { /* ... */ }

Хотя в остальном эта программа вполне корректна. В случаях, когда такой порядок определения функций (сначала main, потом f) по какой-то причине более предпочтителен, можно использовать специальный элемент языка, называемый объявлением или прототипом функции f:

void f(int a);

int main() {
    f(5);
}

void f(int a) { /* ... */ }

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

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

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

#include "myFunctions.h"

вставляет целиком содержимое указанного заголовочного файла в текущее место исходного файла перед компиляцией (например, в main.c, если в нём встретилась эта строка). После такой вставки в main.c окажутся все объявления функций из файла myFunctions.h и компилятор будет счастлив.

В чём смысл стражей включения

Вкратце, директивы ifndef-define-endif, которые обрамляют любой грамотно оформленный заголовочный файл, являются трюком препроцессора: они обеспечивают то, что любой заголовочный файл будет включён в любой исходный файл не более одного раза.

Более подробно. Каждому заг. файлу «вручную» ставится в соответствие некоторый «символ», обычно связанный с именем этого файла, чтобы обеспечить уникальность. В первой строке проверяется, был ли уже определён этот символ ранее, если да, то весь остальной текст игнорируется. Если нет, то этот символ определяется, а затем вставляется и весь остальной текст заголовочного файла. Последняя строка (endif) просто означает закрытие такого «условного оператора».

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