我应该在头文件中使用#include吗?
是否有必要#include
一些文件,如果在一个头文件(* .h)中,在这个文件中定义的types被使用?
例如,如果我使用GLib并希望在我的头文件中定义的结构中使用gchar
基本types,是否有必要执行#include <glib.h>
,知道我已经在我的* .c文件中有它?
如果是的话,我也必须把它放在#ifndef
和#define
之间或#define
之后?
美国航空航天局戈达德太空飞行中心( GSFC )对于C状态标题的规则是,必须能够在源文件中包含一个标题作为唯一的标题,然后使用该标题提供的工具编码。
这个规则的好处是,如果有人需要使用标题,他们不需要努力计算出哪些其他标题也必须被包含 – 他们知道标题提供了一切必要的东西。
可能的缺点是可能会多次包含一些标题; 这就是为什么多个包含头文件保护是至关重要的(为什么编译器尽可能避免重新包括头文件)。
履行
这个规则意味着如果头文件使用了一个types(比如“ FILE *
”或者“ size_t
”),那么它必须确保包含相应的头文件(例如<stdio.h>
或<stddef.h>
)。 经常被遗忘的一个必然结论是,头文件不应该包含封装用户不需要的任何其他头文件,以便使用该封装文件。 换句话说,标题应该是最小的。
此外,GSFC规则提供了一个简单的技术来确保发生这种情况:
- 在定义function的源文件中,标题必须是列出的第一个标题。
因此,假设我们有一个魔术sorting。
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>
,尽pipe其他几个也是这么做的( <stdio.h>
, <stdlib.h>
, <string.h>
,可能还有其他几个)。
另外,如前所述,如果实现文件需要一些其他的头文件,那就这样吧,这对于一些额外的头文件是必须的。 但是实现文件('magicsort.c')应该包含它们本身,而不是依赖它的头部来包含它们。 标题只应该包括软件用户需要的东西; 而不是实施者需要什么。
configuration标题
如果你的代码使用了一个configuration头文件(例如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
上面链接的URL不再有效(404)。 您可以在EverySpec.com上findC ++标准(582-2003-004); C标准(582-2000-005)似乎失踪了。
C标准的指导原则是:
§2.1单位
(1)代码应以单元或独立的头文件的forms构成。
(2)一个单元应由单个头文件(.h)和一个或多个主体(.c)文件组成。 头文件和正文文件统称为源文件。
(3)单元头文件应包含客户单元要求的所有相关信息。 单元的客户端只需要访问头文件就可以使用单元。
(4)单元头文件应包含单元头所需的所有其他头文件的#include语句。 这可以让客户通过包含一个头文件来使用一个单元。
(5)单元主体文件应在所有其他#include语句之前包含单元头文件的#include语句。 这让编译器validation所有需要的#include语句都在头文件中。
(6)主体文件只能包含与一个单元相关的function。 一个主体文件可能不提供在不同头文件中声明的函数的实现。
(7)使用给定单元U的任何部分的所有客户单元应包括单元U的头文件; 这确保了单元U中的实体只有一个地方被定义。 客户端单元可以仅调用在单元头中定义的function; 他们不能调用正文中定义的函数,但是不能在头文件中声明。 客户端单元不能访问在主体中声明的variables,但不能访问头中的variables。
一个组件包含一个或多个单元。 例如,math库是一个包含多个单位(如vector,matrix和四元数)的组件。
独立头文件没有关联的主体; 例如,一个普通的types头不会声明函数,所以不需要body。
一个单元有多个主体文件的原因:
- 部分身体代码是硬件或操作系统的依赖,但其余的是常见的。
- 这些文件太大了。
- 该单元是一个常用的实用程序包,有些项目只会使用一些function。 将每个函数放在单独的文件中允许链接器从最终图像中排除未使用的函数。
§2.1.1标题包含基本原理
这个标准要求单元的头部包含单元头部所需的所有其他头部的
#include
语句。 首先在单元体中放置单元头的#include
允许编译器validation头是否包含所有需要的#include
语句。本标准不允许的替代devise,不允许在头文件中包含
#include
语句; 所有#include
都在主体文件中完成。 单元头文件必须包含#ifdef
语句,用于检查所需的头文件是否包含在正确的顺序中。备用devise的一个优点是,正文文件中的
#include
列表正是makefile所需的依赖列表,编译器检查此列表。 在标准devise中,必须使用一个工具来生成相关性列表。 但是,所有推荐的开发环境都提供了这样的工具。替代devise的一个主要缺点是,如果单元的所需标题列表发生更改,则必须编辑使用该单元的每个文件以更新
#include
语句列表。 此外,编译器库单元所需的头列表可能在不同的目标上有所不同。替代devise的另一个缺点是必须修改编译器库头文件和其他第三方文件,以添加所需的
#ifdef
语句。一个不同的常见做法是将所有系统头文件包含在正文文件中的任何项目头文件之前。 这个标准并没有遵循这种做法,因为某些项目头文件可能依赖于系统头文件,或者是因为它们使用了系统头中的定义,或者是因为它们想要覆盖系统定义。 这样的项目头文件应该包含系统头文件的
#include
语句; 如果主体包含它们,编译器不会检查这个。
GSFC标准可通过互联网归档2012-12-10
信息提供Eric S. Bullington :
引用的美国宇航局C编码标准可以通过互联网存档访问和下载:
测序
这个问题还问:
如果是,我也必须把它放在
#ifndef
和#define
之间或#define
之后。
答案显示了正确的机制 – 嵌套包含等等,应该在#define
(而#define
应该是头文件中的第二个非注释行) – 但是这并不能解释为什么这是正确的。
考虑一下如果将#include
放在#ifndef
和#define
之间会发生什么。 假设其他头文件本身包含了各种头文件,可能甚至包括#include "magicsort.h"
。 如果第二个包含magicsort.h
发生在#define MAGICSORT_H_INCLUDED
之前,那么在定义它所定义的types之前,头将被第二次包含。 因此,在C89和C99中,任何typedef
types名称将被错误地重新定义(C2011允许它们被重新定义为相同types), 并且您将多次处理该文件的开销,从而破坏了头文件保护的目的第一名。 这也是为什么#define
是第二行,而不是在#endif
之前写的。 给出的公式是可靠的:
#ifndef HEADERGUARDMACRO #define HEADERGUARDMACRO ...original content of header — other #include lines, etc... #endif /* HEADERGUARDMACRO */
一个好的做法是只在包含文件需要的时候才将#include包含在包含文件中。 如果给定包含文件中的定义仅用于.c文件,则仅将其包含在.c文件中。
在你的情况下,我会将它包含在#ifdef /#endif之间的包含文件中。
这将最大限度地减less依赖关系,如果包含文件发生更改,那么不需要重新编译那些不需要给定包含的文件。
通常,库开发人员保护他们包括多个包括#ifndef / #define / #endif“技巧”,所以你不必这样做。
当然,你应该检查…但不pipe怎样,编译器会在某个时候告诉你;-)这是一个很好的做法,检查多个包含,因为它会减慢编译周期。
在编译期间,预处理器只是用指定的文件内容replace#include指令。 为了防止无尽的循环,应该使用
#ifndef SOMEIDENTIFIER #define SOMEIDENTIFIER ....header file body........ #endif
如果某个头文件被包含到另一个包含在文件中的头文件中,则不需要再次明确地包含它,因为它将被recursion地包含在文件中
是的,这是必要的,或者编译器会在尝试编译“不知道”的代码时抱怨。 想一想#include是一个提示/微调/肘向编译器告诉它拿起声明,结构等为了一个成功的编译。 jldupont指出的#ifdef /#endif标题技巧是加速编译代码。
它用于你有C ++编译器和编译纯C代码的情况,如下所示这里是一个诀窍的例子:
#ifndef __MY_HEADER_H__ #define __MY_HEADER_H__ #ifdef __cplusplus extern“C”{ #万一 / *这里的C代码,如结构,声明等* / #ifdef __cplusplus } #万一 #endif / * __MY_HEADER_H__ * /
现在,如果包含多次,编译器将只包含一次,因为符号__MY_HEADER_H__
被定义一次,这会加快编译时间。 在上面的例子中,注意符号cplusplus,如果你有一个C代码,那么这就是应对C ++编译的正常标准方法。
我已经包含了上面的内容来展示这个(尽pipe与海报的原始问题没有多大关系)。 希望这有助于,最好的问候,汤姆。
PS:对不起,让任何人都低估了这一点,因为我认为这对于C / C ++的新手来说是有用的。 留下评论/批评等,因为他们是最受欢迎的。
只需将所有外部头文件包含在项目中的一个公共头文件中,例如global.h,并将其包含在所有的c文件中:
它可以看起来像这样:
#ifndef GLOBAL_GUARD #define GLOBAL_GUARD #include <glib.h> /*...*/ typedef int YOUR_INT_TYPE; typedef char YOUR_CHAR_TYPE; /*...*/ #endif
该文件使用包含警戒来避免多重包含,非法多重定义等。
你需要在标题中包含标题,而不需要将其包含在.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 */ }
我使用下面的构造来确保在包含之前包含所需的包含文件。 我只将所有头文件包含在源文件中。
#ifndef INCLUDE_FILE_H #error "#include INCLUDE.h" must appear in source files before "#include THISFILE.h" #endif
我通常做的是做一个单一的包含文件,其中包含所有必要的依赖关系正确的顺序。 所以我可能会有:
#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可以被包含在任何地方,没有订单要求,但我仍然有我的依赖,types和API函数原型在不同的头的豪华。