在src1.cpp中,我定义了一个名为“Test”的类和一个名为“f1”的函数,该函数创建了Test的一个示例。
// src1.cpp
#include <iostream>
class Test {
int a = 1;
public:
Test() {
std::cout << "src1 Test\n";
}
};
void f1() {
Test t;
}
在src1.h中,“f1”被暴露。
// src1.h
#pragma once
void f1();
src2.cpp和src2.h的创建方式大致相同。具有相同名称的类和构造它的示例的函数。
// src2.cpp
#include <iostream>
class Test {
long a;
public:
Test() {
std::cout << "src2 Test\n";
}
};
void f2() {
Test t;
}
// src2.h
#pragma once
void f2();
然后在www.example.com中main.cc,我调用f1和f2。
// main.cpp
#include "src1.h"
#include "src2.h"
int main() {
f1();
f2();
return 0;
}
我用下面的命令编译**,没有警告和错误**。g++ -Wall -o main main.cpp src1.cpp src2.cpp
程序输出为:
src1 Test
src1 Test
编译器似乎允许Test类的不同定义,f1和f2都调用src1.cpp中Test的构造函数。
当我以相反的顺序编译时,比如g++ -Wall -o main main.cpp src1.cpp src2.cpp
,程序输出变为:
src2 Test
src2 Test
当我用一个重复的变量替换class Test
时,会发生编译错误。在这种情况下,链接器如何处理重复的定义?
2条答案
按热度按时间e0bqpujr1#
您的代码是不允许的,即使您没有得到任何警告和错误。
这段代码违反了单定义规则(ODR),因为
Test
的定义在不同的翻译单元(TU)中必须始终相同(即.cpp
文件)。然而,odr违反是**格式不良,不需要诊断(IFNDR)**的情况。这意味着您的代码不是有效的C++,但编译器不需要发出任何警告或错误。C实际上有大量这样的IFNDR情况。
C标准中的措辞是这样的:
对于具有多个翻译单元中的定义的任何可定义项
D
,D
是一个非内联非模板化函数或变量,或者程序是病态的;[...]
*每一个这样的定义应包含相同的令牌序列
这意味着
T
的所有定义必须完全相同,基本上是彼此的复制/粘贴版本。解决方案
很难确保您不会手动违反ODR。这就是为什么你通常把类型放在头文件中,这样就可以保证它们在所有包含它们的cpp文件中是相同的:
或者,如果您想重用
Test
名称,但给予不同的定义:这也解决了这个问题,因为两个
Test
类具有内部链接,这意味着a.cpp
和b.cpp
中的Test
引用不同的类型。因为它们是不同的类型,所以它们也可以被不同地定义。为什么链接器检测不到这个,却检测到重复的变量?
原因很简单:类型不是像函数和变量那样被发出的实体,它们只是存在于程序中。它们仍然必须在任何地方都被定义为相同的,但是单独定义
Test
根本不会产生任何程序集。即使它是id,也很难检测到odr违规,因为
Test
* 可以在多个地方定义。它只需要以同样的方式定义。链接器必须以某种方式测试两个类之间的相等性,但C标准对链接器的要求非常低。只有在使用C20模块时才能得到有保证的诊断。参见
w3nuxt5m2#
程序的行为未定义。
类的定义包括它们的内联构造函数应该由相同的标记组成,但是它们具有不同的标记和实体。
例如,在C++14标准中有3.2一个定义规则)
6一个类类型(第9章)、枚举类型(第7.2章)、具有外部链接的内联函数(第7.1.2章)、类模板(第14章)、非静态函数模板(第14.5.6章)、类模板的静态数据成员(第14.5.1.3章)、类模板的成员函数(第14.5.1.1章)或程序中某些模板参数未指定的模板专用化(第14.7章、第14.5.5章)的定义可以不止一个,只要每个定义出现在不同的翻译单元中,并且定义满足以下要求。给定在多于一个翻译单元中定义的名为D的这样的实体,则
(6.1)-D的每个定义都应包含相同的标记序列;和
等等
如果D是一个模板,并且在多个翻译单元中定义,那么前面的要求既适用于模板定义中使用的模板的封闭范围中的名称(14.6.3),也适用于示例化时的从属名称(14.6.2)。如果D的定义满足所有这些要求,那么行为就好像D只有一个定义。如果D的定义不满足这些要求,那么行为是未定义的。