虚函数实现细节

1.虚函数

·虚表是怎么实现的?虚表存放在哪里?

·虚表中的数据是在什么时候确定的?

·对象中的虚表指针又在什么时候赋值的?

我们很难通过 C++语言本身来找到答案。 C++标准给编译器实现者定义了语法规范,但是被并没有定义如何实现这些语法规范,不同的编译器实现者可能有不同的实现方法,可以肯定的是他们的编译器必须符合这些语法规范。汇编语言作为最接近机器语言的计算机语言,可以为我们揭示一些隐藏在编译器内部的细节。接下来本来就试图通过对 C++源码进行反汇编的方式来解答这些疑惑。

二、分析

这里我选用 WinXP 和 VS2008 作为我们这次分析的平台。我们建立一个最简单的 Win32 控制台程序,并定义两个简单的类:


接下来我们可以直接编译这些 C++源码就可以得到相应的汇编代码。 通过分析这些汇编代码我们就找到许多有用的信息。我们可以找到这样的汇编代码:


以上的汇编代码定义了两个数据段, 而这两个数据段中的内容恰好就是类的虚表。 至此虚表的"庐山真面目"完全展示在我们的面前。 根据这些信息,我们可以推理出很多有用的结论:

·拥有虚函数的类会有一个虚表,而且这个虚表存放在类定义模块的数据段中。模块的数据段通常存放定义在该模块的全局数据和静态数据,这样我们可以把虚表看作是模块的全局数据或者静态数据

·类的虚表会被这个类的所有对象所共享。类的对象可以有很多,但是他们的虚表指针都指向同一个虚表,从这个意义上说,我们可以把虚表简单理解为类的静态数据成员。值得注意的是,虽然虚表是共享的,但是虚表指针并不是,类的每一个对象有一个属于它自己的虚表指针。

·虚表中存放的是虚函数的地址。

另外一个大的疑惑就是对象的虚表指针是在什么时候被赋值的? 我们都知道,类的对象是通过构造函数来完成初始化,但是我们从来没有在构造函数中初始化虚表指针, 那么编译器在幕后又做了哪些事情呢? 我们依然还是通过反汇编来找到答案。 在这个控制台程序的 main 函数中我们构建一个类对象:



 

类的非静态成员函数调用时,编译器会传入一个"隐藏"的参数。 这个参数就是通常我们说的"this"指针,它的值就是对象的地址。 在上面的代码中,寄存器 ECX 保存的就是这个"

this" 指 针 , 同 时 它 的 值 又 赋 给 了 寄 存 器 EAX。"??_7CD-szBase@@6B@"就是上面提到的虚表,同时它也代表了虚表的地址。

接下来,虚表的地址被赋给了由寄存器 EAX 指定的内存中。由此可见,虚表的地址被存放在对象的起始位置,即对象的第一个数据成员就是它的虚表指针。 同时我们还可以注意到,虚表指针的初始化确实发生在构造函数的调用过程中, 但是在执行构造函数体之前,即进入到构造函数的"{"和"}"之前。 为了更好的理解这一问题, 我们可以把构造函数的调用过程细分为两个阶段,即:

1.进入到构造函数体之间。在这个阶段如果存在虚函数的话,虚表指针被初始化。如果存在构造函数的初始化列表的话,初始化列表也会被执行。

2.进入到构造函数体内。这一阶段是我们通常意义上说的构造函数

 

简单的搞个基类Base{void fun();virtual void print(){...};public:int a;static b;}

定义一个对象 B b;调试状态下就可以看到b包含了什么

类中只有虚表指针和普通成员(包括const成员)而普通函数,静态成员是不在类中的.



class Base {

     public:

            virtual void f() { cout << "Base::f" << endl; }

            virtual void g() { cout << "Base::g" << endl; }

            virtual void h() { cout << "Base::h" << endl; }

 

};

 

按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:

 

          typedef void(*Fun)(void);

 

            Base b;

 

            Fun pFun = NULL;

 

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

            cout << "虚函数表的地址,同时也是  第一个虚函数地址:" << (int*)*(int*)(&b) << endl;

 

            // Invoke the first virtual function 

            pFun = (Fun)*((int*)*(int*)(&b));

            pFun();

 

虚函数表地址:0012FED4

虚函数表 — 第一个函数地址:0044F148

Base::f

 虚函数表的地址是一样的,也就是说一个类维护一张虚函数表,每个对象拥有一个虚函数表指针指向虚函数表

 

通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()Base::h(),其代码如下:

 

         (Fun)*((int*)*(int*)(&b)+0);  // Base::f()

            (Fun)*((int*)*(int*)(&b)+1);  // Base::g()

            (Fun)*((int*)*(int*)(&b)+2);  // Base::h()


//测试虚函数表和虚函数指针
//基类每个对象都有独立的一个虚函数表指针,指向同一个虚函数表
//一个类只有一张虚函数表,所有对象共享
//派生类的虚函数表从基类继承过来,在虚函数表上修改虚函数指针
//派生类新增的虚函数不会在虚函数表中
#include <iostream>
using namespace std;

typedef void (*Func)();

class CBase
{
public:
    static int m_gNum;
	int m_nNum;
	char m_c;

	CBase():m_nNum(0), m_c(0){
		cout << "call CBase()." << endl;
	}
	virtual void func1(){
		cout << "call CBase::func1()" << endl;
	}
	virtual void func2(){
		cout << "call CBase::func2()" << endl;
	}
	virtual ~CBase(){
		cout << "call ~CBase()." << endl;
	}
};

class CDerive : public CBase
{
public:
	int m_nCount;
	CDerive():CBase(), m_nCount(0){
		cout << "call CDeriv()" << endl;
	}
	virtual void func2(){
		cout << "call CDerive::func2()" << endl;
	}
	virtual void func3(){
		cout << "call CDerive ::func3()" << endl;
	}
	virtual ~CDerive(){
        cout << "call ~CDerive" << endl;
	}
};

int main(void)
{
	CBase base1, base2;
	CDerive deriv1;
	CBase *base3 = new CDerive();
	CBase *base4 = new CBase();
	cout << "base1:" << &base1 << endl;
	cout << "base1:" << (int*)*(int*)&base1 << endl;
	cout << "base2:" << &base2 << endl;
	cout << "base2:"  << (int*)*(int*)&base2 << endl;
	cout << "deriv1:" << &deriv1 << endl;
	cout << "deriv1:" << (int*)*(int*)&deriv1 << endl;
	cout << "base3:" << base3 << endl;
	cout << "base3:"  << (int*)*(int*)base3 << endl;
	cout << "base4:" << base4 << endl;
	cout << "base4:"  << (int*)*(int*)base4 << endl;
    Func p = (Func)*((int*)*(int*)&base1);
	p();
    p = (Func)*((int*)*(int*)&deriv1);
	p();
    p = (Func)*((int*)*(int*)&deriv1 + 1);
	p();
    p = (Func)*((int*)*(int*)&deriv1 + 2);
	p();//程序崩溃,调用了析构函数,不会调用新增的虚函数
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值