Должен ли я использовать #include в заголовках?
Нужно ли #include
какой-нибудь файл, если внутри заголовка (*.h) используются типы, определенные в этом файле?
Например, если я использую GLib и хочу использовать gchar
базовый тип в структуре, определенной в моем заголовке, это необходимо сделать #include <glib.h>
, зная, что он уже есть в моем файле *.c?
Если да, я также должен поставить его между #ifndef
а также #define
или после #define
?
9 ответов
Правила Центра космических полетов имени Годдарда ( GSFC) НАСА для заголовков в C гласят, что должна быть возможность включить заголовок в исходный файл в качестве единственного заголовка и что код, использующий средства, предоставленные этим заголовком, будет скомпилирован.
Преимущество этого правила заключается в том, что если кому-то нужно использовать заголовок, ему не нужно пытаться определить, какие другие заголовки также должны быть включены - они знают, что заголовок обеспечивает все необходимое.
Возможным недостатком является то, что некоторые заголовки могут быть включены много раз; Вот почему защита заголовков с несколькими включениями имеет решающее значение (и почему компиляторы стараются избегать повторного включения заголовков, когда это возможно).
Реализация
Это правило означает, что если заголовок использует тип - такой как ' FILE *
' или же ' size_t
'- тогда он должен убедиться, что соответствующий другой заголовок (<stdio.h>
или же <stddef.h>
например) должен быть включен. Следствием, часто забываемым, является то, что заголовок не должен включать в себя любой другой заголовок, который не нужен пользователю пакета для использования пакета. Другими словами, заголовок должен быть минимальным.
Кроме того, правила GSFC предоставляют простой метод, гарантирующий, что это то, что происходит:
- В исходном файле, который определяет функциональность, заголовок должен быть первым в списке.
Следовательно, предположим, у нас есть Волшебная сортировка.
magicsort.h
#ifndef MAGICSORT_H_INCLUDED
#define MAGICSORT_H_INCLUDED
#include <stddef.h>
typedef int (*Comparator)(const void *, const void *);
extern void magicsort(void *array, size_t number, size_t size, Comparator cmp);
#endif /* MAGICSORT_H_INCLUDED */
magicsort.c
#include <magicsort.h>
void magicsort(void *array, size_t number, size_t size, Comparator cmp)
{
...body of sort...
}
Обратите внимание, что заголовок должен включать в себя некоторый стандартный заголовок, который определяет size_t
; самый маленький стандартный заголовок, который делает это <stddef.h>
хотя некоторые другие тоже так делают (<stdio.h>
, <stdlib.h>
, <string.h>
возможно несколько других).
Кроме того, как упоминалось ранее, если файлу реализации нужны другие заголовки, пусть будет так, и для некоторых дополнительных заголовков вполне нормально. Но файл реализации ('magicsort.c') должен включать их сам, а не полагаться на свой заголовок для их включения. Заголовок должен включать только то, что нужно пользователям программного обеспечения; не то, что нужно разработчикам.
Заголовки конфигурации
Если ваш код использует заголовок конфигурации (например, GNU Autoconf и сгенерированный 'config.h'), вам может понадобиться использовать его в 'magicsort.c':
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
#include "magicsort.h"
...
Это единственный раз, когда я знаю, что частный заголовок модуля - не самый первый заголовок в файле реализации. Однако условное включение config.h, вероятно, должно быть в самом файле magicsort.h.
Обновление 2011-05-01
Ссылка, указанная выше, больше не работает (404). Вы можете найти стандарт C++ (582-2003-004) на EverySpec.com; стандарт C (582-2000-005), по-видимому, отсутствует в действии.
Руководящие принципы из стандарта C были:
§2.1 ЕДИНИЦЫ
(1) Код должен быть структурирован как единицы или как отдельные заголовочные файлы.
(2) Единица должна состоять из одного файла заголовка (.h) и одного или нескольких файлов тела (.c). В совокупности файлы заголовка и тела называются исходными файлами.
(3) Файл заголовка блока должен содержать всю необходимую информацию, требуемую клиентским блоком. Клиенту подразделения необходимо получить доступ только к заголовочному файлу, чтобы использовать устройство.
(4) Файл заголовка блока должен содержать операторы #include для всех других заголовков, требуемых заголовком блока. Это позволяет клиентам использовать единицу, включая один заголовочный файл.
(5) Файл тела модуля должен содержать инструкцию #include для заголовка модуля перед всеми остальными операторами #include. Это позволяет компилятору проверить, что все необходимые операторы #include находятся в заголовочном файле.
(6) Файл тела должен содержать только функции, связанные с одним блоком. Один файл тела может не предоставлять реализации для функций, объявленных в разных заголовках.
(7) Все клиентские блоки, которые используют любую часть данного блока U, должны включать файл заголовка для блока U; это гарантирует, что есть только одно место, где определены объекты в блоке U. Клиентские блоки могут вызывать только функции, определенные в заголовке блока; они могут не вызывать функции, определенные в теле, но не объявленные в заголовке. Клиентские модули могут не иметь доступа к переменным, объявленным в теле, но не в заголовке.
Компонент содержит одну или несколько единиц. Например, математическая библиотека - это компонент, который содержит несколько единиц, таких как вектор, матрица и кватернион.
Автономные заголовочные файлы не имеют связанных тел; например, заголовок общих типов не объявляет функции, поэтому он не нуждается в теле.
Некоторые причины наличия нескольких файлов тела для юнита:
- Часть кода тела зависит от аппаратного обеспечения или операционной системы, а остальное является общим.
- Файлы слишком велики.
- Модуль представляет собой общий пакет утилит, и некоторые проекты будут использовать только некоторые из функций. Помещение каждой функции в отдельный файл позволяет компоновщику исключить неиспользуемые функции из окончательного изображения.
§2.1.1 Заголовок включает обоснование
Этот стандарт требует, чтобы заголовок блока содержал
#include
операторы для всех других заголовков, требуемых заголовком блока. размещение#include
для заголовка блока сначала в теле блока позволяет компилятору проверить, что заголовок содержит все необходимые#include
заявления.Альтернативный дизайн, не разрешенный этим стандартом, не позволяет
#include
операторы в заголовках; все#include
s сделаны в файлах тела. Файлы заголовка блока должны содержать#ifdef
операторы, которые проверяют, что требуемые заголовки включены в правильном порядке.Одним из преимуществ альтернативного дизайна является то, что
#include
list в файле body - это именно тот список зависимостей, который необходим в make-файле, и этот список проверяется компилятором. При стандартном дизайне необходимо использовать инструмент для создания списка зависимостей. Тем не менее, все рекомендованные для отрасли среды разработки предоставляют такой инструмент.Основным недостатком альтернативного дизайна является то, что если изменяется требуемый список заголовков блока, каждый файл, который использует этот блок, должен быть отредактирован для обновления
#include
Список выписок. Кроме того, требуемый список заголовков для модуля библиотеки компилятора может отличаться для разных целей.Другим недостатком альтернативного дизайна является то, что заголовочные файлы библиотеки компилятора и другие сторонние файлы должны быть изменены, чтобы добавить необходимые
#ifdef
заявления.Другой распространенной практикой является включение всех файлов системных заголовков перед файлами заголовков проекта в файлы тела. Этот стандарт не следует этой практике, поскольку некоторые файлы заголовков проекта могут зависеть от файлов системных заголовков, либо потому, что они используют определения в системном заголовке, либо потому, что они хотят переопределить определение системы. Такие заголовочные файлы проекта должны содержать
#include
операторы для системных заголовков; если тело включает их в первую очередь, компилятор не проверяет это.
Стандарт GSFC доступен через интернет-архив 2012-12-10
Информация предоставлена Eric S. Bullington:
Ссылочный стандарт кодирования NASA C доступен и загружен через интернет-архив:
Последовательность действий
Вопрос также задает:
Если да, я должен также поставить это
#include
линии) между#ifndef
а также#define
или после#define
,
Ответ показывает правильный механизм - вложенные включения и т. Д. Должны быть после #define
(и #define
должна быть второй строкой без комментариев в заголовке) - но это не объясняет, почему это правильно.
Подумайте, что произойдет, если вы разместите #include
между #ifndef
а также #define
, Предположим, что другой заголовок сам включает в себя различные заголовки, возможно, даже #include "magicsort.h"
косвенно. Если второе включение magicsort.h
происходит раньше #define MAGICSORT_H_INCLUDED
затем заголовок будет включен во второй раз, прежде чем будут определены его типы. Итак, в C89 и C99 любой typedef
имя типа будет ошибочно переопределено (C2011 позволяет переопределять их для одного и того же типа), и вы получите накладные расходы на обработку файла несколько раз, в первую очередь победив цель защиты заголовка. Это также почему #define
является второй строкой и не записывается непосредственно перед #endif
, Приведенная формула достоверна:
#ifndef HEADERGUARDMACRO
#define HEADERGUARDMACRO
...original content of header — other #include lines, etc...
#endif /* HEADERGUARDMACRO */
Хорошая практика - помещать #include во включаемый файл только в том случае, если он нужен для включаемого файла. Если определения в данном включаемом файле используются только в файле.c, включите его только в файл.c.
В вашем случае я бы включил его во включаемый файл между #ifdef/#endif.
Это минимизирует зависимости, так что файлы, которые не нуждаются в заданном включении, не должны быть перекомпилированы, если файл включения изменяется.
Во время компиляции препроцессор просто заменяет директиву #include указанным содержимым файла. Для предотвращения бесконечного цикла следует использовать
#ifndef SOMEIDENTIFIER
#define SOMEIDENTIFIER
....header file body........
#endif
Если какой-то заголовок был включен в другой заголовок, который был включен в ваш файл, то нет необходимости явно включать его снова, потому что он будет включен в файл рекурсивно
Обычно разработчики библиотек защищают свои включения от нескольких, в том числе с помощью "трюка" #ifndef /#define / #endif, поэтому вам не нужно это делать.
Конечно, вы должны проверить... но в любом случае компилятор скажет вам в какой-то момент;-) В любом случае это хорошая практика для проверки нескольких включений, так как это замедляет цикл компиляции.
Вы должны включить заголовок из своего заголовка, и нет необходимости включать его в.c. Включения должны идти после #define, чтобы они не включались без необходимости несколько раз. Например:
/* myHeader.h */
#ifndef MY_HEADER_H
#define MY_HEADER_H
#include <glib.h>
struct S
{
gchar c;
};
#endif /* MY_HEADER_H */
а также
/* myCode.c */
#include "myHeader.h"
void myFunction()
{
struct S s;
/* really exciting code goes here */
}
Да, это необходимо, или компилятор будет жаловаться, когда он пытается скомпилировать код, о котором он не "знает". Подумайте о том, что # include - это подсказка / толчок / отвод компилятору, чтобы он сказал, чтобы он брал объявления, структуры и т. Д. Для успешной компиляции. Хитрость заголовка #ifdef/#endif, как указывает jldupont, заключается в ускорении компиляции кода.
Он используется в тех случаях, когда у вас есть компилятор C++ и компиляция простого кода на C, как показано здесь. Вот пример хитрости:
#ifndef __MY_HEADER_H__ #define __MY_HEADER_H__ #ifdef __cplusplus extern "C" { #endif / * C-код здесь, такой как структуры, объявления и т. Д. */ #ifdef __cplusplus } #endif #endif /* __MY_HEADER_H__ */
Теперь, если это было включено несколько раз, компилятор включит его только один раз, так как символ __MY_HEADER_H__
определяется один раз, что ускоряет время компиляции.Обратите внимание на символ cplusplus в приведенном выше примере, это обычный стандартный способ справиться с компиляцией C++, если у вас есть код на C.
Я включил вышеупомянутое, чтобы показать это (несмотря на то, что это не относилось к первоначальному вопросу автора). Надеюсь, это поможет, С наилучшими пожеланиями, Том.
PS: Извините за то, что позволил кому-либо понизить это, так как я думал, что это будет полезно для новичков в C/C++. Оставьте комментарий / критику и т. Д., Так как они приветствуются.
Я использую следующую конструкцию, чтобы быть уверенным, что нужный файл включения включен перед включением. Я включаю все заголовочные файлы только в исходные файлы.
#ifndef INCLUDE_FILE_H
#error "#include INCLUDE.h" must appear in source files before "#include THISFILE.h"
#endif
Обычно я делаю один включаемый файл, который включает все необходимые зависимости в правильном порядке. Так что я мог бы иметь:
#ifndef _PROJECT_H_
#define _PROJECT_H_
#include <someDependency.h>
#include "my_project_types.h"
#include "my_project_implementation_prototypes.h"
#endif
Все в project.h. Теперь project.h можно включить в любом месте без требований к заказу, но я все еще могу позволить себе иметь свои зависимости, типы и прототипы функций API в разных заголовках.
Просто включите все внешние заголовки в один общий заголовочный файл в вашем проекте, например, global.h, и включите его во все ваши c-файлы:
Это может выглядеть так:
#ifndef GLOBAL_GUARD
#define GLOBAL_GUARD
#include <glib.h>
/*...*/
typedef int YOUR_INT_TYPE;
typedef char YOUR_CHAR_TYPE;
/*...*/
#endif
Этот файл использует include guard, чтобы избежать нескольких включений, недопустимых множественных определений и т. Д.