是否有必要使用#include
某些文件,如果在标头(* .h)中使用了此文件中定义的类型?
例如,如果我使用GLib并希望gchar
在我的标头中定义的结构中使用基本类型,那么是否#include <glib.h>
知道我的* .c文件中已经包含了它,是否有必要做一个?
如果是,我还必须将其放在#ifndef
和之间#define
或之后#define
吗?
Answers:
NASA的戈达德太空飞行中心(GSFC)规定C头为标头,必须有可能在源文件中包含头作为唯一的头,然后将使用该头提供的功能来编译该代码。
这意味着标头必须是独立的,幂等的并且是最小的:
此规则的好处是,如果有人需要使用标头,则不必费力确定还必须包含哪些其他标头-他们知道标头提供了所有必要的内容。
可能的不利之处是某些标头可能会多次包含在内;这就是为什么多重包含标头保护至关重要的原因(以及为什么编译器尽量避免重新包含标头的原因)。
此规则的手段,如果报头使用类型-如“ FILE *
”或“ size_t
” -那么它必须确保相应的其他报头(<stdio.h>
或<stddef.h>
例如)应包括在内。一个推论,往往忘记了,是此时的标题不应该包括被任何其他头不为了使用程序包所需的包的用户。换句话说,标题应该最小。
此外,GSFC规则提供了一种简单的技术来确保发生这种情况:
因此,假设我们有一个魔术排序。
#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 */
#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”本身中。
上面链接的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
在标题中使用任何语句;所有#include
的都在主体文件中完成。然后,单元头文件必须包含#ifdef
用于检查所需头是否以正确顺序包含的语句。替代设计的一个优点是,
#include
主体文件中的列表正是makefile中所需的依赖项列表,并且此列表由编译器检查。对于标准设计,必须使用工具来生成依赖关系列表。但是,所有分支推荐的开发环境都提供了这样的工具。替代设计的主要缺点是,如果某个单元的所需标头列表发生更改,则必须编辑使用该单元的每个文件以更新
#include
语句列表。同样,在不同的目标上,编译器库单元所需的头列表可能会有所不同。替代设计的另一个缺点是,必须修改编译器库头文件和其他第三方文件才能添加所需的
#ifdef
语句。另一种不同的惯例是在主体文件中包括所有系统头文件,然后再包括任何项目头文件。该标准不遵循这种做法,因为某些项目头文件可能依赖于系统头文件,或者是因为它们使用了系统头中的定义,或者是因为它们想覆盖系统定义。这样的项目头文件应包含
#include
系统头的语句;如果主体首先包含它们,则编译器不会对此进行检查。
可以通过Internet档案访问和下载参考的NASA C编码标准:
问题还问:
如果是,我是否还必须将其(
#include
线)放在#ifndef
和之间#define
或之后#define
。
答案显示了正确的机制-嵌套的include等应位于的后面#define
(并且#define
应当是标头中的第二个非注释行),但是并不能解释为什么这是正确的。
试想,如果您将会怎么样#include
之间#ifndef
和#define
。假设其他标头本身包括各种标头,甚至可能是#include "magicsort.h"
间接的。如果第二个包含magicsort.h
发生在之前#define MAGICSORT_H_INCLUDED
,则在定义其定义的类型之前,将第二次包含头。因此,在C89和C99中,任何typedef
类型的名称都将被错误地重新定义(C2011允许将它们重新定义为相同的类型),并且您将获得多次处理文件的开销,这在第一个中就破坏了标题保护功能。地点。这也是为什么the#define
是第二行而不是仅在#endif
。之前编写的原因。给出的公式是可靠的:
#ifndef HEADERGUARDMACRO
#define HEADERGUARDMACRO
...original content of header — other #include lines, etc...
#endif /* HEADERGUARDMACRO */
#define X
不指定任何值,则使用X表示空字符串,除非在条件上下文中(如X等于#if X
0)。显然,#define X 1
总是扩展为1。只要您始终使用#ifdef X
(或#if defined(X)
或#elif defined(X)
)而不是使用进行测试#if X
,那么您都可以使用。
<inttypes.h>
包含<stdint.h>
。
好的做法是仅在包含文件需要它们时才将其包含在包含文件中。如果给定包含文件中的定义仅在.c文件中使用,则仅在.c文件中包括它。
在您的情况下,我会将其包含在#ifdef /#endif之间的包含文件中。
这样可以最大程度地减少依赖关系,因此,如果包含文件发生更改,则不需要重新编译不需要给定包含的文件。
#ifndef MY_HEADER
// header contents
#define MY_HEADER
#endif
这消除了像其他参考一样遵循的要求#endif
// MY_HEADER
是的,这是必要的,否则编译器在尝试编译不“意识到”的代码时会抱怨。认为#include是对编译器的提示/轻推/弯头,告诉编译器选择声明,结构等以使编译成功。jldupont指出的#ifdef /#endif标头技巧是为了加快代码编译速度。
在具有C ++编译器并编译纯C代码的情况下使用它,如下所示。
这是技巧的示例:
#ifndef __MY_HEADER_H__ #定义__MY_HEADER_H__ #ifdef __cplusplus extern“ C” { #万一 / *此处的C代码,例如结构,声明等。* / #ifdef __cplusplus } #万一 #endif / * __MY_HEADER_H__ * /
现在,如果多次包含该符号,则编译器将只包含一次,因为该符号__MY_HEADER_H__
定义了一次,从而加快了编译时间。
请注意上面示例中的符号cplusplus,这是处理C ++编译的正常标准方式。
我已经包含了以上内容以证明这一点(尽管与发帖人的原始问题并不真正相关)。希望这对您有所帮助,汤姆,谢谢。
PS:抱歉让任何人对此表示不满,因为我认为这对C / C ++新手来说是有用的花絮。留下评论/批评等,因为它们是最受欢迎的。
您需要从标头中包含标头,而无需在.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 _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函数原型。
__const
)是什么意思?
只需将所有外部标头包含在项目的一个公共标头文件中,例如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来避免多个包含,非法的多个定义等。