了解链接器错误

Understanding linker errors

我用虚函数写了下面的程序:

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 的实现。相反,我们得到了运行时错误。为什么?为什么不是链接器错误?

你在这里首先要明白的是,在 class A 的构造函数处于活动状态时对 foo() 的调用被调度到 A::foo(),即使如果正在构建的完整对象的类型为 BB 覆盖 foo()B::foo() 的存在被简单地忽略。

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

C++ 语言不保证在这种情况下应该发生哪种 "error"。这意味着您对 "linker error" 的期望是完全没有根据的。如果程序试图执行对纯虚函数的虚调用,则行为只是 undefined。从 C++ 语言的角度来看,这是这里唯一可以说的。

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

在你的例子中,你的程序试图对纯虚函数进行虚调用A::foo()。在一般情况下,编译器通过实现多态性的运行时间机制动态分派虚拟调用(所谓的虚拟方法table 是最受欢迎的)。在某些情况下,当编译器可以确定调用中使用的对象的确切类型时,它会优化代码并对虚拟函数进行普通的直接(非动态)调用。

实际上,如果一个函数是纯虚函数,它的虚方法table入口包含一个空指针。对此类函数的动态调用通常会导致 运行-time 错误。同时,直接(优化)调用此类函数通常会导致编译器或链接器错误。

在你的例子中,编译器没有优化调用。它通过虚拟方法 table 对 A::foo() 进行了完整的动态调用。 table 中的空指针触发了 运行 时间错误。

如果直接从构造函数调用纯虚函数

 A() { foo(); } 

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

B 确实有 foo 的实现,所以链接器没有问题。

据我所知,A 在糟糕的时间调用 foo 这一事实不需要 compiler/linker 弄清楚。 (虽然在这种情况下进行这样的检查可能很简单,但我相信我们可以想出更复杂的情况,这些情况将更难或可能无法发现。)

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

Member functions, including virtual functions (10.3), can be called during construction or destruction (12.6.2). When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the classes non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor's or destructor's class and not one overriding it in a more-derived class. If the virtual function call uses an explicit class member access (5.2.5) and the object expression refers to the complete object of x or one of that object's base class subobjects but not x or one of its base class subobjects, the behavior is undefined.

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

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()'