我应该在标题中使用#include吗?

Vic*_*tor 70 c file-organization c-preprocessor

是否需要#include某个文件,如果在标题(*.h)内,使用此文件中定义的类型?

例如,如果我使用GLib并希望gchar在我的标题中定义的结构中使用基本类型,是否有必要执行a #include <glib.h>,知道我已经在我的*.c文件中有它?

如果是,我还必须把它放在#ifndef#define之后#define

Jon*_*ler 98

美国国家航空航天局的戈达德太空飞行中心(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 */
Run Code Online (Sandbox Code Playgroud)

magicsort.c

#include <magicsort.h>

void magicsort(void *array, size_t number, size_t size, Comparator cmp)
{
    ...body of sort...
}
Run Code Online (Sandbox Code Playgroud)

请注意,标头必须包含一些定义的标准标头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"

...
Run Code Online (Sandbox Code Playgroud)

这是我唯一知道模块的私有头不是实现文件中的第一个头的时候.但是,'config.h'的条件包含应该在'magicsort.h'本身.


更新2011-05-01

上面链接的URL不再起作用(404).您可以在EverySpec.com找到C++标准(582-2003-004); 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在标题中使用任何语句; 所有 #includes都在body文件中完成.然后,单元头文件必须包含#ifdef检查所需标头是否按正确顺序包含的语句.

备用设计的一个优点是#include正文文件中的列表正是makefile中所需的依赖列表,编译器会检查此列表.使用标准设计,必须使用工具来生成依赖关系列表.但是,所有分支推荐的开发环境都提供了这样的工具.

备用设计的一个主要缺点是,如果单元的必需标头列表发生更改,则必须编辑使用该单元的每个文件以更新#include语句列表.此外,编译器库单元所需的标头列表在不同的目标上可能不同.

备用设计的另一个缺点是必须修改编译器库头​​文件和其他第三方文件以添加所需的#ifdef语句.

不同的常见做法是在正文文件中的任何项目头文件之前包含所有系统头文件.此标准不遵循此惯例,因为某些项目头文件可能依赖于系统头文件,因为它们使用系统头中的定义,或者因为它们想要覆盖系统定义.此类项目头文件应包含#include 系统头的语句; 如果正文首先包含它们,编译器不会检查它.

GSFC标准可通过Internet Archive 2012-12-10获得

信息Eric S. Bullington提供:

可以通过Internet存档访问和下载引用的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 */
Run Code Online (Sandbox Code Playgroud)

  • 没有运营上的差异.使用没有指定值的`#define X`意味着X计算为空字符串,除了条件上下文,如`#if X`,其中它的计算结果为0; 很明显,`#define X 1`总是扩展为1.只要你总是用`#ifdef X`(或`#if defined(X)`或`#elif defined(X)`)而不是``#来测试如果X`,那么你也可以. (4认同)
  • 好的回答!+1 (3认同)

Ric*_*ton 20

一个好的做法是只在include文件需要时才将#includes放在include文件中.如果给定包含文件中的定义仅用于.c文件,则仅将其包含在.c文件中.

在你的情况下,我会将它包含在#ifdef /#endif之间的包含文件中.

这将最小化依赖性,以便在包含文件更改时不必重新编译不需要给定包含的文件.

  • 我想你误解了我的答案.通常的做法是在.h文件中使用#ifdef /#endif以避免多个.h文件包含. (19认同)

jld*_*ont 0

通常,库开发​​人员会使用#ifndef /#define / #endif“技巧”来保护他们的包含免受多重包含的影响,因此您不必这样做。

当然,您应该检查...但无论如何编译器会在某个时候告诉您;-) 无论如何,检查多个包含项是一个很好的做法,因为它会减慢编译周期。