所以我在使用编译单元时偶然发现了这一点。
我有2个标头,它们定义了一个具有相同名称的类。第一个编译单元包括第一个标头并声明指向该类的外部指针,第二个编译单元包括第二个标头并定义指针。
现在我有T *指向U。
mcve:
h1.h
#pragma once
struct a_struct {
int i;
a_struct(int _i) : i{ _i } {}
};
h2.h
#pragma once
struct a_struct {
float f;
a_struct(float _f) : f{ _f } {}
};
foo.h
#pragma once
struct foo {
int bar();
};
cu1.cpp
#include "foo.h"
#include "h1.h"
extern a_struct* s;
int foo::bar() {
return s->i;
}
cu2.cpp
#include "h2.h"
a_struct* s = new a_struct(1.0f);
main.cpp
#include "foo.h"
#include <iostream>
int main() {
foo f;
std::cout << f.bar() << std::endl; // <- 1065353216
system("PAUSE");
return 0;
}
为什么链接器看不到h1.h :: a_struct不是h2.h :: a_struct?在标准中是否将此作为未定义的行为提及?
(我也知道给两个同名的类命名是愚蠢的...)
答案 0 :(得分:2)
标准中是否将此行为定义为未定义行为?
是的,这违反了“一个定义规则”的“标题版本”。在此版本中,适用于类定义,inline
函数和变量以及头文件中通常定义的其他此类内容,单个实体中的多个定义可以在单独的转换单元中使用,但这些定义必须都具有相同的定义标记(在预处理之后),并且都必须实质上表示同一件事。以这种方式不同的多个定义是未定义的行为。参见C ++ 20草案中的[basic.def.odr]/12,以及One Definition Rule at cppreference.com下的第五段。
为什么链接程序看不到h1.h
::a_struct
不是h2.h::a_struct
?
在大多数C ++实现中,编译器将转换单元转换为包含功能代码和符号定义的目标文件,并且功能代码可以利用其他对象定义的其他“未定义符号”。到目标文件为止,关于C ++源代码或类型信息的信息很少,除了可能在调试器数据中。链接器可能只会看到cu1.o中的函数foo::bar()
使用未定义的符号s
,cu2.o定义了符号s
,以及cu2的全局动态初始化函数。 o还使用符号s
。链接器将进行调整,以使执行foo::bar()
可以正确访问同一对象s
,而不必关心任何函数实际上对属于该符号的字节的作用。
(当目标文件不同意与符号关联的字节数时,链接器有时会发出警告,但两个指向类类型的指针可能具有相同的大小。)
答案 1 :(得分:1)
编译器分别编译每个源文件。它相信给定的类声明对于所有源文件都是相同的。
当您执行上述操作时,您会欺骗编译器针对某个类编译具有两个不同定义的两个文件。每个文件都会生成一个前后一致的代码。
然后链接器出现,并链接您的各种代码。有一种对象/库格式,可在所有编译器之间共享。这是为了允许每个链接器与每个编译器一起使用。在这一点上,链接器只知道某些代码将传递 foo 对象,而另一些代码将接收 foo 对象。窥视和检查并抱怨不是自己的事。
请记住,在链接时,源代码甚至可能不可用。您可能有一些供应商提供的没有源代码的库。可能有各种#define可能影响了此对象。链接器不需要知道什么是编译设置,甚至不需要什么源。该代码甚至可以用另一种语言编写。
要获得这种灵活性和互操作性,您必须遵循一些规则。其中之一是“不要以不同的方式定义相同的类两次”。