参考什么决定了两个源文件中的同名类包含哪个类定义?,其中故意,明显违反了一个定义规则,我仍然感到困惑,编译器/链接器甚至可以选择一个定义而不是另一个定义。
(基于答案/评论的附录:我正在寻找一个示例,说明编译器/链接器如何产生如下所示的结果,给出的代码故意违反标准,因此代码导致未定义的行为。)
代码示例是:
// 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。
但是 for 的构造函数A
是内联的!这不是函数调用!因此,我认为编译器必须在编译函数时在函数本身a
的编译代码中包含用于实例化对象的代码的汇编/机器指令。foo()
foo()
因此,我认为稍后,在链接时,链接器不会更改汇编/机器指令以定义foo()
何时决定将函数包含foo()
在已编译的二进制文件中(因为它只知道foo()
实际上被调用,在链接时)。根据这种推理,链接器不可能影响将哪个内联构造函数代码编译到函数foo()
中,因此尽管故意违反了单一定义规则,但始终使用的必须是 file2 的内联构造函数版本。
如果 for 的构造函数A
不是内联的,那么我会理解,在foo()
编译函数时,函数的 JUMP 语句(构造函数A
)可能会放在函数的汇编代码中foo()
;然后,稍后,在链接时,链接器可以用它选择的构造函数的两个定义来填充 JUMP 语句的地址A
。
我能想到的唯一解释是,实际上,尽管存在内联构造函数,但有时会foo()
打印1
,有时会foo()
打印2
是编译器在编译“file2.cpp”时,会在编译的程序集/机器代码中创建 SPACE 表示用于对 A 的构造函数foo()
进行内联调用的函数,但实际上并不填充程序集/机器代码本身;并且稍后,在链接时,链接器将构造函数的代码复制A
到函数本身的编译定义中的预定位置foo()
,使用它在构造函数的内联函数的两个定义之间的(任意)选择A
.
我的解释是正确的,还是有其他解释?在这个例子中,尽管故意违反了单一定义规则,编译器/链接器怎么可能选择调用哪个构造函数A
,因为构造函数调用是内联的?
附录:我更改了标题并在顶部附近添加了一段澄清,以回应评论和答案,以明确表示我理解此示例中的行为未定义,并且我正在寻找一个示例来说明如何一个真正的编译器/链接器甚至可以产生一次观察到的行为。请注意,我不是在寻找可以预测任何特定时间行为的答案。
附录 2:作为对评论的回应,我A a;
在 VS 调试器的行中放置了一个断点并选择了“反汇编”视图。事实上,从反汇编代码中可以看出,尽管存在“内联”,但在这种情况下,编译器选择不内联对象的构造函数调用a
:
因此,Alf 的回答是正确的:尽管构造函数是隐式inline
的,但构造函数调用并没有被内联。
inline
因此,一个切题的问题出现了:关于构造函数是否比常规成员函数更不可能被内联(假设在这两种情况下显式或隐式地存在),是否可以做出明确的陈述 - 一种方式或另一种方式?如果可以对此发表声明并且答案是“是的,编译器更有可能拒绝inline
构造函数,而不是拒绝inline
常规成员函数”,那么后续问题将是“为什么”?