首先我用下面这一段代码来测试:
- #include <iostream>
- using namespace std;
- class TheBase
- {
- public:
- TheBase();
- TheBase(int);
- virtual void PrintInfo();
- virtual void PrintItself();
- virtual void PrintTest();
- private:
- int m_mem;
- };
- TheBase::TheBase()
- {
- m_mem = 0;
- }
- TheBase::TheBase(int value)
- {
- m_mem = value;
- }
- void TheBase::PrintInfo()
- {
- cout<<"Hello, you have Enter the TheBase::PrintInfo function"<<endl;
- }
- void TheBase::PrintItself()
- {
- cout<<"I am TheBase class"<<endl;
- }
- void TheBase::PrintTest()
- {
- cout<<"Hello, you have Enter the TheBase::PrintTest function"<<endl;
- }
- //用一个类来继承
- class TheSon : public TheBase
- {
- public:
- TheSon();
- TheSon(int);
- void PrintInfo();
- void PrintItself();
- };
- TheSon::TheSon()
- {
- }
- TheSon::TheSon(int value):TheBase(value)
- {
- }
- void TheSon::PrintInfo()
- {
- cout<<"Hello, you have Enter the TheSon::PrintInfo function"<<endl;
- }
- void TheSon::PrintItself()
- {
- cout<<"I am TheSon class"<<endl;
- }
- int main()
- {
- TheBase* test = new TheSon(10);
- test->PrintInfo();
- test->PrintItself();
- test->PrintTest();
- delete test;
- return 0;
- }
相信大家很容易看懂这段代码了,运行结果就可想而知了。但是我们有没有问过问什么会这样呢?虚函数表现出来的动态链编是怎样实现的呢?
对于这些疑问,我现在慢慢分析。
首先就是要确定我们在用test调用函数的时候,到底做了什么事。要知道这些,我通过了反汇编看了一些,同时为了便于分析,我采用的debug版本的。
- mov eax, [ebp+var_20]; 将类指针传给eax
- mov [ebp+var_14], eax
- mov [ebp+var_4], 0FFFFFFFFh
- mov ecx, [ebp+var_14]
- mov [ebp+var_10], ecx
- mov edx, [ebp+var_10]
- mov eax, [edx];取得类中的虚函数表的起始地址
- mov esi, esp
- mov ecx, [ebp+var_10]
- call dword ptr [eax];调用虚函数表中的第一个函数
根据注释相信大家就能够分析出最后一条call dword ptr [eax]就是调用虚函数表中的第一个函数了,很明显就是PrintInfo,可虚函数表是怎样的呢?我们来看一下子。
上面对应的就是TheSon类的虚函数表,相信知道命名粉碎的人都能够看懂上面的?PrintInfo@TheSon@@UAEXXZ代表的含义。不过不了解的话也不要紧,不会有影响我们的理解。大家只需要知道第一个是函数的名字,接着跟的是这个函数所在的类名,后面的是参数规则,在这里就不多说了。
很明显第一个函数是PrintInfo,第二个函数是PrintItself,同时这两个函数所在的类都是TheSon。我们仔细看第三个函数,他的名字是PrintTest,但是所属的类是TheBase,并不是TheSon.可想而知即便我们采用TheSon的对象来调用的话也是调用TheBase中的函数PrintTest。造成这个的原因是:我们在TheSon类中没有管父类中的PrintTest函数,导致编译器默认为没有了虚函数的特性了,也就丧失了虚函数的特性了。
弄清楚了虚函数是通过虚函数表来实现的,那么虚函数是怎样建立的,在什么时候被建立的呢?
要弄清楚这,还得从new创建一个TheSon对象开始说起:
- push 8 ; unsigned int 传递的是给new函数的参数,大小是8,为什么?虚函数表地址4字节 + 一个int类型的变量 = 8
- call ??2@YAPAXI@Z ; operator new(uint) 调用new来分配空间,当然返回值在eax中。
;按照程序的执行,就要将这个分配的空间交给TheSon类来进行一些初始化了,下面的代码就是干这个事。- push 0Ah ;我们传递个TheSon构造函数的参数是10,所以这里是0Ah
- mov ecx, [ebp+var_18];将类的this指针给ecx,其实就是我们前面分配的空间的地址。
- call j_??0TheSon@@QAE@H@Z ; TheSon::TheSon(int) 调用TheSon的构造函数。
进入到类的构造函数中:熟悉C++的人知道,首先就要进行父类的初始化,这个过程是通过调用父类的构造函数实现的。的确代码就是这样执行的。
- push eax ;eax保存的就是我们传递的参数10,现在我们要传递给父类,因为变量在父类中。
- mov ecx, [ebp+var_4] ;这个过程是讲this指针交给ecx,当然现在这个this指针是子类的。其实对于汇编来说
- ;根本就没有这个说法
- call j_??0TheBase@@QAE@H@Z ; TheBase::TheBase(int) ;果然,调用了父类的构造函数,参数是通过压栈传入的。
现在就要进入父类的构造函数了。
- mov [ebp+var_4], ecx ;将this指针交个了一个局部变量
- mov eax, [ebp+var_4] ;又交给了eax
- mov dword ptr [eax], offset ??_7TheBase@@6B@ ; const TheBase::`vftable' ;现在就要填充虚函数表的地址了。
- mov ecx, [ebp+var_4] ;又将this交给了eax,用于返回。
当然上面填充的虚函数表是父类的,那么是怎样的呢?
相信大家有前面的基础现在看这个表不是很困难。但是将父类的表交给了this指针所指的对象,而this指针有没有类型之分,同时this指针也返回给了子类,那么子类的虚函数表且不是和父类的一样吗?
肯定不一样了,因为如果是一样,那么运行的结果就不是我们所看到的了,那是怎样处理的呢?
- mov ecx, [ebp+var_4]
- call j_??0TheBase@@QAE@H@Z ; TheBase::TheBase(int) ;跟前面的一样,调用父类构造函数,在这里写出来是
- ;为了让大家更好的理解。
- mov ecx, [ebp+var_4] ;这里将this指针交给了ecx
- mov dword ptr [ecx], offset ??_7TheSon@@6B@ ; const TheSon::`vftable' ;注意在这里,又进行了虚函数表的
- ;初始化,很显然,就是子类的虚函数表的初始化了,那么子类的虚函数是怎样的呢?前面我已经给出啦。
到此,我们就明白了C++中到底是怎样实现多态的了,关键在于一张虚函数表。通过上面的分析,我们应该很清楚的看出了虚函数表的建立过程。
呵呵,到这里就结束了,以上只是我的一些浅薄间接,有错误的还请高手指正。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请
点击举报。