【问题标题】:Why include guards do not prevent multiple function definitions? [duplicate]为什么包含守卫不阻止多个函数定义? [复制]
【发布时间】:2023-03-28 13:16:01
【问题描述】:

链接器在此报告重复符号:

#ifndef testttt
#define testttt

void anything(){
    std::cout<<"hellooooooo";
}

#endif

因为它在包含保护中,所以我希望这个函数只定义一次。但显然不是。

我知道我可以把 static 放在它前面,然后它就可以工作了(我仍然觉得很讽刺,因为 static 应该给它内部链接,但这个函数可以从多个 cpp 文件中使用) .

所以我想我的两部分问题是:1)为什么包含防护不会像对其他标题项那样阻止此函数的多个定义,以及 2)为什么 static 字在静态时解决这个问题应该防止名称在其他翻译单元中可见吗?我添加了它,我实际上可以从包含这个头文件的任何地方调用这个函数。

【问题讨论】:

  • fwiw,作为重复链接的问题实际上是在这个问题之后提出的,所以说它“已经”有一个,不太正确。

标签: c++


【解决方案1】:

为什么包含守卫不像其他标题项那样阻止此函数的多个定义?

从 C++ 程序创建可执行文件的过程包括三个阶段:

  1. 预处理
  2. 编译&
  3. 链接

预处理:在此阶段替换宏等预处理器指令。
编译是通过检查语言语义将源代码转换为目标代码。
链接是将所有生成的目标代码链接在一起以形成可执行文件。

标题保护防止标题的内容被多次包含在相同 translation unit在预处理过程中。它们不会阻止内容包含在不同的翻译单元中。当你在不同的翻译单元中包含这个头文件时,每个翻译单元都会有这个函数的定义。
编译器分别编译每个翻译单元以生成一个单独的目标文件(.o),每个.o 文件都将具有此函数定义的副本。当链接器在生成.exe 时尝试链接到函数定义时,它会发现相同函数的多个定义,从而导致不知道要链接到哪一个。为了避免这个问题,该标准定义了一个称为 One defintion rule(ODR) 的规则,该规则禁止同一实体的多个定义。
如您所见,在头文件中包含函数定义并将该头文件包含在多个翻译单元中违反了 ODR。
通常的做法是在头文件中提供 声明 和一个且唯一的 定义一个源文件。

当静态应该防止名称在其他翻译单元中可见时,为什么静态词会解决这个问题?

当您将关键字 static 添加到函数时,每个翻译单元将拥有自己的函数副本。默认情况下,函数具有external linkage,但static 强制函数具有内部链接。因此,该定义对不同的翻译单元不可见。这种函数的每个实例都被视为一个单独的函数(每个函数的地址不同),并且这些函数的每个实例都有自己的静态局部变量和字符串文字的副本。请注意,这会大大增加可执行文件的大小。


如果你想在头文件中包含函数定义。有3种方法可以做到:

  1. 将函数标记为inline
  2. 将函数标记为static
  3. 将函数放在未命名的命名空间中。

请注意,#1#2 与上面第二个答案中提到的相同。
对于#3,标准放宽了内联函数的 ODR,并允许每个翻译单元有自己的定义(只要所有定义都相同)。

所以如果你真的想在标题#1 中放置一个函数定义是正确的做法。

【讨论】:

  • 谢谢,有道理。但是为什么包含守卫不允许这是一个没有多个声明的“全局函数”呢?
  • @SebbyJohanns:更新为 detail.Hth。
  • 未命名的命名空间解决方法效果很好!我将它与内联声明的枚举一起使用。
【解决方案2】:

1) 为什么包含守卫不像其他标题项那样阻止此函数的多个定义,

包含保护可防止在相同翻译单元中多次包含标题。它确实,但是,防止多重定义:如果头部包含在多个翻译单元中,那么就会出现多重定义错误,因为函数是在每个翻译单元中定义的,并且由于它有外部联动,所有翻译单元都可以看到所有其他翻译单元的定义。为防止此错误,您只需在标头中提供声明,并在一个.cpp 文件中提供定义

了解One Definition Rule (ODR)External Linkage

2) 当静态应该防止名称在其他翻译单元中可见时,为什么静态词会解决这个问题?

因为static使函数internal到每个翻译单元。这就是internal链接的意思:其他翻译单元看不到定义。

【讨论】:

    【解决方案3】:

    "1) 为什么包含防护不能像对其他标题项那样阻止此函数的多个定义"

    因为每个翻译单元(即 .cpp 文件)都是单独处理的,并且经过相同的条件。翻译单元不会共享其他翻译单元遇到的预处理器定义。这意味着 所有 将处理该标题的翻译单元将包含该函数的定义。当然,链接器会抱怨同一个函数有多个定义。

    "2) 当静态应该防止名称在其他翻译单元中可见时,为什么静态词会解决这个问题?"

    因为static 关键字为每个翻译单元制作了该函数的私有副本。

    但是,如果您希望在共享标头中定义该函数,则应将其标记为 inline,这将解决您的问题并使预处理器保护变得不必要。

    【讨论】:

    • +1,除了它不会“使预处理器防护变得不必要”。守卫将我们从同一个翻译单元中的多个定义中拯救出来。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-27
    • 2022-01-22
    • 2012-01-19
    • 1970-01-01
    • 2015-03-04
    相关资源
    最近更新 更多