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

时间:2012-09-01 20:29:33

标签: c++ c header

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

但请考虑以下情况:

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

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

main.o包含commonheader.h 的内容。

stuff.o包含commonheader.h 的内容。

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

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

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

4 个答案:

答案 0 :(得分:2)

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

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

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

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

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

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

答案 1 :(得分:1)

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

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

extern Globals globals;

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

#include "globalstype.h"
Globals globals;

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

答案 2 :(得分:0)

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

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

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

所以同时:

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

是头文件中的文件,

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

不是。

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

答案 3 :(得分:0)

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

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

然而,

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

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

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

当然,该语言仍然允许不良形式,并且如果您反复重复说明并且包含多个翻译定义,并且每个翻译必须必须被复制,则不会抱怨。那么你肯定会有很多膨胀。

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