Должен ли я использовать #include в заголовках? - PullRequest
64 голосов
/ 26 ноября 2009

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

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

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

Ответы [ 9 ]

89 голосов
/ 26 ноября 2009

Центр космических полетов имени Годдарда ( 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. Клиентские подразделения могут вызывать только функции, определенные в заголовке блока; они могут не вызывать функции, определенные в тело, но не объявлено в заголовке. Клиентские блоки могут не иметь доступа к переменным, объявленным в теле но не в шапке.

A компонент содержит одну или несколько единиц. Например, математическая библиотека является компонентом, который содержит несколько единиц, таких как вектор, матрица и кватернион.

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

Некоторые причины наличия нескольких файлов тела для юнита:

  • Часть кода тела зависит от аппаратного обеспечения или операционной системы, а остальное является общим.
  • Файлы слишком велики.
  • Устройство представляет собой общий пакет утилит, и некоторые проекты будут использовать только некоторые из функции. Помещение каждой функции в отдельный файл позволяет компоновщику исключать те, которые не используется из окончательного изображения.

§2.1.1 Заголовок включает обоснование

Этот стандарт требует, чтобы заголовок блока содержал #include операторов для всех остальных требуемых заголовков по заголовку блока. Размещение #include для заголовка блока первым в теле блока позволяет компилятору убедитесь, что заголовок содержит все необходимые операторы #include.

Альтернативный дизайн, не разрешенный этим стандартом, не допускает операторов #include в заголовках; все #include делаются в файлах тела. В этом случае заголовочные файлы должны содержать #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 */
19 голосов
/ 26 ноября 2009

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

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

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

0 голосов
/ 26 ноября 2009

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

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

0 голосов
/ 26 ноября 2009

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

#ifndef INCLUDE_FILE_H
 #error "#include INCLUDE.h" must appear in source files before "#include THISFILE.h"
#endif
0 голосов
/ 26 ноября 2009

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

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

Используется в тех случаях, когда у вас есть компилятор C ++ и компиляция простого кода 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 ++, если у вас есть код на C.

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

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

0 голосов
/ 26 ноября 2009

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

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

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

0 голосов
/ 26 ноября 2009

Просто включите все внешние заголовки в один общий файл заголовка в вашем проекте, например. 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, чтобы избежать множественных включений, неправильных множественных определений и т. Д.

0 голосов
/ 26 ноября 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...