为什么一个函数的多重定义是错误的?
Why is a multiple definition of a function an error?
示例:
//a.h
// no include guards
class A {};
如果我在一个翻译单元(一个cpp文件)中包含这个头两次,我会得到一个链接器错误,没关系。但是如果我把它包含在两个不同的翻译单元中也是可以的,对吧?
现在考虑全局函数:
// b.h
// no include guards
void foo() {}
不仅不允许在同一单元中包含它两次,而且也不允许在任何其他翻译单元中第二次包含它。为什么?
如果我在一个翻译单元(一个cpp文件)中包含这个头文件两次,我会得到一个链接错误
我相信你得到一个编译错误,而不是一个链接错误。我看不到应该生成的代码,所以链接器看不到它可以抱怨的东西。
空白foo {}
我相信你指的是void foo(){}
!
c++使用"一个定义规则"。这只是一个定义,从用户的角度来看,获得两次定义是没有意义的,可能具有不同的语义。这就是语言本身,也是在一个程序中摆脱多个不同定义的一种简单方法。
为什么发起者决定使用ODR在这里不能给出。也许Bjarne读到这里,可以给你一个更详细的答案:-)
声明类型在多个翻译单元中是允许的(否则头文件将不起作用),你也可以定义静态和内联函数,但是(简化)你不能多次使用"外部链接"定义任何东西。
如果选择了,您希望链接器选择哪个副本?
c++有ODR: One定义规则。基本上,它说你可以在多个地方复制相同的定义,但是它们必须匹配,否则将会出现未定义的行为。
(我上面写的不太简化的版本是,链接器有聪明的方法来统一不可避免地要多次生成的c++主义(模板、构造函数等):所谓的"COMDAT"节,但这些不适用于普通函数。)
如果你想要真正的技术,那么你可以探索"弱"链接。基本上,你可以说链接器应该使用这个定义,除非另一个强定义可用(即没有"弱"属性的定义)。当你有可选的特性,你想在它们可用的时候启用它们,但不是一般的兴趣,这是很有用的。
另一个有趣的问题是共享库;有时,出于性能或依赖的原因,库将有一个链接到它的函数的私有副本。这可能导致程序的不同部分使用相同函数的不同副本,可能具有不同的功能和bug等。当函数中有你想要共享的静态数据时,这尤其麻烦。
当然,对于共享库,您也有冲突函数名称的可能性,但这违反了ODR,并且是一个错误。
你混淆了声明和定义。在翻译单元中可以有许多相同的声明。所以如果foo
是一个函数,返回一个整型,接受一个整型和一个字符串,那么声明int foo(int i, std::string s);
可以重复很多次。在第一次使用之前,它应该存在于每个翻译单元(cpp文件)中。你也可以声明类。A类的前向声明是:class A;
,没有其他内容,可以在一个翻译单元中重复。
函数定义在整个程序中只允许出现一次。foo
的定义类似于:
int foo(int i, std::string s) {
return i + s.size();
}
类A的完整声明可以是:
class A {
int a;
std::string s;
public:
A(int a):s("") { this->a = a;} // this constructor is declared and defined inline
int bar(int a); // this method is only declared here, will be defined elsewhere
...
};
在每个翻译单元中只能出现一次。
方法bar
的定义(只能在类声明中声明)可以是:
int A::bar(int i) {
return i + a;
}
这个定义在整个程序中只能出现一次。
一旦说了,规则是:
- 包含只包含函数声明或前向类声明的文件,可以在每个翻译单元中包含多次(*) 包含完整类声明的文件在每个翻译单元(*)中最多包含一次
- 全局函数和方法的实现在cpp文件中,在整个程序中只出现一次。
(*)通常的用法是在每个翻译单元中使用include保护符只包含它们一次。
以上原因:首先,这是法律,我们都必须遵守。但是编译器很容易看到函数声明和前向类声明是相同的,所以很容易允许它们在一个翻译单元中多次出现。它们自己不生成代码,所以链接器不关心它们。
函数声明可以生成代码。所以它们必须在每个翻译单元中只出现一次。因为它们确实是声明,它们可以出现在任何翻译单元中,并且链接器将只保留生成代码的一个版本。定义实际上生成代码,它们没有理由在程序中重复。因此,如果同一个函数或方法在一个程序中定义了多次,链接器就会抛出错误。
这是编译和链接c++文件的自然结果。
当您将翻译单元编译成目标文件时,每个外部函数都由重定位项引用。当链接器将所有内容链接在一起时,它会尝试用实际函数的实际地址填充这些重定位存根。
现在,假设文件A.o定义了foo()
,也就是说,它提供了实际的代码。现在假设B.o需要一个函数foo()
。然后我们告诉链接器将A.o和B.o链接到一个可执行文件或库中。在这里,链接器想要在B.o中填补空白,即重定位。因此,当它看到B.o正在寻找foo()
,而A.o提供了它时,它将A.o中foo()
的最终地址放入B.o的调用代码中。
现在,假设我们告诉链接器将A.o, B.o 和 C.o链接在一起。假设C.o,由于某种原因,也提供了foo()
的定义,例如,因为它包含了一个定义它的头,A.o也包含了这个头。现在链接器想要填充B.o中的foo()
存根。它应该选择哪一个?第一个?第二个吗?它是如何选择的?如果你不小心,这可能会导致麻烦。
这就是为什么默认情况下,主链接器禁止对同一个函数定义多个。通常,您可以使用标志启用它。但通常情况下,它要么是一个即时错误的信号,要么是未来令人头痛的信号。
inline
将允许在多个翻译单元中定义相同的函数
inline
void foo() {}
但这取决于你是否在所有情况下提供相同的定义-如果它们不同,你将得到未定义的行为。
或者,不需要在头文件中定义这样的函数,您可以在头文件中用extern
:
来声明它。foo.hh:
extern void foo();
并在一个翻译单元中提供一个(非inline
)定义。cc文件)。
模板函数是隐式inline
,我相信。
- 使用命名空间时出现多个定义错误
- 对C宏的未定义引用,但在定义它时会出现重新定义错误
- 尝试调用 .h 文件中定义的变量时出现变量未定义错误
- 在头文件和 cpp 文件中使用一次 #pragma 时出现结构重定义错误
- 链接阶段出现多重定义错误
- 避免模板类中的重定义错误
- 即使我没有包含多个文件,C++中的多个定义错误
- 跨多个类的全局变量而不会出现重定义错误?
- 尝试运行 wasm 函数时出现模块未定义错误
- C++ 预期的左大括号以及重定义错误
- C++:成员的越界声明必须是纯虚函数的定义错误
- c++中数组的未定义错误
- Visual C:模板类中的自定义错误消息
- 为什么C++模板不会导致多个定义错误?
- 只有一个定义/声明时标头声明变量的多堆定义错误
- C++ 在多个其他类中使用单个类 - 编译时出现多个定义错误
- 基类未定义.错误 C2504
- Q 斯坦达项重定义错误
- 可视化C++中的结构定义错误
- VC++ C2011 重定义错误 - 未使用的头文件