尽管违反了One Definition Rule,编译器/链接器COULD如何选择备用内联构造函数?

时间:2012-11-29 12:15:23

标签: c++ compiler-construction linker

参考What determines which class definition is included for identically-named classes in two source files?,其中有故意明确违反One Definition Rule,我仍然感到困惑的是,编译器/链接器如何选择一个定义是可能的在另一个。

(ADDENDUM基于答案/评论:我正在寻找一个例子,说明编译器/链接器如何产生下面显示的结果,给定的代码是故意违反标准的因此代码导致未定义的行为。)

代码示例是:

// file1.cpp:

#include <iostream>
#include "file2.h"

struct A
{
    A() : a(1) {}
    int a;
};

int main()
{
    // foo() <-- uncomment this line to draw in file2.cpp's use of class A

    A a; // <-- Which version of class A is chosen by the linker?
    std::cout << a.a << std::endl; // <-- Is "1" or "2" output?
}

...

//file2.h:

void foo();

...

// file2.cpp:

#include <iostream>
#include "file2.h"

struct A
{
    A() : a(2) {}
    int a;
};

void foo()
{
    A a; // <-- Which version of class A is chosen by the linker?
    std::cout << a.a << std::endl; // <-- Is "1" or "2" output?
}

在这种情况下,函数foo()有时打印1,有时打印2。

A的构造函数是内联的!这不是函数调用!因此,我认为编译器必须包含代码的汇编/机器指令,该代码在函数a本身的编译代码中实例化对象foo()。函数foo()已编译。

因此,我认为以后,在链接时,链接器在决定包含函数{{foo()时,不会更改foo()定义的汇编/机器指令。 1}}在已编译的二进制文件中(因为它只知道foo()实际上是在链接时被调用的)。根据这种推理,链接器不可能影响将哪个内联构造函数代码编译到函数foo()中,因此它必须是始终使用的内联构造函数的file2版本,尽管故意违反一个定义规则

如果A的构造函数不是内联的,那么我会理解,当编译函数foo()时,函数的JUMP语句(A的构造函数)可能是放在函数foo()的汇编代码中;然后,在链接时,链接器可以填充JUMP语句的地址,并选择A的构造函数的两个定义。

我能想到的唯一解释是,实际上,有时foo()打印1,有时foo()打印2,尽管内联构造函数的存在是编译器在编译“file2.cpp”时,在编译的程序集/机器代码中创建SPACE,表示内联调用A的构造函数的函数foo(),但实际上并没有填充程序集/机器代码本身;然后,在链接时,链接器将A的构造函数的代码复制到函数foo()本身的编译定义中的预定位置,使用它之间的(任意)选择。 A的构造函数的内联函数的两个定义。

我的解释是否正确,还是有其他解释?在这个例子中,尽管故意违反一个定义规则,编译器/链接器可以选择调用A的构造函数,假设构造函数调用是内联的,那么它怎么可能呢?

ADDENDUM:我更改了标题并在顶部附近添加了一段澄清,以回应评论和回答,以明确我明白在此示例中行为未定义,而且我正在寻找有关真实编译器/链接器如何如何产生观察到的行为的单一示例,甚至一次。请注意,我不是在寻找能够预测任何特定时间行为的答案。

ADDENDUM 2 :在回复评论时,我在VS调试器的第A a;行放置了一个断点,并选择了“反汇编”视图。实际上,从反汇编代码中可以看出,DESPITE存在“内联”,在这种情况下,编译器已选择不内联对象a的构造函数调用:

Disassembly view of the line `A a; from the code sample in the question: The compiler has chosen not to inline the constructor call, despite its being inline.

因此,Alf的答案是正确的:尽管构造函数隐含inline,但构造函数调用尚未内联。

因此,出现了一个切线问题:是否可以通过一种方式或另一种方式做出明确的陈述 - 关于构造函数是否比常规成员函数更不可能被内联(假设inline存在,无论是显式还是隐含地,在两种情况下)?如果可以可以,并且答案为“是”,则编译器更可能拒绝inline构造函数,而不是拒绝inline正式成员函数“,那么后续问题就是”为什么“?

3 个答案:

答案 0 :(得分:11)

在类定义中定义构造函数等效于使用关键字inline和类外定义。

inline不要求/保证机器代码的内联扩展。它暗示了这一点,但就是这样。

inline的保证效果是允许在多个翻译单元中使用相同的函数定义(然后必须在使用它的所有翻译单元中对其进行定义,基本相同)。

因此,基于假设所需/保证内联扩展调用的逻辑会因错误假设而产生错误的结论。

答案 1 :(得分:1)

我真的很惊讶代码可能会链接。我几乎肯定它没有与MS Visual Studio链接,链接器抱怨名称冲突。

在这种情况下,如果你真的不想给它们指定不同的名字,你可以使用匿名命名空间来定义你的结构。

答案 2 :(得分:0)

违反ODR不需要诊断。也就是说,如果你这样做,程序的行为是不确定的:任何事情都可能发生。在该示例中,链接器可能不涉及,因为所有成员函数都是内联的,因此代码可能会编译和链接而没有抱怨,并且做它看起来应该做的事情。尽管如此,该标准并未承诺会发生这种情况;这就是未定义行为的含义。