为什么这个编译,而不是链接

Why does this compile, but not link?

本文关键字:链接 编译 为什么      更新时间:2023-10-16
#include <iostream>
using namespace std;
class C
{
public:
    virtual void a();
};
class D : public C
{
public:
    void a() { cout<<"D::an"; }
    void b() { cout<<"D::bn"; }
};
int main()
{
    D a;
    a.b();
    return 0;
}

我得到一个关于undefined reference to 'vtable for C'的链接错误。这意味着什么?为什么会这样?

我知道问题显然是基类有一个从未定义的非纯虚函数,但如果我从不调用它,为什么这会打扰链接器?为什么它和其他函数不同,我声明了,没有定义,如果我不调用它,我就没事了?
对基本细节感兴趣。

大多数c++编译器的实现都会为每个类生成一个虚函数表,它是虚函数的函数指针表。与任何其他数据项一样,虚值表只能有一个定义。一些c++编译器在编译类型中第一个声明的虚函数的实现时生成这个虚函数表(这保证了虚函数表只有一个定义)。如果你没有提供第一个虚函数的实现,编译器就不会生成虚函数表,并且链接器会报错虚函数表缺失,并给出链接错误。

可以看到,具体细节取决于所选择的编译器和链接器的实现。并不是所有的工具链都是一样的

问:我知道问题显然是基类有一个从未定义的非纯虚函数

A:这就是你问题的答案了。

问:为什么它与我声明而不定义的任何其他函数不同,如果我从不调用它,我就没事了?

A:因为它不仅仅是一个"功能"。这是一个虚拟类方法

建议:

  • 声明三个不同的类:

    一个简单的方法

    2)虚方法(如上面的"C")

    3)抽象虚方法(= 0)

  • 生成装配输出(例如:

  • 比较三种情况。如果创建了"构造函数",请仔细注意:)

如果你想要C是一个纯虚拟基类,你需要告诉编译器"将没有a()的实现",你通过使用... a() = 0;来声明类来做到这一点。编译器试图找到一个基类虚函数表,但是没有一个!

在几种情况下可能需要实际的函数定义。您已经命名了这样一种情况:如果调用,则必须定义该函数。然而,这还不是全部。考虑这段代码

void foo();
int main() {
  void (*p)() = &foo;
}

此代码从不调用foo。但是,如果您尝试编译它,典型的编译器会抱怨在链接阶段缺少foo的定义。因此,取函数的地址(即使你从不调用它)恰好是必须定义函数的情况之一。

这就是虚函数问题的由来。虚函数通常通过虚表实现:虚表包含指向虚函数定义的指针。这个表是由编译器事先无条件地形成和初始化的,而不管您是否实际调用了这些函数。编译器实际上会隐式地获取程序中每个非纯虚函数的地址,并将其放入相应的表中。因此,即使不调用每个非纯虚函数,也需要定义。

确切的虚拟机制是一个实现细节,因此您可能会根据您的情况(例如缺少函数定义或缺少虚拟表本身)而最终出现不同的特定错误,但这些错误的根本原因是我上面描述的。