如何创建一个复杂的联盟?

时间:2019-07-16 01:58:24

标签: c++ visual-c++

所以我在使用编译单元时偶然发现了这一点。

我有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?在标准中是否将此作为未定义的行为提及?

(我也知道给两个同名的类命名是愚蠢的...)

2 个答案:

答案 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可能影响了此对象。链接器不需要知道什么是编译设置,甚至不需要什么源。该代码甚至可以用另一种语言编写。

要获得这种灵活性和互操作性,您必须遵循一些规则。其中之一是“不要以不同的方式定义相同的类两次”。