Как структурировать #include в C - PullRequest
6 голосов
/ 09 ноября 2008

Скажем, у меня есть программа на 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 ]

6 голосов
/ 09 ноября 2008

Вы должны включить 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. Это может вызвать проблемы со связыванием, а также является признаком плохого дизайна
4 голосов
/ 10 ноября 2008

Как уже отмечали другие, заголовок 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

3 голосов
/ 09 ноября 2008

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

2 голосов
/ 10 ноября 2008

Используя примеры foo.c и foo.h Я нашел эти рекомендации полезными:

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

  • foo.h объявляет общедоступные функции foo.c: функции, макросы, typedefs и ( shudder ) глобальные переменные.

  • 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
2 голосов
/ 09 ноября 2008

Файл .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, то это признак того, что модульность сломалась, и вам следует немного подумать о реструктуризации.

2 голосов
/ 09 ноября 2008

Я включил в файл .h самый минимальный набор заголовков, а остальные - в файл .c. Это имеет то преимущество, что иногда сокращается время компиляции. Учитывая ваш пример, если foo.h на самом деле не нужно bar.h, но в любом случае включает его, а какой-то другой файл включает foo.h, то этот файл будет перекомпилирован, если bar.h изменится, даже если он на самом деле не нужен или используйте bar.h.

...