interface——关于虚函数的内存分配
之前学C++的时候了解到,其中很多控件是基于com的,今天初学接口,写了一小段测试程序
接口为IX,IY
组件为CA,(继承于IX和IY)
客户为main函数
流程如下:
接口连接组件的功能是由虚函数实现的
一般来说,父类作为接口,子类作为组件,子类实现父类的功能(组件实现接口所要求的功能)
虚函数就是实现这一过程的方法
对于虚函数在内存中的分配,下面是个小测试程序:
1 #include<iostream> 2 using namespace std; 3 class CA 4 { 5 public: 6 int i; 7 virtual void __stdcall virtualFun()=0; 8 }; 9 10 class CASub:public CA 11 { 12 virtual void __stdcall virtualFun() 13 { 14 return; 15 } 16 }; 17 18 class CB 19 { 20 public: 21 int i; 22 }; 23 24 void main() 25 { 26 CASub *pA = new CASub; 27 CB *pB = new CB; 28 cout<<"pA Address:"<<pA<<endl; 29 cout<<"pA int address"<<(&pA->i)<<endl; 30 cout<<"Size of pA:"<<sizeof(*pA)<<endl<<endl; 31 cout<<"pB Address:"<<pB<<endl; 32 cout<<"pB int Address:"<<&(pB->i)<<endl; 33 cout<<"Size of pB:"<<sizeof(*pB)<<endl; 34 }
运行结果如下图
由此可见pA(由CASub产生,CASub又是由CA继承而来)的长度为8个字节,pA中有虚函数和int类型
pB(由CB产生)的长度为4个字节,其中pB中只有int类型,因此知道虚函数占用的是4个字节,
不过虚函数中并没有数据,因此该4个字节应该是虚函数的地址,
另外,程序中虚函数是写在int后的,而到了内存中虚函数却在前面,由此可以判定是编译器故意为之,为了提高虚函数调用的速度。
然后我们来看一下接口地址和组件地址的关系,
组件是CA,接口为IX和IY,
测试代码如下
1 #include<iostream> 2 #include<ObjBase.h> 3 using namespace std; 4 void trace(const char* pMsg) 5 { 6 cout<<pMsg<<endl; 7 } 8 9 interface IX 10 { 11 virtual void __stdcall Fx1()=0; 12 virtual void __stdcall Fx2()=0; 13 14 }; 15 16 interface IY 17 { 18 virtual void __stdcall Fy1()=0; 19 virtual void __stdcall Fy2()=0; 20 }; 21 22 class CA : public IX, 23 public IY 24 { 25 public : 26 virtual void __stdcall Fx1(){cout<<"CA::Fx1"<<endl;} 27 virtual void __stdcall Fx2(){cout<<"CA::Fx2"<<endl;} 28 29 virtual void __stdcall Fy1(){cout<<"CA::Fy1"<<endl;} 30 virtual void __stdcall Fy2(){cout<<"CA::Fy2"<<endl;} 31 }; 32 33 void main() 34 { 35 36 37 trace("Client:Create an instance of the component."); 38 CA* pA =new CA; 39 40 IX* pIX=pA; 41 trace("Client:Use the IX interface."); 42 pIX->Fx1(); 43 pIX->Fx2(); 44 45 IY* pIY=pA; 46 trace("Client:Use the IY interface."); 47 pIY->Fy1(); 48 pIY->Fy2(); 49 50 51 cout<<endl; 52 cout<<"pA Address:"<<pA<<endl; 53 cout<<"pIX Address:"<<pIX<<endl; 54 cout<<"pIY Address:"<<pIY<<endl; 55 cout<<"Size of pA:"<<sizeof(*pA)<<endl; 56 cout<<"Size of pIX:"<<sizeof(*pIX)<<endl; 57 cout<<"Size of pIY:"<<sizeof(*pIY)<<endl; 58 59 trace("Client :Delete the component"); 60 delete pA; 61 return ; 62 }
运行结果
CA继承于IX和IY,由运行结果可以看出pA地址跟pIX一致,这也是继承的特性,构造CA的时候会先构造父类IX,然后构造IY;
组件CA在内存中的大小为8字节,接口各4个字节,而接口作为内部有虚函数的类,虚函数地址就占了4个字节,并且每个接口有两个函数,
据书上的说法,虚函数的实现是,内存中首个4BYTE包含的是指向虚函数表的指针,然后再在表中包含指向实现该虚函数的实际内存地址,如下图:
注:接口的内存结构对于不同的操作系统可能是不同的,例如在mac上,vtbl指针将指向一个伪指针,而第一个函数指针则位于第二个表项中
假设子类不含有虚函数Fx1,那么就会把父类的虚函数的入口地址写到子类虚函数表的Fx1处,如果子类存在虚函数Fx1,那么该处就填子类Fx1的入口地址,
对于所有不含有Fx1的子类,而父类有Fx1,那么所有子类将共用该函数的代码段
在学习过程中,本人发现了类中的静态数据类型(static)必须在外部重新定义一遍
如:
class A{public: static int i;};
手动定义一遍,int A::i=0;这个时候才会进行 i 的内存分配
另外还有一个问题是上面的这条int A::i=0;无法放在main 函数中,原因估计是作为类中静态变量,同时也是全局变量,必须时刻都在内存中
由于水平有限,上面部分为推测,如有错误欢迎留言纠正