探索C++虚函数在g++中的实现
本文是我在追查一个诡异core问题的过程中收获的一点心得,把公司项目相关的背景和特定条件去掉后,仅取其中通用的C++虚函数实现部分知识记录于此。
在开始之前,原谅我先借用一张图黑一下C++:
“无敌”的C++
如果你也在写C++,请一定小心…至少,你要先有所了解: 当你在写虚函数的时候,g++在写什么?
先写个例子
为了探索C++虚函数的实现,我们首先编写几个用来测试的类,代码如下:
C++
#include <iostream>using namespace std;class Base1{public: virtual void f() { cout << "Base1::f()" << endl; }};class Base2{public: virtual void g() { cout << "Base2::g()" << endl; }};class Derived : public Base1, public Base2{public: virtual void f() { cout << "Derived::f()" << endl; } virtual void g() { cout << "Derived::g()" <&l<div>本文来源gaodai.ma#com搞##代!^码@网3</div>t; endl; } virtual void h() { cout << "Derived::h()" << endl; }};int main(int argc, char *argv[]){ Derived ins; Base1 &b1 = ins; Base2 &b2 = ins; Derived &d = ins; b1.f(); b2.g(); d.f(); d.g(); d.h();}
代码采用了多继承,是为了更多的分析出g++的实现本质,用UML简单的画一下继承关系:
示例代码UML图
代码的输出结果和预期的一致,C++实现了虚函数覆盖功能,代码输出如下:
Derived::f()Derived::g()Derived::f()Derived::g()Derived::h()
开始分析!
我写这篇文章的重点是尝试解释g++编译在底层是如何实现虚函数覆盖和动态绑定的,因此我假定你已经明白基本的虚函数概念以及虚函数表(vtbl)和虚函数表指针(vptr)的概念和在继承实现中所承担的作用,如果你还不清楚这些概念,建议你在继续阅读下面的分析前先补习一下相关知识,陈皓的 《C++虚函数表解析》 系列是一个不错的选择。
通过本文,我将尝试解答下面这三个问题:
-
g++如何实现虚函数的动态绑定?
-
vtbl在何时被创建?vptr又是在何时被初始化?
-
在Linux中运行的C++程序虚拟存储器中,vptr、vtbl存放在虚拟存储的什么位置?
首先是第一个问题:
g++如何实现虚函数的动态绑定?
这个问题乍看简单,大家都知道是通过vptr和vtbl实现的,那就让我们刨根问底的看一看,g++是如何利用vptr和vtbl实现的。
第一步,使用 -fdump-class-hierarchy 参数导出g++生成的类内存结构:
Vtable for Base1Base1::_ZTV5Base1: 3u entries0 (int (*)(...))04 (int (*)(...))(& _ZTI5Base1)8 Base1::fClass Base1 size=4 align=4 base size=4 base align=4Base1 (0xb6acb438) 0 nearly-empty vptr=((& Base1::_ZTV5Base1) + 8u)Vtable for Base2Base2::_ZTV5Base2: 3u entries0 (int (*)(...))04 (int (*)(...))(& _ZTI5Base2)8 Base2::gClass Base2 size=4 align=4 base size=4 base align=4Base2 (0xb6acb474) 0 nearly-empty vptr=((& Base2::_ZTV5Base2) + 8u)Vtable for DerivedDerived::_ZTV7Derived: 8u entries0 (int (*)(...))04 (int (*)(...))(& _ZTI7Derived)8 Derived::f12 Derived::g16 Derived::h20 (int (*)(...))-0x00000000424 (int (*)(...))(& _ZTI7Derived)28 Derived::_ZThn4_N7Derived1gEvClass Derived size=8 align=4 base size=8 base align=4Derived (0xb6b12780) 0 vptr=((& Derived::_ZTV7Derived) + 8u) Base1 (0xb6acb4b0) 0 nearly-empty primary-for Derived (0xb6b12780) Base2 (0xb6acb4ec) 4 nearly-empty vptr=((& Derived::_ZTV7Derived) + 28u)