为什么包括守卫不防止多个函数的定义?
链接器报告重复的符号:
#ifndef testttt #define testttt void anything(){ std::cout<<"hellooooooo"; } #endif
因为它在内部守卫里面,所以我期望这个function只定义一次。 但显然不是。
我知道我可以把它放在它的前面static
,然后它会工作(我仍然觉得很讽刺,因为静态应该是内部联系,但function可以从多个cpp文件中使用)。
所以我想我的两部分的问题是:1)为什么包含警卫不会阻止这个函数的多重定义,就像他们对其他头文件所做的那样; 2)为什么当静态应该阻止名称其他翻译单位的知名度? 我添加它,我实际上可以从包含这个头文件的任何地方调用这个函数。
“1)为什么包含守护程序不会阻止这个函数的多重定义,就像他们为其他头文件所做的那样”
因为每个翻译单元(即.cpp文件)被分开处理,并且经历相同的条件。 翻译单元不会共享其他翻译单元遇到的预处理器定义。 这意味着所有将处理该头文件的翻译单元将包含该函数的定义。 当然,链接器会抱怨同一个函数有多个定义。
“2)为什么当静态应该防止其他翻译单元中的名称被可见时,静态词解决这个问题?
因为static
关键字为每个翻译单元创build了该函数的私人副本。
如果你想在共享头文件中定义这个函数,你应该把它标记为inline
,这样可以解决你的问题,并且不需要预处理器的保护。
为什么包含守护程序不能阻止这个函数的多重定义,就像他们为其他头文件所做的那样?
从C ++程序创build可执行文件的过程包括三个阶段:
- 预处理
- 汇编&
- 链接
预处理 :在这个阶段,像macros一样的预处理指令被replace。
编译通过检查语言语义将源代码转换为目标代码。
链接是将所有生成的目标代码链接在一起形成一个可执行文件。
在预处理过程中,标题保护可防止标题的内容多次包含在同一个 翻译单元中。 他们不妨碍内容被包含在不同的翻译单位。 当您将这个头文件包含在不同的翻译单元中时,这些单元中的每一个都将具有此function的定义。
编译器分别编译每个翻译单元以生成单独的目标文件( .o ),这些.o文件中的每一个都将具有此函数定义的副本。 当链接器在生成.exe
时试图链接到函数定义时,它会find相同函数的多个定义,从而导致混淆链接到哪个函数。 为了避免这个问题,标准定义了一个被称为一个定义规则(ODR)的规则 ,该规则禁止同一实体的多个定义。
正如您所看到的,在头文件中包含函数定义并在多个翻译单元中包含该头文件违反了ODR。
通常的做法是在头文件中提供声明 ,并在唯一的源文件中提供定义 。
为什么当静态应该阻止名称在其他翻译单元中的可见性时,静态词解决这个问题?
当您将关键字static
添加到函数中时,每个翻译单元都将拥有自己的函数副本。 默认情况下,function具有外部连接,但static
强制function具有内部连接 。 因此这个定义对于不同的翻译单位是不可见的。 这样一个函数的每个实例被视为一个单独的函数( 每个函数的地址是不同的 ),这些函数的每个实例都有自己的静态局部variables和string文本的副本。 请注意,这大大增加了可执行文件的大小。
如果要将函数定义包含在头文件中。 有3种方法可以做到这一点:
- 将该函数标记为
inline
或 - 将该函数标记为
static
或 - 把函数放在一个未命名的名字空间中。
请注意#1
和#2
与上面第二个答案中提到的相同。
标准#3
放宽了内联函数的ODR,并允许每个翻译单元有自己的定义( 只要所有定义相同 )。
所以如果你真的想在头#1
放置一个函数定义是正确的方法。
1)为什么包含守护程序不像其他标题项那样阻止这个函数的多重定义,
包括警卫防止在同一翻译单元中包含多个标题。 然而,它并没有阻止多重定义:如果标题被包含在多个翻译单元中,则会有多个定义错误,因为该function是在每个翻译单元中定义的,并且由于它具有外部连接,所以所有翻译单元都可以请参阅所有其他翻译单元的定义。 为了防止这个错误,你只需要在头文件中提供声明 ,并在一个.cpp
文件中提供定义 。
了解一个定义规则(ODR)和外部链接 。
2)为什么当静态应该防止其他翻译单元中的名称被可视化时,为什么静态词解决这个问题?
因为static
使每个翻译单元的内部function成为内部连接的意思,所以其他翻译单元看不到定义。