3

当我有以下代码A.cpp 并且 B.cpp没有生成警告或错误,但是Initializer::Initializer()inB.cpp被调用两次而 inA.cpp没有被调用时。

static int x = 0;

struct Initializer
{
    Initializer()
    {
        x = 10;
    }
};

static Initializer init;

由于这违反了一个定义规则并导致未定义的行为,我认为这是完全正常的。但是,当我将构造函数定义移到一个或两个文件中的类声明之外时,如下所示:

static int x = 0;

struct Initializer
{
    Initializer();
};

Initializer::Initializer()
{
    x = 10;
}

static Initializer init;

链接器突然变得足够聪明,可以出错并说one or more multiply defined symbols found. 这里发生了什么变化,为什么这很重要?我原以为链接器应该始终能够检测到 ODR 损坏 - 什么情况下它不能检测到?

如果我错了,有人纠正我,但我的理论是,当你有模板化代码(定义总是在标题中)时,你最终会在许多编译单元中得到重复的定义。它们碰巧都是相同的,因此链接器是否只选择一个并孤立其他无关紧要,但不会出错,有多个定义或模板不起作用。

4

3 回答 3

2

您的第二个示例具有明显且易于诊断的违反单一定义规则的情况。它对具有外部链接的非内联函数有两个定义。这对于链接器来说很容易诊断,因为从它所链接的目标文件中包含的函数的名称中可以明显看出违规。

您的第一个示例以更微妙的方式打破了单一定义规则。因为在类体中定义的函数是隐式声明inline的,所以您必须检查函数体以确定是否违反了一个定义规则。

仅出于这个原因,我对您的实施未能发现违规行为并不感到惊讶(我不是第一次这样做)。显然,编译器在孤立地查看一个源文件时不可能发现违规,但可能是检测违规和链接时间的信息实际上并不存在于传递给链接器的目标文件中。这肯定超出了我期望链接器找到的范围。

于 2012-06-19T06:02:00.607 回答
2

当多个翻译单元对于具有外部链接的项目具有相同的名称签名时,会发生多重定义符号错误,除非它们是内联函数或方法。

内联函数通常在编译时不受 ODR 影响。如果是这样,方法的内联实现将到处中断。但是,ODR 是在链接期间追溯应用的,因为选择了一个内联函数。因此,具有相同签名的内联函数应该具有相同的行为。您的内联构造函数违反了该期望。

相反,如果您在头文件中声明了一个模板,如下所示:

#ifndef I_HH
#define I_HH
tempalte <typename T>
struct Initializer {
    Initializer () { x = 10; }
};
#endif

并使用它来包含在A.cppandB.cpp中,并且在每个中都创建了一个静态实例:

static int x;
#include "i.hh"
static Initializer<int> init;

我相信编译器应该抱怨一个格式不正确的模板(g++ 无论如何都会这样做),这与检测违反 ODR 一样好(构造函数在不同的上下文中会表现不同)。

于 2012-06-19T07:00:36.380 回答
0

如果您在类定义中有函数的实现,则该函数是内联的,不需要链接。所以没有错误。必须有一个定义,但没关系它包含在不同的 cpp 文件中。例如,您在头文件中定义了一个类,但将头文件包含在不同的 cpp 文件中。

于 2012-06-19T06:06:07.063 回答