C++中虚函数深入剖析

 虚函数=虚拟函数    虚拟函数地址表=虚表(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

*/

 

 

你可能感兴趣的:(C++,c,汇编,Class,编译器)