Как структурировать #include в C
Скажем, у меня есть программа на C, которая разбита на набор файлов *.c и *.h. Если код из одного файла использует функции из другого файла, где я должен включить файл заголовка? Внутри файла *.c, который использовал функцию, или внутри заголовка этого файла?
Например, файл foo.c
включает в себя foo.h
, который содержит все объявления для foo.c
; то же самое для bar.c
а также bar.h
, функция foo1()
внутри foo.c
звонки bar1()
, который объявлен в bar.h
и определяется в bar.c
, Теперь вопрос, должен ли я включить bar.h
внутри foo.h
или внутри foo.c
?
Что было бы хорошим практическим правилом для таких проблем?
6 ответов
Вы должны включить foo.h в foo.c. Таким образом, другие файлы c, которые включают foo.h, не будут нести bar.h без необходимости. Это мой совет для включения заголовочных файлов:
- Добавьте определения включений в файлы c - таким образом файловые зависимости более очевидны при чтении кода.
- Разделите файл foo.h на два отдельных файла, например, foo_int.h и foo.h. Первый содержит объявления типа и forward, необходимые только для foo.c. Foo.h включает в себя функции и типы, необходимые внешним модулям. Это что-то вроде частного и публичного раздела foo.
- Избегайте перекрестных ссылок, то есть панели ссылок foo и ссылок на foo. Это может вызвать проблемы со связыванием, а также является признаком плохого дизайна.
Как уже отмечали другие, заголовок foo.h должен объявлять информацию, необходимую для возможности использования средств, предоставляемых исходным файлом foo.c. Это будет включать типы, перечисления и функции, предоставляемые foo.c. (Вы не используете глобальные переменные, не так ли? Если вы используете, то они также объявлены в foo.h.)
Заголовок foo.h должен быть автономным и идемпотентным. Автономный означает, что любой пользователь может включить foo.h и ему не нужно беспокоиться о том, какие другие заголовки могут понадобиться (потому что foo.h включает эти заголовки). Идемпотент означает, что если заголовок включен более одного раза, то никакого ущерба не будет. Это достигается классической техникой:
#ifndef FOO_H_INCLUDED
#define FOO_H_INCLUDED
...rest of the contents of foo.h...
#endif /* FOO_H_INCLUDED */
Заданный вопрос:
Файл foo.c включает в себя foo.h, который содержит все объявления для foo.c; То же самое для bar.c и bar.h. Функция foo1() внутри foo.c вызывает bar1(), которая объявлена в bar.h и определена в bar.c. Теперь вопрос в том, должен ли я включить bar.h внутри foo.h или внутри foo.c?
Это будет зависеть от того, зависят ли сервисы, предоставляемые foo.h, от bar.h или нет. Если другим файлам, использующим foo.h, понадобится один из типов или перечислений, определенных bar.h, чтобы использовать функциональность foo.h, то foo.h должен убедиться, что bar.h включен (включив его). Однако, если сервисы bar.h используются только в foo.c и не нужны тем, кто использует foo.h, тогда foo.h не должен включать bar.h
Используя примеры foo.c
а также foo.h
Я нашел эти рекомендации полезными:
Помните, что цель
foo.h
чтобы облегчить использованиеfoo.c
так что держите это как можно более простым, организованным и понятным. Будьте либеральны с комментариями, которые объясняют, как и когда использовать функцииfoo.c
- а когда их не использовать.foo.h
объявляет публичные чертыfoo.c
: функции, макросы, typedefs и (дрожать) глобальные переменные.foo.c
должен#include "foo.h
- см. обсуждение, а также комментарий Джонатана Леффлера ниже.Если
foo.c
для компиляции требуются дополнительные заголовки, включите их вfoo.c
,Если внешние заголовки требуются для
foo.h
скомпилировать, включить их вfoo.h
Используйте препроцессор для предотвращения
foo.h
от включения более одного раза. (Увидеть ниже.)Если по какой-то причине внешний заголовок потребуется для другого
.c
файл для использования функций вfoo.c
, включите заголовок вfoo.h
чтобы избавить следующего разработчика от ненужной отладки. Если вы против этого, рассмотрите возможность добавления макроса, который будет отображать инструкции во время компиляции, если требуемые заголовки не были включены.Не включать
.c
файл в другом.c
подать, если у вас нет веских причин и четко документировать.
Как отметил kgiannakakis, полезно отделить общедоступный интерфейс от определений и деклараций, необходимых только внутри foo.c
сам. Но вместо того, чтобы создавать два файла, иногда лучше позволить препроцессору сделать это за вас:
// foo.c
#define _foo_c_ // Tell foo.h it's being included from foo.c
#include "foo.h"
. . .
// foo.h
#if !defined(_foo_h_) // Prevent multiple inclusion
#define _foo_h_
// This section is used only internally to foo.c
#ifdef _foo_c_
. . .
#endif
// Public interface continues to end of file.
#endif // _foo_h_ // Last-ish line in foo.h
Я бы включил только заголовочные файлы в файл *.h, который требуется для самого заголовочного файла. Заголовочные файлы, которые необходимы для исходного файла, на мой взгляд, должны быть включены в исходный файл, чтобы зависимости были очевидны из источника. Заголовочные файлы должны быть созданы для обработки нескольких включений, чтобы вы могли поместить их в оба, если это требуется для ясности.
Файл.h должен определять общедоступный интерфейс (он же api) к функциям в файле.c.
Если интерфейс file1 использует интерфейс file2, тогда #include file2.h в file1.h
Если реализация в file1.c использует материал из file2.c, то file1.c должен #include file2.h.
Я должен признать, хотя - потому что я всегда #include file1.h в file1.c - я обычно не стал бы беспокоить #include file2.h непосредственно в file1.c, если он уже был #include в file1.h
Если вы когда-нибудь окажетесь в ситуации, когда два файла.c включают в себя файлы.h, то это признак того, что модульность сломалась, и вам следует немного подумать о реструктуризации.
Я включаю самый минимальный набор заголовков в .h
файл, а остальные включить в .c
файл. Это имеет преимущество в том, что иногда сокращается время компиляции. Учитывая ваш пример, если foo.h
на самом деле не нужно bar.h
но включает его в любом случае, а некоторые другие файлы включают foo.h
то этот файл будет перекомпилирован если bar.h
изменения, даже если это на самом деле не нужно или использовать bar.h
,