为什么要编译,而不是链接?

时间:2012-12-25 20:41:06

标签: c++ linker virtual

#include <iostream>
using namespace std;

class C
{
public:
    virtual void a();
};

class D : public C
{
public:
    void a() { cout<<"D::a\n"; }
    void b() { cout<<"D::b\n"; }
};

int main()
{
    D a;
    a.b();

    return 0;
}

我收到关于undefined reference to 'vtable for C'的链接错误。这是什么意思,为什么呢?

我知道问题显然是基类有一个永远不会定义的非纯虚函数,但是如果我从不调用它,为什么这会打扰链接器呢?为什么它与我声明和未定义的任何其他功能不同,如果我从不称呼它我很好? 我对这些细节很感兴趣。

4 个答案:

答案 0 :(得分:9)

C ++编译器的大多数实现为每个类生成 vtable ,这是一个虚函数的函数指针表。与任何其他数据项一样,vtable只能有一个定义。一些C ++编译器在编译一个类型中第一个声明的虚函数的实现时生成此vtable(这保证了vtable只有一个定义)。如果您未能为第一个虚函数提供实现,则您的编译器不会生成vtable,并且链接器会因链接错误而抱怨缺少的vtable。

如您所见,具体细节取决于您选择的编译器和链接器的实现。并非所有工具链都是相同的。

答案 1 :(得分:4)

问:我知道问题很明显,基类有一个永不定义的非纯虚函数

答:这是你问题的答案:)

问:为什么它与我声明和未定义的任何其他功能不同,如果我从不称呼它我很好?

答:因为它不仅仅是一个“功能”。这是一个虚拟类方法

建议:

  • 声明三个不同的类:

    1)一个简单的方法

    2)虚拟方法(如上面的“C”)

    3)抽象虚拟方法(= 0)

  • 生成程序集输出(例如GCC的“-S”)

  • 比较三种情况。仔细注意是否创建了*构造函数:)

答案 2 :(得分:0)

如果您希望C成为纯虚拟基类,则需要告诉编译器&#34;不会实现a()&#34;,您可以通过将... a() = 0;用于类的声明。编译器正在尝试使用基类vtable,但是没有一个!

答案 3 :(得分:0)

在某些情况下可能会出现对实际功能定义的需求。您已命名其中一种情况:如果您调用,则必须定义该函数。但是,这还不是全部。考虑这段代码

void foo();

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

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

这就是虚函数的问题所在。虚函数通常通过虚拟表实现:包含指向虚函数定义的指针的表。无论您是否实际调用函数,此表都由编译器无条件地预先形成和初始化。编译器将基本上隐式地获取程序中每个非纯虚函数的地址,并将其放入相应的表中。因此,即使您从未调用过它,每个非纯虚函数也需要一个定义。

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