本文重点参考了《C++ 虚函数表解析》一文(链接:http://blog.csdn.net/haoel/article/details/1948051/),陈皓前辈此文讲解清晰,之前阅读受益匪浅,只是代码中存在一些问题。例如涉及到本文重点虚函数表的地方,写到

Base b;
cout << "虚函数表地址:" << (int*)(&b) << endl;

    但是,实际上(int*)(&b)并非虚函数表地址,而是对象b的地址,*(int*)(&b)才是虚函数表的地址。此外,后文中一些指针操作也有异常之处(也有可能是编译环境不同?)。为此,我重新进行编码实验,并记录此文。


一、实验环境

本机操作系统Linux wm-ThinkPad-X240s 3.13.0-44-generic #73-Ubuntu SMP Tue Dec 16 00:22:43UTC 2014 x86_64 x86_64 x86_64 GNU/Linux。注意是64位机器,意味着指针长度为8字节;如果是32位机器,则指针长度为4字节。


    我们根据包含虚函数的类是否涉及继承、如何继承,分单个类无继承、一般继承、多重继承三种情况,讨论虚函数的内存分配。

二、单个类无继承

        我们编写下列代码,其中类Base包含三个虚函数。

#include <iostream>
 
using namespace std;
 
class Base {
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
};
 
typedef void (*Fun) ();
 
int main() {
    Base b;
    Fun pFun = NULL;
 
    cout << "sizeof(b): " << sizeof(b) << endl;
 
    cout << "对象的地址: " << (&b) << endl;
    cout << "虚函数表的地址: " << *(int*)(&b) << endl;
 
    pFun = (Fun)(*(int*)(*(int*)(&b)));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&b) + 8));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&b) + 16));
    pFun();
 
    return 0;
}


运行结果为

wKiom1XVV3SjC2nlAADKd3l7ftA359.jpg

根据运行结果可知,对象b的内存规模为8字节,这8字节为存储虚函数表指针所占用(64位机器,指针8字节)。

这里顺便说一些C++类中与虚函数无直接关系的内存分配知识,如果没有虚函数,则类和对象的所占内存为类中数据成员的内存量(需要考虑对齐),类中函数成员不占内存量。那么如果一个类没有数据成员和虚函数,对其求sizeof,结果应当是多少呢?结果本来应当是0,但是一个实例它必须在内存中占有一定的空间,因此实际结果为1。《剑指offer》中就有类似的题目,有兴趣的朋友可以自行实验。

根据代码及其运行结果,我们可以推断出其内存分配如下图所示。

wKioL1XVXJeCUhG7AAHEaKD_Jy4088.jpg


三、一般继承

        我们编写下列代码,其中Base为基类, Derive继承Base类。

#include <iostream>
 
using namespace std;
 
class Base {
public:
    virtual void f() { cout << "Base::f" << endl; }
    virtual void g() { cout << "Base::g" << endl; }
    virtual void h() { cout << "Base::h" << endl; }
};
 
class Derive : public Base {
public:
    void f() { cout << "Derive::f" << endl; }
    virtual void g1() { cout << "Derive::g1" << endl; }
    virtual void h1() { cout << "Derive::h1" << endl; }
};
 
typedef void (*Fun) ();
 
int main() {
    Derive d;
    Fun pFun = NULL;
 
    cout << "sizeof(d): " << sizeof(d) << endl;
    cout << "对象的地址: " << (&d) << endl;
    cout << "虚函数表的地址: " << *(int*)(&d) << endl;
 
    pFun = (Fun)(*(int*)(*(int*)(&d)));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&d) + 8));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&d) + 16));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&d) + 24));
    pFun();
 
    pFun = (Fun)(*(int*)(*(int*)(&d) + 32));
    pFun();
 
    return 0;
}

运行结果为

wKiom1XVWvyAbppoAADoBPPPuzQ059.jpg

根据代码及其运行结果,我们可以推断出其内存分配如下图所示。

wKioL1XVXSDSpDtpAAG4RRuf_BQ431.jpg


四、多重继承

        我们编写下列代码,类Derive继承类Base1Base2Base3

#include <iostream>
 
using namespace std;
 
class Base1 {
public:
    virtual void f() { cout << "Base1::f" << endl; }
    virtual void g() { cout << "Base1::g" << endl; }
    virtual void h() { cout << "Base1::h" << endl; }
};
 
class Base2 {
public:
    virtual void f() { cout << "Base2::f" << endl; }
    virtual void g() { cout << "Base2::g" << endl; }
    virtual void h() { cout << "Base2::h" << endl; }
};
 
class Base3 {
public:
    virtual void f() { cout << "Base3::f" << endl; }
    virtual void g() { cout << "Base3::g" << endl; }
    virtual void h() { cout << "Base3::h" << endl; }
};
 
class Derive : public Base1, public Base2, public Base3 {
public:
    void f() { cout << "Derive::f" << endl; }
    virtual void g1() { cout << "Derive::g1" << endl; }
    virtual void h1() { cout << "Derive::h1" << endl; }
};
 
typedef void(*Fun)(void);
 
int main() {
    Derive d;
    Fun pFun = NULL;
    int** pVtab = (int**)&d;
 
    cout << "sizeof(b): " << sizeof(d) << endl;
 
    pFun = (Fun)pVtab[0][0];
    pFun();
 
    pFun = (Fun)pVtab[0][2];
    pFun();
 
    pFun = (Fun)pVtab[0][4];
    pFun();
 
    pFun = (Fun)pVtab[0][6];
    pFun();
 
    pFun = (Fun)pVtab[0][8];
    pFun();
 
    pFun = (Fun)pVtab[1][0];
    pFun();
 
    pFun = (Fun)pVtab[1][2];
    pFun();
 
    pFun = (Fun)pVtab[1][4];
    pFun();
 
    pFun = (Fun)pVtab[2][0];
    pFun();
 
    pFun = (Fun)pVtab[2][2];
    pFun();
 
    pFun = (Fun)pVtab[2][4];
    pFun();
 
    return 0;
}

运行结果

wKiom1XVW17icx1QAADnJINvDxs178.jpg

根据代码及其运行结果,我们可以推断出其内存分配如下图所示。

wKioL1XVXfDDdf3VAALywYTDdCA501.jpg