• 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 函数中,原因估计是作为类中静态变量,同时也是全局变量,必须时刻都在内存中

    由于水平有限,上面部分为推测,如有错误欢迎留言纠正

  • 相关阅读:
    通过图像识别技术监控屏幕画面卡死问题
    人生四问
    bring to front 必须在右边的form上才生效。
    ADO 右键问题,当dbgrideh非只读的时候,会遇到这个问题
    K-Means聚类算法
    BIRCH算法---使用聚类特征树的多阶段算法
    BIRCH算法---使用聚类特征树的多阶段算法
    分布式系统阅读笔记(二十二)-----时钟和时钟同步
    分布式系统阅读笔记(二十二)-----时钟和时钟同步
    分布式系统阅读笔记(二十二)-----时钟和时钟同步
  • 原文地址:https://www.cnblogs.com/TaigaCon/p/2591039.html
Copyright © 2020-2023  润新知