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

Dan*_*aum 4 c++ compiler-construction linker

参考什么决定在两个源文件中为同名的类包含哪个类定义?在其中存在故意明确违反单一定义规则的情况,我仍然感到困惑的是,编译器/链接器如何选择一个定义而不是另一个定义是可能的.

(附录基于答案/评论:我要寻找的编译器/连接如何只举一个例子可能.产生如下所示的结果,给定的代码是故意标准的侵犯,因此代码产生不确定的行为)

代码示例是:

// 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?
}
Run Code Online (Sandbox Code Playgroud)

...

//file2.h:

void foo();
Run Code Online (Sandbox Code Playgroud)

...

// 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?
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,该功能foo()有时会打印1,有时打印2.

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

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

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

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

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

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

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

线的拆卸视图`A a;  从问题中的代码示例:编译器已选择不内联构造函数调用,尽管它是内联的.

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

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

Che*_*Alf 11

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

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

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

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