一个定义规则 - 编译

One Definition Rule - compilation

本文关键字:规则 编译 定义 一个      更新时间:2023-10-16

我试图理解ODR。 我创建了一个文件 pr1.cpp如下所示:

struct S{
int a;
};

第二个文件 pr2.cpp像这样

struct S {
char  a;
};

和这样的主文件:

#include <iostream>
int main() { 
return 0;
}

我正在使用带有命令的终端进行编译:

g++ -Wall -Wextra pr1.cpp pr2.cpp main.cpp -o mypr

编译器没有发现任何类型的错误,但是有两个类型为"S"的声明...我不明白到底发生了什么。由于违反 ODR 规定,我想在"链接"阶段后出现错误。. 我只能编辑主文件.cpp文件时出现错误,添加:

#include "pr1.cpp"
#include "pr2.cpp"

谁能解释一下发生了什么?

除非在同一文件中包含这两个定义,否则没有问题。这是因为编译器在单个翻译单元上运行,该翻译单元通常是.cpp文件。您在此文件中#include的所有内容也是翻译单元的一部分,因为预处理器基本上复制并粘贴所有包含文件的内容。

发生的情况是,编译器将为每个翻译单元创建和目标文件(通常为 .obj(,然后链接器将通过链接所有目标文件和项目所依赖的库来创建单个可执行文件(或.dll等(。在您的情况下,编译器在不同的翻译单元中遇到了每个结构,因此它看不到问题。当您包含这两个文件时,这两个定义现在会发现自己位于同一个翻译单元中,并且编译器会引发错误,因为如果在此翻译单元中使用S,编译器将无法解决歧义(即使您不必使用一个翻译单元来使程序格式不正确(。

作为旁注,不要在其他.cpp文件中包含.cpp文件。我相信你可以找到很多关于如何在头文件和源文件中组织代码的信息,它没有直接回答这个问题,所以我不会展开它。

编辑:我忘了说为什么你没有收到链接器错误。一些评论指出,这是未定义的行为,这意味着即使您的链接器可能应该抱怨,它实际上也不必抱怨。在您的情况下,每个结构都有一个 .obj 文件和一个 main.obj。这些都没有引用另一个,因此链接器看不到任何需要解析的引用,并且可能不会费心检查不明确的符号。

我认为如果您声明struct S;并尝试使用S*S&,大多数链接器都会抛出错误(实际S需要在同一翻译单元内定义(。这是因为链接器需要解析该符号,并且它将找到两个匹配的定义。但是,鉴于这是未定义的,符合标准的链接器可以只选择一个并静默地将您的程序链接到无意义的内容中,因为您打算使用另一个。这对于从一个.cpp传递到另一个的结构尤其危险,因为定义需要保持一致。当同名结构/类通过库边界传递时,也可能是一个问题。出于这些原因,请始终避免重复名称。