参考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
的构造函数调用:
因此,Alf的答案是正确的:尽管构造函数隐含inline
,但构造函数调用尚未内联。
inline
存在,无论是显式还是隐含地,在两种情况下)?如果可以可以,并且答案为“是”,则编译器更可能拒绝inline
构造函数,而不是拒绝inline
正式成员函数“,那么后续问题就是”为什么“?
答案 0 :(得分:11)
在类定义中定义构造函数等效于使用关键字inline
和类外定义。
inline
不要求/保证机器代码的内联扩展。它暗示了这一点,但就是这样。
inline
的保证效果是允许在多个翻译单元中使用相同的函数定义(然后必须在使用它的所有翻译单元中对其进行定义,基本相同)。
因此,基于假设所需/保证内联扩展调用的逻辑会因错误假设而产生错误的结论。
答案 1 :(得分:1)
我真的很惊讶代码可能会链接。我几乎肯定它没有与MS Visual Studio链接,链接器抱怨名称冲突。
在这种情况下,如果你真的不想给它们指定不同的名字,你可以使用匿名命名空间来定义你的结构。
答案 2 :(得分:0)
违反ODR不需要诊断。也就是说,如果你这样做,程序的行为是不确定的:任何事情都可能发生。在该示例中,链接器可能不涉及,因为所有成员函数都是内联的,因此代码可能会编译和链接而没有抱怨,并且做它看起来应该做的事情。尽管如此,该标准并未承诺会发生这种情况;这就是未定义行为的含义。