澄清C / C ++中使用的标题保护和头文件包含

我知道人们建议在头文件中包含标题保护,以防止头文件内容被预处理器多次插入到源代码文件中。

但请考虑以下情形:

假设我有文件main.cppstuff.cppcommonheader.h ,其中.h文件有标题保护。

如果.cpp文件尝试多次包含commonheader.h ,那么预处理器将阻止它发生,并且在编译到目标代码后,我们得到,

main.o包含main.o的内容一次。

stuff.o包含stuff.o的内容一次。

请注意,commonheader的内容已在文件中重复,但不在同一.o文件中。

那么在链接步骤中会发生什么? 由于.o文件被融合到一个exectuable中,我们将不得不第二次确保commonheader的内容不被重复。 编译器会处理这个吗? 如果没有,当我们处理大量头文件时,这不会成为问题,导致跨文件的代码重复并导致大的可执行文件大小。

如果我在问题的任何地方犯了一些概念错误,请纠正我。

通常,您的头文件实际上不应该定义任何符号,它应该只声明它们。 所以commonheader.h看起来像这样(省略包含警卫):

 void commonFunc1(void); void commonFunc2(void); 

在那种情况下,没有问题。 如果你在main.cppstuff.cpp调用commonFunc1main.ostuff.o都知道他们想要链接一个名为commonFunc1的符号,链接器将尝试找到该符号。 如果链接器未找到该符号,则会出现未定义的引用错误。 commonFunc1的实际定义需要在某个cpp文件中。

如果您确实要在头文件中定义函数,请使用static以便链接器不会看到它们。 所以你的commonheader.h看起来像:

 static void commonFunc1() { /* ... do stuff ... */ } 

在这种情况下,链接器不知道commonFunc1并且不会发生错误。 这可能会增加可执行文件的大小; 你最终可能会得到commonFunc1两个代码commonFunc1

扩展Grayson的答案以涵盖变量。 如果要在头文件中声明变量,则应使用extern关键字。 这是处理全局变量的一种方法。

在头文件global.h中你写这个:

 extern Globals globals; 

那么你可以在任何文件中使用foo,包括global.h,而在global.cpp中你可以写

 #include "globalstype.h" Globals globals; 

请注意,global.cpp不需要包含global.h,但是您需要确保将global.cpp编译到每个用法中,否则链接器会抱怨。

头文件通常包含声明性代码而不是确定代码 。 那就是他们宣称存在必须存在一次的东西。 允许使用宏和内联函数,并且无论在何处使用它们都必须重复。

编译器使用声明将未解析的链接(或引用)插入到目标代码中。 链接器的工作是通过将引用与单个定义匹配来解析这些链接。

如果省略包含保护,在单个转换单元中包含多个包含,则会因多次声明现有符号而出现编译器错误 。 但是,如果您有一个错误地包含定义的标头,并且标头包含在多个转换单元中,则会有多个具有定义的目标文件 – 这会导致多个定义链接器错误

所以同时:

 extern int b ; // declaration, may occur in multiple translation units 

是头文件中的fin,

 int b ; // definition, must occur in only object file. 

不是。

并非声明未包含在目标代码中,而是编译器使用它们来创建链接器将解析的引用(如果编译器尚未使用该定义并已解决它)。

是的,这可能是个问题。 您最终可能会有多个定义或冗余副本。

在这方面,C非常简单。 你有静态,外部和内联 – 编译器也定义了几种改变可见性的方法。 我认为其他答案已经涵盖了很多这方面的内容。

但是,C ++完全不同。 有很多信息,也有隐含的定义(例如编译器可能会发出复制构造函数或RTTI)。

使用C ++,定义出现在头文件中的可能性更大 – 考虑模板,类声明中定义的方法等等。 C ++默认使用One Definition Rule。 您将需要更详细地阅读它,但它基本上表明某些类别的符号可能是多重定义的; 根据装饰和声明的位置/范围,在许多情况下,允许链接器假定每个主体(定义)是相同的,并且可以自由地丢弃它遇到的任何副本(在二进制文件中保留一个定义)。 所以这确实减少了生成的二进制文件的大小 ,除非你指定一个副本应该生成。

但是,在头文件中包含这些定义肯定会增加编译每个文件所需的编译时间,内存和文件,可见的依赖关系,并且会增加编辑定义时必须重新编译的文件数。

当然,该语言仍然允许不良forms,如果您反复重复陈述并且包含多个翻译定义,则必须为每个翻译复制。 然后你肯定会有很多膨胀。

这可能是一个很好的介绍: http : //www.informit.com/guides/content.aspx? g = cplusplus&seqNum = 386