了解导致此多重定义错误的原因

Understanding what causes this multiple definition error

本文关键字:错误 定义 了解      更新时间:2023-10-16

我有一个基类,它有一个由两个类实现的纯虚拟方法:

// base_class.hpp
class base_class {
public:
virtual std::string hello() = 0;
};
// base_implementer_1.hpp
class base_implementer1 : base_class {
public:
std::string hello();
};
// base_implementer_2.hpp
class base_implementer2 : base_class {
public:
std::string hello();
};
// base_implementer_1.cpp
std::string hello() {
return(std::string("Hello!"));
}
// base_implementer_2.cpp
std::string hello() {
return(std::string("Hola!"));
}

请注意,在实现中缺少base_implementer1::base_implementer2::。这是经过深思熟虑的。

通过添加base_implementer1::base_implementer2::,我不会得到多重定义错误。然而,如果不考虑它们,链接器会抱怨我有两个相同函数的定义(hello())。

由于头文件中没有这两个实现,我认为(尽管它们在实际实现hello()方面不正确)它们是允许的,因为没有理由不能在两个不同的.cpp文件中拥有两个hello()函数。但事实似乎并非如此。有人能告诉我链接器中发生了什么导致了这个多重定义错误吗?

One Definition Rule定义了两个作用域的规则,即翻译单元作用域和程序作用域。

以下具有翻译单元范围的规则规定,同一翻译单元不得包含同一功能的两个不同定义:

任何变量、函数、类类型、枚举只有一个定义在任何一个翻译单元中都允许使用类型或模板(这些可能有多个声明,但只有一个定义允许)。

因此,如果您有两个不同的.cpp文件,则您有两种不同的翻译单元,并且它们中的每一个都可能有自己的hello()定义;在翻译单位的范围内不违反ODR。

以下具有程序范围的规则定义了odr使用的函数必须在程序中定义一次:

每个非内联函数或变量的唯一定义使用的odr(见下文)需要出现在整个程序(包括任何标准库和用户定义库)。这个编译器不需要诊断此冲突,但需要诊断行为违反它的程序的是未定义的。

非正式使用的odr的定义指出,对于每个被调用或地址被占用的函数,都必须在程序中定义:

在非正式情况下,如果一个对象的地址被占用,或者引用绑定到它,如果函数对其进行调用或获取其地址。如果对象或函数是odr使用的,其定义必须存在于程序违反这一点就是链接时间错误。

因此,如果多个.cpp文件公开了hello()的实现,并且如果调用或引用了此函数,则显然违反了程序范围内的ODR。

如果未使用(即调用或引用)相应的函数,则在我看来,不应违反odr;

如果编译器抱怨符号重复,那是因为程序违反了链接规则(请同时给出关于"如果我不使用变量"的SO答案)。C++11§3.5[基本链接]/9状态:

两个相同的名称在不同的作用域中声明应表示相同的变量、函数、类型、枚举器、模板或命名空间(如果)

  • 两个名称都具有外部链接,或者两个名称具有内部链接并在同一翻译单元中声明;而且

要避免这种情况,请确保最多公开一个hello()的实现,并将所有其他实现设为static或使用未命名的命名空间。

在C编程语言中,static与全局变量和函数一起使用,将它们的作用域设置为包含文件,即它不公开此实现,并且避免了与其他二进制文件的名称冲突。

因此,一个合理的建议是:使仅在翻译单元中使用的函数定义仅对该翻译单元可见;以及定义在命名空间或类内公开的函数,以避免链接器中意外或不可预见的名称冲突/重复符号问题。

一个名为hello的函数在两个不同的翻译单元中有两种不同的定义。当涉及到链接时间时,链接器不知道要链接到哪个hello函数。

考虑:

A.cpp

#include <string>
std::string hello() {
return "A";
}

B.cpp

#include <string>
std::string hello() {
return "B";
}

C.cpp

#include <iostream>
std::string hello();
int main() {
std::cout << hello() << 'n';
}

链接器怎么可能知道在main中调用哪个hello?它不能,因为违反了一个定义规则。

在base_implementor_1.cpp中定义了一个名为hello的全局函数。在base_iamplementor_2.cpp中又定义了另一个称为hello的全局函数,这会导致多重定义和违反ODR所需的错误。为什么这是个问题?如果您有一个调用hello()的第三个源文件,那么应该调用哪个函数?

如果您想在多个源文件中定义具有相同名称的不同函数,可以在它们前面加上static关键字

static void hello() { }

或在匿名名称空间内

namespace {
void hello() { }
}