C++ 链接器何时可以在多个定义的符号上出错?

C++ 链接器何时可以在多个定义的符号上出错?,c++,visual-studio-2010,C++,Visual Studio 2010,当我在A.cpp和B.cpp中有以下代码时,不会生成警告或错误,但是B.cpp中的Initializer::Initializer()会被调用两次,而A.cpp中的代码不会被调用 static int x = 0; struct Initializer { Initializer() { x = 10; } }; static Initializer init; 因为这违反了单定义规则,导致了未定义的行为,我认为这是完全正常的。但是,当我将构造函数定

当我在
A.cpp
B.cpp
中有以下代码时,不会生成警告或错误,但是
B.cpp
中的
Initializer::Initializer()
会被调用两次,而
A.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;
#ifndef I_HH
#define I_HH
tempalte <typename T>
struct Initializer {
    Initializer () { x = 10; }
};
#endif
链接器突然变得足够聪明,可以出错并说出
找到的一个或多个多重定义符号
。这里发生了什么变化?为什么这很重要?我本以为链接器应该始终能够检测ODR破损——当它不能检测时,会发生什么情况


如果我错了,有人会纠正我,但我的理论是,当您对代码进行模板化(定义总是在标题中)时,许多编译单元中都会出现重复的定义。它们碰巧都是相同的,因此,如果链接器只选择一个并孤立其他链接器,这并不重要,但不能错误地认为有多个定义或模板不起作用。

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

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


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

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

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

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

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

static int x = 0;

struct Initializer
{
    Initializer();
};

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

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

我认为编译器应该抱怨一个格式不正确的模板(g++还是这样做了),这就像检测到违反ODR一样好(构造函数在不同的上下文中会有不同的行为)。

如果你把
内联
放在外部定义的构造函数前面,你还会得到链接错误吗?@user315052 No,事实上有趣。我希望链接器没有选择的多个定义被剥离;链接后可以做这样的事情吗?我想说的是:模板会导致膨胀吗?我相信选择一个模板的全部目的是移除其他模板,如果它们可以被移除的话(如果它们做了一些毛茸茸的事情,比如包含函数局部静态变量,它们可能不会)。不过,我不确定动态链接器如何处理具有相同模板实例化的多个库。