虚函数=虚拟函数 虚拟函数地址表=虚表(vtable)
每个类中含有虚函数的对象,编译器都会为它们指定一个虚表(其实是一个函数指针数组),保存在数据区,它由此类所有的对象共用(即静态的),同时编译器也会为它(每个类对象)加上一个成员变量,一个指向自己虚表的指针(常称为"vptr"),并存放在对象的首地址上,由此每个类(含有虚函数)分配的对象都有一个vptr,当我们调用虚函数时,实际上是我们通过vptr找到虚表(vtable),再通过偏移来找到真正函数的地址,
其奥妙在于这个vtable以及这种间接调用的方法,vtable按照类中虚函数声明的顺序一一填入函数地址,派生类会继承基类的vtable(当然还会有其它可继承的成员),当我们在派生类中修改虚函数时,同时派生类中虚表中的内容也随之被修改,表中相应的元素已经不是基类的函数地址,而是派生类的函数地址
class CShape
{
public:
CShape():b1(1){};
void MyTest()
{
cout << "CShape::MyTest /n";
}
virtual void play()
{
cout << "CShape::play /n";
}
virtual void display()
{
cout <<b1<< "Shape /n";
}
int b1;
};
class CRect : public CShape
{
public:
CRect():b2(2){};
void MyTest()
{
cout << "CRect::MyTest /n";
}
void display()
{
cout <<b2<< "Rectangle /n";
}
int b2;
};
//--------------------------------------------
class CSquare : public CRect{public:
CSquare():b3(3){};
void MyTest()
{
cout << "CSquare::MyTest /n";
}
void display()
{
cout <<b3<< "Square /n";
}
int b3;};
void main(int argc, char* argv[])
{
CShape aShape;
CRect aRect;
CSquare aSquare;
CShape* pShape[3] = { &aShape,&aRect,&aSquare };
for (int i=0; i< 3; i++)
{
pShape[i]->display();
pShape[i]->MyTest();
}
}
/*
以下是上面那个程序(vTest.cpp)里for循环和循环体中的内存结构,代码的反汇编,和一些注释,我能证明的只有这些了
以下是栈:
0012FF4C> 00000000 ; int i; //(循环体内的定义);
0012FF50> 0012FF78 ; CShape* pShape[0]
0012FF54> 0012FF6C ; pShape[1]
0012FF58> 0012FF5C ; pShape[2]
0012FF5C> 00426064 ; CSquare aSquare;
0012FF60> 00000001 ; b1
0012FF64> 00000002 ; b2
0012FF68> 00000003 ; b3
0012FF6C> 00426048 ; CRect aRect;
0012FF70> 00000001 ; b1
0012FF74> 00000002 ; b2
0012FF78> 0042601C ; CShape aShape;
0012FF7C> 00000001 ; b1
以下是三个对象vtable的内容(前面是virtual void play()的地址,后面是virtual void display()的地址):
00426064> 37 10 40 00 50 10 40 00
00426048> 37 10 40 00 55 10 40 00
0042601C> 37 10 40 00 5F 10 40 00
以下是代码,for循环和循环体内的反汇编:
004010E9> JMP SHORT SHAPE.004010F4
004010EB> MOV EAX,DWORD PTR SS:[EBP-34]
004010EE> ADD EAX,1
004010F1> MOV DWORD PTR SS:[EBP-34],EAX ;i++
004010F4> CMP DWORD PTR SS:[EBP-34],3 ; 循环次数的控制,i<3
004010F8> JGE SHORT SHAPE.00401124
; 关键,寻址得到 &pShape
004010FA> MOV ECX,DWORD PTR SS:[EBP-34]
004010FD> MOV ECX,DWORD PTR SS:[EBP+ECX*4-30]
00401101> MOV EDX,DWORD PTR SS:[EBP-34]
00401104> MOV EAX,DWORD PTR SS:[EBP+EDX*4-30]
; 关键,得到函数表的首地址
00401108> MOV EDX,DWORD PTR DS:[EAX]
0040110A> MOV ESI,ESP
0040110C> CALL DWORD PTR DS:[EDX+4] ; 调用虚拟的成员函数
0040110F> CMP ESI,ESP
00401111> CALL SHAPE.__chkesp ; 收拾残局^_^
; 寻址得到 &pShape
00401116> MOV EAX,DWORD PTR SS:[EBP-34]
00401119> MOV ECX,DWORD PTR SS:[EBP+EAX*4-30]
0040111D> CALL SHAPE.00401073 ; 调用普通的成员函数
00401122> JMP SHORT SHAPE.004010EB
*/