C++ 程序是如何通过链接器的?
这是程序C++ 程序是如何通过链接器的?,c++,C++,这是程序link\u函数.cpp #include <iostream> class Base { public: Base() { init(); // first condition // log(); // second condition } virtual ~Base() {} virtual void log() = 0; pr
link\u函数.cpp
#include <iostream>
class Base {
public:
Base() {
init(); // first condition
// log(); // second condition
}
virtual ~Base() {}
virtual void log() = 0;
private:
void init() {
log();
}
};
class Derived: public Base {
virtual void log() {}
};
int main() {
Derived d;
return 0;
}
init()
函数如何传递链接器而不产生未定义到引用日志()的错误
第二个条件
当我注释int()
并只调用log()时,它产生了一个链接器错误
对“Base::log()”的未定义引用
它们之间的区别是什么?您的日志功能是虚拟的,所以链接器不必担心。链接器不关心虚拟函数,因为它们的地址只有在运行时才知道
代码的问题在于,您从Base
构造函数调用虚函数。但是在调用Base
构造函数时,派生的
尚未构造,因此log
函数有点未定义
Derived
类的每个对象都包含Base
类的“子对象”,该类在对象本身之前构造(即,在Derived
之前调用Base
构造函数)。当Base
构造函数执行时,它不知道派生的
类的虚拟表,因此它无法解析日志
要调用的地址。因此,它将其视为纯虚拟函数并中止执行。派生类是从基类开始构建的。在Base的构造函数期间,派生的构造函数尚未执行。因此,vtable尚未完成
在构造函数中调用虚拟函数(即使是非纯函数)是很淘气的。这意味着类设计不正确
这就是为什么这是个坏主意:
#include <iostream>
using namespace std;
class Base {
public:
Base() {
init();
// log();
}
virtual void log() { cout << "stange outcome 1" << endl; }
private:
void init() {
log();
}
};
class Derived: public Base {
public:
virtual void log() { cout << "stange outcome 2" << endl;}
Derived()
: Base()
{
log();
}
};
int main() {
Derived d;
d.log();
return 0;
}
解释原因:
当我们创建派生类的对象时,程序执行以下操作:
为派生内存分配足够大的内存
将对象的vtable指针设置为指向基础的vtable
执行Base::Base()[对log()的调用将调用Base::log()]
将对象的vtable指针设置为指向一个虚拟对象的vtable
执行派生::派生()[对log()的调用将调用Dervied::log()]
对象现在已完全构造
这是一个强大的实现细节。但是链接器无法可靠地发现v-table包含未初始化的条目。最重要的是因为C++类有两个V表。在构造函数完成执行之前,真正的类不会被放置到位。我猜链接器可能会在第一种情况下通过v-tables找到派生类的log()
实现。但为什么它找不到一个处于第二种状态的。它知道在基构造函数中调用虚函数是一种糟糕的做法。我只是好奇细节。你能给我推荐一些这方面的书吗?非常感谢。您说过,链接器不关心虚拟函数,因为它们的地址只有在运行时才知道。
但在条件2中,链接器会抱怨。看起来编译器将构造函数中的日志
识别为非虚拟方法(因为它无论如何都不能调用虚拟方法)并尝试直接调用log
。它不会抱怨init,因为1)它可以在构造函数外部调用,2)完整的源代码分析在计算上很昂贵(并且在某种程度上受到限制,请参见停止定理)。但这是我的意见。非常感谢你的回答。我知道在基构造函数中调用虚函数实际上是一种糟糕的做法。然而,如此相似的条件1和条件2产生了不同的结果。我只是对这些很好奇。在构造过程中,Base完全不知道有任何派生自它的东西。它的vtable是一个基地的vtable。当调用派生的构造函数时,它会修改Base的vtable(实际上可能会用一个新的替换它)。因此,在完成编译阶段后,Base的vtable在两种情况下是否相同?因为它们都通过了编译器阶段,而条件2在链接器阶段失败。
#include <iostream>
using namespace std;
class Base {
public:
Base() {
init();
// log();
}
virtual void log() { cout << "stange outcome 1" << endl; }
private:
void init() {
log();
}
};
class Derived: public Base {
public:
virtual void log() { cout << "stange outcome 2" << endl;}
Derived()
: Base()
{
log();
}
};
int main() {
Derived d;
d.log();
return 0;
}
Compiling the source code....
$g++ -std=c++11 main.cpp -o demo -lm -pthread -lgmpxx -lgmp -lreadline 2>&1
Executing the program....
$demo
stange outcome 1
stange outcome 2
stange outcome 2