1

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

但请考虑以下情况:

假设我有文件main.cpp,stuff.cppcommonheader.h,.h文件有它的标题保护。

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

main.o只包含一次 commonheader.h 的内容

stuff.o只包含一次 commonheader.h 的内容

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

那么在链接步骤中会发生什么?由于 .o 文件被融合到一个可执行文件中,我们必须再次确保commonheader的内容不会重复。编译器会处理这个吗?如果不是,那么当我们处理巨大的头文件时,这不是一个问题,会导致文件之间的代码重复并导致较大的可执行文件大小。

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

4

4 回答 4

3

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

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

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

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

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

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

于 2012-09-01T20:32:49.490 回答
1

扩展格雷森的答案以涵盖变量。如果你想在头文件中声明一个变量,你应该使用 extern 关键字。这是处理全局变量的一种方法。

在头文件 global.h 中,您可以这样写:

extern Globals globals;

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

#include "globalstype.h"
Globals globals;

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

于 2012-09-01T20:49:34.927 回答
0

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

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

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

所以虽然:

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

在头文件中是 fin,

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

不是。

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

于 2012-09-01T20:50:54.677 回答
0

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

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

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

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

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

当然,该语言仍然允许使用错误的形式,并且如果您一遍又一遍地反复陈述并在多个翻译中包含必须为每个翻译复制的定义,也不会抱怨。然后你肯定会以很多膨胀结束。

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

于 2012-09-01T21:18:12.293 回答