Должен ли я использовать #include в заголовках?

Нужно ли #include некоторым файлам, если внутри заголовка (*.h) используются типы, определенные в этом файле?

Например, если я использую GLib и хочу использовать базовый тип gchar в структуре, определенной в моем заголовке, нужно ли делать #include <glib.h>, зная, что у меня уже есть это в моем файле *.c

Если да, я также должен поставить его между #ifndef и #define или после #define?

Ответ 1

NASA Центр космических полетов имени Годдарда (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 выполняются в файлах тела. В этом случае файлы заголовков #ifdef должны содержать операторы #ifdef которые проверяют, что требуемые заголовки включены в правильном порядке.

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

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

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

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

Стандарт GSFC доступен через интернет-архив 2012-12-10

Информация предоставлена Эриком С. Буллингтоном:

Ссылочный стандарт кодирования NASA C доступен и загружен через интернет-архив:

http://web.archive.org/web/20090412090730/http://software.gsfc.nasa.gov/assetsbytype.cfm?TypeAsset=Standard

Последовательность действий

Вопрос также задает:

Если да, я должен также поместить это (строки #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 */

Ответ 2

Хорошей практикой является только включение #includes в include файле, если файл include нуждается в них. Если определения в указанном файле include используются только в файле .c, тогда включите его только в файл .c.

В вашем случае я бы включил его в файл include между # ifdef/# endif.

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

Ответ 3

Обычно разработчики библиотек защищают свои включенные из нескольких включений с помощью #ifndef/# define/#endif "трюка", поэтому вам не нужно это делать.

Конечно, вы должны проверить... но в любом случае компилятор скажет вам в какой-то момент;-) Во всяком случае, хорошая практика проверять множественные включения, поскольку она замедляет цикл компиляции.

Ответ 4

Просто включите все внешние заголовки в один общий файл заголовка в вашем проекте, например. global.h и включить его во все ваши файлы c:

Он может выглядеть так:

#ifndef GLOBAL_GUARD
#define GLOBAL_GUARD

#include <glib.h>
/*...*/
typedef int  YOUR_INT_TYPE;
typedef char YOUR_CHAR_TYPE;
/*...*/
#endif

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

Ответ 5

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

#ifndef SOMEIDENTIFIER
#define SOMEIDENTIFIER
....header file body........
#endif

Если какой-либо заголовок был включен в другой заголовок, который был включен в ваш файл чем нет необходимости явно включать его снова, потому что он будет включен в файл рекурсивно

Ответ 6

Да, это необходимо или компилятор будет жаловаться, когда он пытается скомпилировать код, который он не "знает". Подумайте о том, что #include - это подсказка/подталкивание/локоть к компилятору, чтобы сообщить ему, чтобы он собирал декларации, структуры и т.д. Для успешного компиляции. # Ifdef/# endif header трюк, как указано jldupont, заключается в ускорении компиляции кода.

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

#ifndef __MY_HEADER_H__
#define __MY_HEADER_H__

#ifdef __cplusplus
extern "C" {
#endif


/* C code here such as structures, declarations etc. */

#ifdef __cplusplus
}
#endif

#endif /* __MY_HEADER_H__ */

Теперь, если это было включено несколько раз, компилятор включит его только один раз, поскольку символ __MY_HEADER_H__ определяется один раз, что ускоряет время компиляции. Обратите внимание на символ cplusplus в приведенном выше примере, это обычный стандартный способ справиться с компиляцией на С++, если у вас есть код C.

Я включил приведенное выше, чтобы показать это (несмотря на то, что он не имеет особого отношения к оригинальному вопросу с плакатом). Надеюсь это поможет, С наилучшими пожеланиями, Том.

PS: Извините за то, что позволил кому-то сжать это, так как я думал, что это будет полезно для новичков на C/С++. Оставьте комментарий/критику и т.д., Поскольку они наиболее приветствуются.

Ответ 7

Вам нужно включить заголовок из заголовка, и нет необходимости включать его в .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 */
}

Ответ 8

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

#ifndef INCLUDE_FILE_H
 #error "#include INCLUDE.h" must appear in source files before "#include THISFILE.h"
#endif

Ответ 9

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

#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 в разных заголовках.