C++ 为什么我的包含守卫不阻止递归包含和多个符号定义?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14909997/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why aren't my include guards preventing recursive inclusion and multiple symbol definitions?
提问by Andy Prowl
Two common questions about include guards:
关于包括守卫的两个常见问题:
FIRST QUESTION:
Why aren't include guards protecting my header files from mutual, recursive inclusion? I keep getting errors about non-existing symbols which are obviously there or even weirder syntax errors every time I write something like the following:
"a.h"
#ifndef A_H #define A_H #include "b.h" ... #endif // A_H
"b.h"
#ifndef B_H #define B_H #include "a.h" ... #endif // B_H
"main.cpp"
#include "a.h" int main() { ... }
Why do I get errors compiling "main.cpp"? What do I need to do to solve my problem?
第一个问题:
为什么不包含保护我的头文件免受相互递归包含的保护?每次我编写类似以下内容时,我都会收到关于不存在的符号的错误,这些错误显然存在甚至更奇怪的语法错误:
“啊”
#ifndef A_H #define A_H #include "b.h" ... #endif // A_H
“哈”
#ifndef B_H #define B_H #include "a.h" ... #endif // B_H
“主.cpp”
#include "a.h" int main() { ... }
为什么编译“main.cpp”会出错?我需要做什么来解决我的问题?
SECOND QUESTION:
Why aren't include guards preventing multiple definitions? For instance, when my project contains two files that include the same header, sometimes the linker complains about some symbol being defined multiple times. For instance:
"header.h"
#ifndef HEADER_H #define HEADER_H int f() { return 0; } #endif // HEADER_H
"source1.cpp"
#include "header.h" ...
"source2.cpp"
#include "header.h" ...
Why is this happening? What do I need to do to solve my problem?
第二个问题:
为什么不包括防止多重定义的守卫?例如,当我的项目包含两个包含相同标头的文件时,有时链接器会抱怨某些符号被多次定义。例如:
“头文件.h”
#ifndef HEADER_H #define HEADER_H int f() { return 0; } #endif // HEADER_H
“source1.cpp”
#include "header.h" ...
“source2.cpp”
#include "header.h" ...
为什么会这样?我需要做什么来解决我的问题?
回答by Andy Prowl
FIRST QUESTION:
Why aren't include guards protecting my header files from mutual, recursive inclusion?
第一个问题:
为什么不包含保护我的头文件免受相互递归包含的保护?
They are.
他们是。
What they are not helping with is dependencies between the definitions of data structures in mutually-including headers. To see what this means, let's start with a basic scenario and see why include guards do help with mutual inclusions.
他们没有帮助的是相互包含的 headers 中数据结构定义之间的依赖关系。要了解这意味着什么,让我们从一个基本场景开始,看看为什么包含守卫确实有助于相互包含。
Suppose your mutually including a.h
and b.h
header files have trivial content, i.e. the ellipses in the code sections from the question's text are replaced with the empty string. In this situation, your main.cpp
will happily compile. And this is only thanks to your include guards!
假设您相互包含的文件a.h
和b.h
头文件具有微不足道的内容,即问题文本中代码部分中的省略号被替换为空字符串。在这种情况下,您main.cpp
将愉快地编译。这要归功于你的包含守卫!
If you're not convinced, try removing them:
如果您不相信,请尝试删除它们:
//================================================
// a.h
#include "b.h"
//================================================
// b.h
#include "a.h"
//================================================
// main.cpp
//
// Good luck getting this to compile...
#include "a.h"
int main()
{
...
}
You'll notice that the compiler will report a failure when it reaches the inclusion depth limit. This limit is implementation-specific. Per Paragraph 16.2/6 of the C++11 Standard:
您会注意到编译器在达到包含深度限制时会报告失败。此限制是特定于实现的。根据 C++11 标准的第 16.2/6 段:
A #include preprocessing directive may appear in a source file that has been read because of a #include directive in another file, up to an implementation-defined nesting limit.
#include 预处理指令可能出现在由于另一个文件中的 #include 指令而被读取的源文件中,直到实现定义的嵌套限制。
So what's going on?
那么发生了什么?
- When parsing
main.cpp
, the preprocessor will meet the directive#include "a.h"
. This directive tells the preprocessor to process the header filea.h
, take the result of that processing, and replace the string#include "a.h"
with that result; - While processing
a.h
, the preprocessor will meet the directive#include "b.h"
, and the same mechanism applies: the preprocessor shall process the header fileb.h
, take the result of its processing, and replace the#include
directive with that result; - When processing
b.h
, the directive#include "a.h"
will tell the preprocessor to processa.h
and replace that directive with the result; - The preprocessor will start parsing
a.h
again, will meet the#include "b.h"
directive again, and this will set up a potentially infinite recursive process. When reaching the critical nesting level, the compiler will report an error.
- 解析时
main.cpp
,预处理器将满足指令#include "a.h"
。该指令告诉预处理器处理头文件a.h
,获取处理结果,并#include "a.h"
用该结果替换字符串; - 处理时
a.h
,预处理器会满足指令#include "b.h"
,同样的机制适用:预处理器处理头文件b.h
,取其处理结果,并#include
用该结果替换指令; - 处理时
b.h
,指令#include "a.h"
会告诉预处理器处理a.h
并用结果替换该指令; - 预处理器将
a.h
再次开始解析,再次满足#include "b.h"
指令,这将建立一个潜在的无限递归过程。当达到临界嵌套级别时,编译器会报错。
When include guards are present, however, no infinite recursion will be set up in step 4. Let's see why:
但是,当存在包含守卫时,第 4 步中将不会设置无限递归。让我们看看原因:
- (same as before) When parsing
main.cpp
, the preprocessor will meet the directive#include "a.h"
. This tells the preprocessor to process the header filea.h
, take the result of that processing, and replace the string#include "a.h"
with that result; - While processing
a.h
, the preprocessor will meet the directive#ifndef A_H
. Since the macroA_H
has not yet been defined, it will keep processing the following text. The subsequent directive (#defines A_H
) defines the macroA_H
. Then, the preprocessor will meet the directive#include "b.h"
: the preprocessor shall now process the header fileb.h
, take the result of its processing, and replace the#include
directive with that result; - When processing
b.h
, the preprocessor will meet the directive#ifndef B_H
. Since the macroB_H
has not yet been defined, it will keep processing the following text. The subsequent directive (#defines B_H
) defines the macroB_H
. Then, the directive#include "a.h"
will tell the preprocessor to processa.h
and replace the#include
directive inb.h
with the result of preprocessinga.h
; - The compiler will start preprocessing
a.h
again, and meet the#ifndef A_H
directive again. However, during previous preprocessing, macroA_H
has been defined. Therefore, the compiler will skip the following text this time until the matching#endif
directive is found, and the output of this processing is the empty string (supposing nothing follows the#endif
directive, of course). The preprocessor will therefore replace the#include "a.h"
directive inb.h
with the empty string, and will trace back the execution until it replaces the original#include
directive inmain.cpp
.
- (同前)解析时
main.cpp
,预处理器将满足指令#include "a.h"
。这告诉预处理器处理头文件a.h
,获取处理结果,并#include "a.h"
用该结果替换字符串; - 在处理时
a.h
,预处理器将满足指令#ifndef A_H
。由于宏A_H
尚未定义,它将继续处理以下文本。随后的指令 (#defines A_H
) 定义了宏A_H
。然后,预处理器将满足指令#include "b.h"
:预处理器现在应处理头文件b.h
,获取其处理结果,并#include
用该结果替换指令; - 处理时
b.h
,预处理器将满足指令#ifndef B_H
。由于宏B_H
尚未定义,它将继续处理以下文本。随后的指令 (#defines B_H
) 定义了宏B_H
。然后,指令#include "a.h"
会告诉预处理器处理a.h
并用预处理的结果替换#include
指令;b.h
a.h
- 编译器将
a.h
再次开始预处理,并再次满足#ifndef A_H
指令。但是,在之前的预处理过程中,A_H
已经定义了宏。因此,编译器这次会跳过后面的文本,直到#endif
找到匹配的指令,并且这次处理的输出是空字符串(#endif
当然,假设指令后面没有任何内容)。因此,预处理器将用空字符串替换#include "a.h"
in 指令b.h
,并将追溯执行,直到它替换 .in 中的原始#include
指令main.cpp
。
Thus, include guards do protect against mutual inclusion. However, they can't help with dependencies between the definitions of your classesin mutually-including files:
因此,包含守卫确实可以防止相互包含。但是,它们无法帮助解决相互包含文件中类定义之间的依赖关系:
//================================================
// a.h
#ifndef A_H
#define A_H
#include "b.h"
struct A
{
};
#endif // A_H
//================================================
// b.h
#ifndef B_H
#define B_H
#include "a.h"
struct B
{
A* pA;
};
#endif // B_H
//================================================
// main.cpp
//
// Good luck getting this to compile...
#include "a.h"
int main()
{
...
}
Given the above headers, main.cpp
will not compile.
鉴于上述标题,main.cpp
将无法编译。
Why is this happening?
为什么会这样?
To see what's going on, it is enough to go through steps 1-4 again.
要查看发生了什么,再次执行步骤 1-4 就足够了。
It is easy to see that the first three steps and most of the fourth step are unaffected by this change (just read through them to get convinced). However, something different happens at the end of step 4: after replacing the #include "a.h"
directive in b.h
with the empty string, the preprocessor will start parsing the content of b.h
and, in particular, the definition of B
. Unfortunately, the definition of B
mentions class A
, which has never been met before exactly becauseof the inclusion guards!
很容易看出前三个步骤和第四个步骤的大部分内容不受此更改的影响(只需通读它们即可确信)。然而,在第 4 步结束时发生了一些不同的事情:在用空字符串替换#include "a.h"
指令 in后b.h
,预处理器将开始解析 的内容b.h
,特别是B
. 不幸的是,定义中B
提到了 class A
,这正是因为包含守卫而以前从未遇到过的!
Declaring a member variable of a type which has not been previously declared is, of course, an error, and the compiler will politely point that out.
声明一个以前没有声明过的类型的成员变量当然是一个错误,编译器会礼貌地指出这一点。
What do I need to do to solve my problem?
我需要做什么来解决我的问题?
You need forward declarations.
您需要提前声明。
In fact, the definitionof class A
is not required in order to define class B
, because a pointerto A
is being declared as a member variable, and not an object of type A
. Since pointers have fixed size, the compiler won't need to know the exact layout of A
nor to compute its size in order to properly define class B
. Hence, it is enough to forward-declareclass A
in b.h
and make the compiler aware of its existence:
实际上,定义类的A
,以限定类不需要B
的,因为一个指针到A
正在被声明作为成员变量,而不是类型的对象A
。由于指针的大小是固定的,编译器不需要知道确切的布局,A
也不需要计算它的大小来正确定义 class B
。因此,它是足够前瞻性声明类A
中b.h
,使编译器知道它的存在:
//================================================
// b.h
#ifndef B_H
#define B_H
// Forward declaration of A: no need to #include "a.h"
struct A;
struct B
{
A* pA;
};
#endif // B_H
Your main.cpp
will now certainly compile. A couple of remarks:
你main.cpp
现在肯定会编译。几点说明:
- Not only breaking the mutual inclusion by replacing the
#include
directive with a forward declaration inb.h
was enough to effectively express the dependency ofB
onA
: using forward declarations whenever possible/practical is also considered to be a good programming practice, because it helps avoiding unnecessary inclusions, thus reducing the overall compilation time. However, after eliminating the mutual inclusion,main.cpp
will have to be modified to#include
botha.h
andb.h
(if the latter is needed at all), becauseb.h
is no more indirectly#include
d througha.h
; - While a forward declaration of class
A
is enough for the compiler to declare pointers to that class (or to use it in any other context where incomplete types are acceptable), dereferencing pointers toA
(for instance to invoke a member function) or computing its size are illegaloperations on incomplete types: if that is needed, the full definition ofA
needs to be available to the compiler, which means the header file that defines it must be included. This is why class definitions and the implementation of their member functions are usually split into a header file and an implementation file for that class (class templatesare an exception to this rule): implementation files, which are never#include
d by other files in the project, can safely#include
all the necessary headers to make definitions visible. Header files, on the other hand, won't#include
other header files unlessthey really need to do so (for instance, to make the definition of a base classvisible), and will use forward-declarations whenever possible/practical.
- 不仅通过更换打破相互包容
#include
与向前声明指令在b.h
足以有效地表达的依赖B
上A
:使用前置声明尽可能/实际也被认为是一个良好的编程习惯,因为它有助于避免不必要的夹杂物,从而减少整体编译时间。但是,在消除相互包含之后,main.cpp
将不得不修改为#include
两者a.h
和b.h
(如果需要后者的话),因为b.h
不再是间接的#include
d 到a.h
; - 虽然类的前向声明
A
足以让编译器声明指向该类的指针(或在可接受不完整类型的任何其他上下文中使用它),但取消引用指向A
(例如调用成员函数)或计算其大小是对不完整类型的非法操作:如果需要,A
编译器需要提供完整的定义,这意味着必须包含定义它的头文件。这就是为什么类定义和它们的成员函数的实现通常被分成一个头文件和一个该类的实现文件(类模板是这个规则的一个例外):实现文件,永远不会#include
被项目中的其他文件d ,可以安全#include
使定义可见的所有必要标题。另一方面,#include
头文件不会使用其他头文件,除非它们确实需要这样做(例如,使基类的定义可见),并且将在可能/实际的情况下使用前向声明。
SECOND QUESTION:
Why aren't include guards preventing multiple definitions?
第二个问题:
为什么不包括防止多重定义的守卫?
They are.
他们是。
What they are not protecting you from is multiple definitions in separate translation units. This is also explained in this Q&Aon StackOverflow.
他们没有保护您免受单独翻译单元中的多个定义的影响。这也在StackOverflow 上的这个问答中进行了解释。
Too see that, try removing the include guards and compiling the following, modified version of source1.cpp
(or source2.cpp
, for what it matters):
太看到了,尝试删除包含守卫并编译以下修改版本source1.cpp
(或source2.cpp
,重要的是):
//================================================
// source1.cpp
//
// Good luck getting this to compile...
#include "header.h"
#include "header.h"
int main()
{
...
}
The compiler will certainly complain here about f()
being redefined. That's obvious: its definition is being included twice! However, the above source1.cpp
will compile without problems when header.h
contains the proper include guards. That's expected.
编译器肯定会在这里抱怨f()
被重新定义。这很明显:它的定义被包含了两次!但是,source1.cpp
当header.h
包含正确的 include guards时,上面的编译将没有问题。这是预期的。
Still, even when the include guards are present and the compiler will stop bothering you with error message, the linkerwill insist on the fact that multiple definitions being found when merging the object code obtained from the compilation of source1.cpp
and source2.cpp
, and will refuse to generate your executable.
尽管如此,即使存在包含守卫并且编译器不会再用错误消息来打扰您,链接器也会坚持这样一个事实,即在合并从source1.cpp
and的编译中获得的目标代码时发现了多个定义source2.cpp
,并且将拒绝生成您的可执行。
Why is this happening?
为什么会这样?
Basically, each .cpp
file (the technical term in this context is translation unit) in your project is compiled separately and independently. When parsing a .cpp
file, the preprocessor will process all the #include
directives and expand all macro invocations it encounters, and the output of this pure text processing will be given in input to the compiler for translating it into object code. Once the compiler is done with producing the object code for one translation unit, it will proceed with the next one, and all the macro definitions that have been encountered while processing the previous translation unit will be forgotten.
基本上,您项目中的每个.cpp
文件(在此上下文中的技术术语是翻译单元)都是单独独立编译的。在解析.cpp
文件时,预处理器将处理所有#include
指令并展开它遇到的所有宏调用,并将这种纯文本处理的输出作为输入提供给编译器以将其翻译成目标代码。一旦编译器完成为一个翻译单元生成目标代码,它将继续下一个,并且在处理前一个翻译单元时遇到的所有宏定义都将被遗忘。
In fact, compiling a project with n
translation units (.cpp
files) is like executing the same program (the compiler) n
times, each time with a different input: different executions of the same program won't share the state of the previous program execution(s). Thus, each translation is performed independently and the preprocessor symbols encountered while compiling one translation unit will not be remembered when compiling other translation units (if you think about it for a moment, you will easily realize that this is actually a desirable behavior).
事实上,用n
翻译单元(.cpp
文件)编译一个项目就像执行同一个程序(编译器)n
,每次都有不同的输入:同一个程序的不同执行不会共享前一个程序执行的状态)。因此,每个翻译都是独立执行的,编译一个翻译单元时遇到的预处理器符号在编译其他翻译单元时不会被记住(如果你仔细想想,你会很容易意识到这实际上是一种理想的行为)。
Therefore, even though include guards help you preventing recursive mutual inclusions and redundantinclusions of the same header in one translation unit, they can't detect whether the same definition is included in differenttranslation unit.
因此,即使包含守卫可以帮助您防止一个翻译单元中相同标题的递归互包含和冗余包含,它们也无法检测相同的定义是否包含在不同的翻译单元中。
Yet, when merging the object code generated from the compilation of all the .cpp
files of your project, the linker willsee that the same symbol is defined more than once, and since this violates the One Definition Rule. Per Paragraph 3.2/3 of the C++11 Standard:
然而,当合并从.cpp
您的项目的所有文件的编译生成的目标代码时,链接器将看到同一个符号被定义了不止一次,因为这违反了一个定义规则。根据 C++11 标准的第 3.2/3 段:
Every program shall contain exactly one definition of every non-inlinefunction or variable that is odr-used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is odr-used.
每个程序都应包含该程序中 odr 使用的每个非内联函数或变量的一个定义;无需诊断。定义可以显式地出现在程序中,可以在标准或用户定义的库中找到,或者(在适当的时候)它是隐式定义的(见 12.1、12.4 和 12.8)。内联函数应在每个使用 odr 的翻译单元中定义。
Hence, the linker will emit an error and refuse to generate the executable of your program.
因此,链接器将发出错误并拒绝生成程序的可执行文件。
What do I need to do to solve my problem?
我需要做什么来解决我的问题?
Ifyou want to keep your function definition in a header file that is #include
d by multipletranslation units (notice, that no problem will arise if your header is #include
d just by onetranslation unit), you need to use the inline
keyword.
如果您想将函数定义保存在#include
由多个翻译单元d的头文件中(请注意,如果您的头#include
仅由一个翻译单元d,则不会出现问题),您需要使用inline
关键字。
Otherwise, you need to keep only the declarationof your function in header.h
, putting its definition (body) into oneseparate .cpp
file only (this is the classical approach).
否则,您只需要在 中保留函数的声明header.h
,将其定义(主体)放入一个单独的.cpp
文件中(这是经典方法)。
The inline
keyword represents a non-binding request to the compiler to inline the function's body directly at the call site, rather than setting up a stack frame for a regular function call. Although the compiler doesn't have to fulfill your request, the inline
keyword does succeed in telling the linker to tolerate multiple symbol definitions. According to Paragraph 3.2/5 of the C++11 Standard:
该inline
关键字表示对编译器的非绑定请求,以直接在调用站点内联函数体,而不是为常规函数调用设置堆栈帧。尽管编译器不必满足您的要求,但inline
关键字确实成功地告诉链接器容忍多个符号定义。根据 C++11 标准的第 3.2/5 段:
There can be more than one definition of aclass type (Clause 9), enumeration type (7.2), inline function with external linkage(7.1.2), class template (Clause 14), non-static function template (14.5.6), static data member of a class template (14.5.1.3), member function of a class template (14.5.1.1), or template specialization for which some template parameters are not specified (14.7, 14.5.5) in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements [...]
可以有多于一个的一个定义的类型(第9节),枚举类型(7.2),与外部连接的内联函数(7.1.2),类模板(第14),非静态函数模板(14.5.6) 、类模板的静态数据成员 (14.5.1.3)、类模板的成员函数 (14.5.1.1) 或在程序中未指定某些模板参数的模板特化 (14.7, 14.5.5),前提是每个定义出现在不同的翻译单元中,并且只要定义满足以下要求 [...]
The above Paragraph basically lists all the definitions which are commonly put in header files, because they can be safely included in multiple translation units. All other definitions with external linkage, instead, belong in source files.
上面的段落基本上列出了所有通常放在头文件中的定义,因为它们可以安全地包含在多个翻译单元中。相反,所有具有外部链接的其他定义都属于源文件。
Using the static
keyword instead of the inline
keyword also results in suppressing linker errors by giving your function internal linkage, thus making each translation unit hold a private copyof that function (and of its local static variables). However, this eventually results in a larger executable, and the use of inline
should be preferred in general.
使用static
关键字而不是inline
关键字还可以通过为函数提供内部链接来抑制链接器错误,从而使每个翻译单元都拥有该函数(及其局部静态变量)的私有副本。但是,这最终会产生更大的可执行文件,inline
通常应该首选使用。
An alternative way of achieving the same result as with the static
keyword is to put function f()
in an unnamed namespace. Per Paragraph 3.5/4 of the C++11 Standard:
获得与static
关键字相同结果的另一种方法是将函数f()
放在未命名的命名空间中。根据 C++11 标准的第 3.5/4 段:
An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage. All other namespaces have external linkage. A name having namespace scope that has not been given internal linkage above has the same linkage as the enclosing namespace if it is the name of:
— a variable; or
— a function; or
— a named class (Clause 9), or an unnamed class defined in a typedef declaration in which the class has the typedef name for linkage purposes (7.1.3); or
— a named enumeration (7.2), or an unnamed enumeration defined in a typedef declaration in which the enumeration has the typedef name for linkage purposes (7.1.3); or
— an enumerator belonging to an enumeration with linkage; or
— a template.
未命名命名空间或在未命名命名空间内直接或间接声明的命名空间具有内部链接。所有其他命名空间都有外部链接。如果名称是以下名称,则具有名称空间范围且未在上面给出内部链接的名称与封闭名称空间具有相同的链接:
- 一个变量; 或者
——一个函数;或者
— 命名类(第 9 条),或在 typedef 声明中定义的未命名类,其中该类具有用于链接目的的 typedef 名称(7.1.3);或者
— 命名枚举 (7.2),或在 typedef 声明中定义的未命名枚举,其中枚举具有用于链接目的的 typedef 名称 (7.1.3);或者
— 属于具有链接的枚举的枚举器;或者
——一个模板。
For the same reason mentioned above, the inline
keyword should be preferred.
出于与上述相同的原因,inline
应首选关键字。
回答by VonC
fiorentinoing's answer is echoed in Git 2.24 (Q4 2019), where a similar code cleanup is taking place in the Git codebase.
fiorentinoing的回答在 Git 2.24(2019 年第 4 季度)中得到了回应,其中在 Git 代码库中进行了类似的代码清理。
See commit 2fe4439(03 Oct 2019) by René Scharfe (rscharfe
).
(Merged by Junio C Hamano -- gitster
--in commit a4c5d9f, 11 Oct 2019)
请参阅René Scharfe ( ) 的commit 2fe4439(2019 年 10 月 3 日)。(由Junio C Hamano合并-- --在提交 a4c5d9f 中,2019 年 10 月 11 日)rscharfe
gitster
treewide: remove duplicate
#include
directivesFound with:
git grep '^#include ' '*.c' | sort | uniq -d
全树:删除重复的
#include
指令发现:
git grep '^#include ' '*.c' | sort | uniq -d
回答by fiorentinoing
First of all you should be 100% sure that you have no duplicates in "include guards".
首先,您应该 100% 确定“包含守卫”中没有重复项。
With this command
用这个命令
grep -rh "#ifndef" * 2>&1 | uniq -c | sort -rn | awk '{print " " }' | grep -v "^1\ "
you will 1) highlight all include guards, get unique row with counter per include name, sort the results, print only counter and include name and remove the ones that are really unique.
您将 1) 突出显示所有包含守卫,获取每个包含名称的带有计数器的唯一行,对结果进行排序,仅打印计数器和包含名称并删除真正唯一的那些。
HINT:this is equivalent to get the list of duplicated include names
提示:这相当于获取重复包含名称的列表