了解链接器错误

时间:2015-06-30 04:18:22

标签: c++ virtual

我用虚函数编写了以下程序:

struct A
{
    virtual void foo() = 0;
    A(){ init(); }
    void init(){ foo(); }
};

struct B : A
{
    virtual void foo(){ }
};

B a;

int main(){
    return 0;
}

DEMO

我认为应该确定一些链接器错误,因为找不到foo的实现。我们得到了运行时错误。为什么?为什么链接器没有错误?

3 个答案:

答案 0 :(得分:2)

这里你必须要理解的第一件事是,当foo()类的构造函数处于活动状态时调用A被调度到A::foo(),即使正在构建的完整对象也是如此类型BB会覆盖foo()B::foo()的存在被忽略了。

这意味着您的代码会尝试调用A::foo()。由于A::foo()是一个纯虚函数,因此代码的行为是未定义的。

C ++语言不保证在这种情况下应该出现什么样的“错误”。这意味着您对“链接器错误”的期望是完全没有根据的。如果程序尝试对纯虚函数执行虚拟调用,则行为只是 undefined 。从C ++语言的角度来看,这是唯一可以说的。

这种未定义的行为如何在实际实现中表现出来取决于实现。例如,允许未定义的行为通过编译时错误表现出来。

在您的情况下,您的程序会尝试对纯虚函数A::foo()进行虚拟调用。在一般情况下,编译器通过实现多态的运行时机制来调度虚拟调用动态(所谓的虚方法表是最常用的)。在某些情况下,当编译器可以确定调用中使用的对象的确切类型时,它会优化代码并对虚函数进行普通的直接(非动态)调用。

实际上,如果函数是纯虚函数,则其虚方法表条目包含空指针。对此类函数的动态调用通常会导致运行时错误。同时,对此类函数的直接(优化)调用通常会导致编译器或链接器错误。

在您的示例中,编译器未优化调用。它通过虚拟方法表对A::foo()进行了全面的动态调用。该表中的空指针触发了运行时错误。

如果直接从构造函数

调用纯虚函数
 A() { foo(); } 

典型的编译器通常会对foo()进行直接(优化)调用,这通常会导致链接器错误。

答案 1 :(得分:1)

B确实有foo的实现,因此链接器没有问题。

据我所知,A在错误的时间调用foo的事实是编译器/链接器不需要弄清楚的。 (虽然在这种情况下进行这样的检查可能很简单,但我确信我们可以提出更复杂的案例,这些案例会更难或可能无法捕捉。)

答案 2 :(得分:0)

您的错误是从构造函数中调用虚函数的结果。调用的函数是A中的函数,而不是更多的派生函数。 C ++标准,第12.7.4节说明,

  

可以调用成员函数,包括虚函数(10.3)   在施工或毁坏期间(12.6.2)。 当虚拟功能   从构造函数或从构造函数直接或间接调用   析构函数,包括在构造或破坏期间   类非静态数据成员,以及调用的对象   适用于建造或销毁的对象(称之为x),   被调用的函数是构造函数中的最终覆盖者   析构函数的类,而不是在更派生的类中重写它。   如果虚函数调用使用显式类成员访问   (5.2.5)和对象表达式是指x的完整对象   或者该对象的基类子对象之一,但不是x或其中一个   基类子对象,行为未定义。

现在,你在示例中作弊。您正在从构造函数调用普通函数,然后从正常函数调用虚函数。将您的代码更改为,

struct A
{
    virtual void foo() = 0;
    A(){ foo(); }
};

并且您将收到错误,

warning: pure virtual ‘virtual void A::foo()’ called from constructor [enabled by default]
_ZN1AC2Ev[_ZN1AC5Ev]+0x1f): undefined reference to `A::foo()'