Заголовочные файлы в C: типы, подключение и лучшие практики
Заголовочный файл — это файл с расширением .h, содержащий объявления функций, макросов, констант и типов, которые нужно разделять между несколькими файлами исходного кода. Существует два вида заголовочных файлов:
- файлы, которые пишет программист (пользовательские);
- файлы, поставляемые с компилятором (системные/стандартные).
Чтобы использовать заголовочный файл, его подключают директивой препроцессора #include — например, #include <stdio.h>.
Подключение заголовочного файла эквивалентно копированию его содержимого в место директивы, но ручное копирование — плохая идея: это источник ошибок и усложняет поддержку, особенно в проектах с множеством .c-файлов.
Хорошая практика: хранить в заголовочных файлах константы, макросы, прототипы функций и глобальные переменные (с extern), а определения — в .c-файлах.
Синтаксис #include
Директива имеет две формы:
#include <file>
Используется для системных заголовочных файлов. Препроцессор ищет файл в стандартных системных каталогах. Список каталогов можно расширить ключом компилятора -I.
#include "file"
Используется для пользовательских заголовочных файлов. Поиск начинается в каталоге, где лежит текущий исходный файл. Тоже можно расширить через -I.
Как работает #include
Директива заставляет препроцессор обработать указанный файл как входной поток перед продолжением текущего файла. На выходе получается: уже обработанный текст, затем текст из подключаемого файла, затем остаток текущего файла после директивы.
Пример. Файл header.h:
char *test(void);
Файл program.c:
int x;
#include "header.h"
int main(void) {
puts(test());
}
Компилятор видит то же, что если бы program.c содержал:
int x;
char *test(void);
int main(void) {
puts(test());
}
Защита от повторного включения (Include Guards)
Если заголовочный файл подключить дважды, компилятор увидит его содержимое дважды — будет ошибка переопределения. Стандартная защита — обернуть всё содержимое в условный блок:
#ifndef HEADER_FILE
#define HEADER_FILE
/* всё содержимое заголовочного файла */
#endif
Эта конструкция называется include guard (или wrapper #ifndef). При повторном включении макрос HEADER_FILE уже определён, условие ложно, и препроцессор пропускает содержимое.
В современном C (C11 и выше) также можно использовать #pragma once — это короче и поддерживается всеми основными компиляторами.
Вычисляемые включения (Computed Includes)
Иногда нужно выбрать один из нескольких заголовочных файлов в зависимости от конфигурации (например, под разные ОС). Можно использовать цепочку условий:
#if SYSTEM_1
# include "system_1.h"
#elif SYSTEM_2
# include "system_2.h"
#elif SYSTEM_3
...
#endif
Но при росте вариантов это становится громоздко. Препроцессор позволяет использовать макрос в качестве имени файла — это называется вычисляемое включение:
#define SYSTEM_H "system_1.h"
...
#include SYSTEM_H
Макрос SYSTEM_H раскроется в строку "system_1.h", и препроцессор подключит именно этот файл. Значение макроса можно задать из Makefile через -DSYSTEM_H="system_2.h".
Язык C
- Работа с файлами в C: мастерство ввода-вывода с fopen, fprintf, fread и fseek
- Работа с файлами в Python: открытие, чтение, запись и управление
- Мастерство файловых операций в Java с java.io: создание, чтение, запись и удаление
- Понимание STL-файлов: основа 3D-печати
- Работа с файлами в Verilog: открытие, чтение, запись и закрытие
- Освоение ввода-вывода файлов в C: создание, открытие и управление файлами
- Ввод-вывод в файлы в C++: чтение и запись с fstream
- Работа с файлами в C#: FileStream, StreamReader, StreamWriter и File API
- Ввод-вывод файлов в Python: полное руководство по операциям I/O
- Треугольная наждачная напильник: конструкция, применение и уход