zoukankan      html  css  js  c++  java
  • C++对象模型学习

    《深度探索C++对象模型》这本书看了2遍了,第一遍时很多东西懵懵懂懂,似懂非懂,在看时就比较清楚了。想着对比书上的理论,写点代码来验证一下,应该理解会更深刻些。这篇博客中先记录一下第三章的内容-data语意学。所有的代码都是在vs2008中编译。

    p84(1):一个空的class内存占用是一个字节,这样的两个空对象就能够在内存中各自分配一个独一无二额地址

    class CTst
    { };
    

    p84(2): 如下代码的大小是4个字节(ms vc 环境下)

    class CSecond : public virtual CTst
    {
    };
    

    这个CSecond的大小受3个因素的影响:

    • 语言本身所造成的额外负担 
    • 编译器对于特殊情况所做的优化处理
    • Alignment的限制  

    CSecond虚继承自CTst,为了支持virtual base,系统会在CSecond数据内分配一个4字节的内存来保存这种关系virtual base,后面会介绍到。Alignment的限制是系统一般都会对数据区进行取整。当我将CTst的定义改成如下时:

    class CTst
    {
    	char a;	 
    };
    

     CTst的长度是1, CSecond的长度是5, 这时可以看见 针对于这个类,编译器并没用进行Alignment方面的动作。然后我在加一个int的数据成员在CTst中时,此时CTSt的大小是8, CSecond的大小是12。很明显,系统对两个类的内存分配进行对齐的操作。

    p88(1): C++对象模型直接把数据存放在一个C++ object中,继承而来的nonstatic 数据也是一样。static的数据成员放在一个全局的数据区中。不过这个class产生多少对象,static的成员只有一份。但是template class的static成员稍有不同。

    p89: Argument list中的名称会在他们第一次遇到时被决议出来。

    typedef  char TLJW;
    
    class CTst
    {
    	char a;
    	TLJW abc;
    
    	typedef long long TLJW;
    };
    

      上面的代码中,abc的类型是char, 而不是 longlong型。

    p92: C++标准规定,同一个access section的数据成员,晚出现的成员在类对象中有较高的地址。不同的access section的数据成员可以自由排列。编译器还可能会生成一些内部使用的成员来支持对象模型,比如vptr。

    p98: 通过 char CTst::* abc 可以定义一个指向 CTst的char类型的数据成员的指针(偏移量),&CTst::a会返回a在CTst中的偏移量,如下代码,分别是定义了2个指向数据的指针,一个直接用类成员的地址进行复制,一个用0来复制,可通过反汇编出来的代码看出,系统会将用零赋值的语句转化为用0xffffffff来赋值,这是为了能够使 tst_mem和tst_zero进行比较时结果正确,区分出”指向class第一个member的指针“和“一个指向class的member的指针,没有指向任何member”。

    	char CTst::* tst_mem = & CTst::a ;
    0041357E  mov         dword ptr [tst_mem],0 
    	char CTst::* tst_zero = 0;
    00413585  mov         dword ptr [tst_zero],0FFFFFFFFh 
    

      每一个nonstatic的数据成员的偏移量(offset)可以再编译时就获知。因此存取一个nonstatic类成员和存取一个C struct成员的效率是一样的。

     p99: “从对象存取”和“从指针存取”有什么重大的差异?

    class CPoint;
    
    	CPoint origin;
    	CPoint* pt = &origin;
    	origin.x;
    	pt->x;
    

      当Point是一个derived class,而其继承结构中有一个virtual base class,并且存取的成员(x)是该virtual base class的成员时,问题中的两种存取方式有重大差异。因为我们不能够确定指针pt必然指向哪一种class类型(因此我们就不知道编译时期这个member真正的offset位置)。所以这个操作必须延迟至运行时,经过一个额外的间接导引才能解决。如果使用origin,就不会有这个问题。

    p100: 具体继承(相对于虚拟继承)并不会增加空间或者存取时间上的额外负担。

    p102: 把一个类分解为2层或者更多层,有可能会为了“表达class体系之抽象化”而膨胀所需要的空间。这个主要是出现在derived class中的base class部分尤其完整原样性。这个主要是子类中父类部分,在内存上要和一个父类的布局相同。

    p108: 具有多态的继承会带来空间和存取时间上的负担,主要有:

    • 导入一个virtual table,用来存放他所声明的每一个virtual function的地址。这个表格一般是声明的virtual function的数据, 在加上一个或者2个slot,用来支持 runtime type  identification。
    • 每个类对象都导入一个vptr,提供运行时的链接
    • 加强construct, 使他能够伟vptr设定初值,让他指向class所对应的virtual table。
    • 加强destructor, 使他能消除指向class相关的virtual table的信息。

     写了一个测试用程序,如下:

            class CH
    {
    public:
    int m_h;

    virtual void printName()
    {
    printf("name of CH\n");
    }
    };

    class CI : public CH
    {
    public:
    char m_c;
    };

    CH _ch;
    CI _ci;
    _ch.m_h = 12;
    _ci.m_c = '2';
    _ci.m_h = 11;
    CH * _pch = &_ch;
    CI *_pci = &_ci;

    printf("test of virtual table for single inherit\n");
    unsigned long * _pMemory = (unsigned long *)&_ch;
    printf("m0:%x, m1:%x, m2:%x, *m0:%x, %x\n", *_pMemory, *(_pMemory+1), *(_pMemory+2), *((unsigned long *)*(_pMemory)), *((unsigned long *)*(_pMemory)+1) );
    _pMemory = (unsigned long *)&_ci;
    printf("m0:%x, m1:%x, m2:%x, *m0:%x, %x\n", *_pMemory, *(_pMemory+1), *(_pMemory+2), *((unsigned long *)*(_pMemory)), *((unsigned long *)*(_pMemory)+1));

    printf("\n");

    输出结果是:

    test of virtual table for single inherit
    m0:41582c, m1:c, m2:cccccccc, *m0:4116b0, 0
    m0:415838, m1:b, m2:cccccc32, *m0:4116b0, 0
    

     这个将CH和CI类对象的内存数据输出出来分别是m0, m1, m2,同时,还把vptr(在对象的开始存放)所指向的内存输出了出来,输出了2个字节,是*m0。 从这个结果可以看出vc中,CH和CI对应虚表不一样,但是他们的表中的第一项都是指向同一个地址,也就是同一个函数。当我在CI中复写printName方法后,输入结果如下:

    test of virtual table for single inherit
    m0:415838, m1:c, m2:cccccccc, *m0:4116b0, 0
    m0:415740, m1:b, m2:cccccc32, *m0:411720, 0
    

      vptr中的第一项不一样了。

    以上是单一继承时的情况,对于多重继承,在以上代码的基础上,在增加如下代码

    		class CJ
    		{
    		public:
    			virtual void printAge()
    			{
    				printf("age from cj\n");
    			}
    		};
    
    		class CK : public CH, public CJ
    		{
    		public:
    			unsigned m_k;
    
    		};
    
    		CJ _cj;
    		CJ * _pcj = &_cj;
    		CK _ck;
    		_ck.m_h=2; _ck.m_k = 5;
    		CK * _pck = &_ck;
    
    
    		printf("test of virtual table for multiple inherit\n");
    		_pMemory = (unsigned long *)&_cj;
    		printf("CJ  \tm0:%x, m1:%x, m2:%x, *m0:%x, %x\n", *_pMemory, *(_pMemory+1), *(_pMemory+2), *((unsigned long *)*(_pMemory)), *((unsigned long *)*(_pMemory)+1) );
    		_pMemory = (unsigned long *)&_ck;
    		printf("CK  \tm0:%x, m1:%x, m2:%x, *m0:%x, %x\n", *_pMemory, *(_pMemory+1), *(_pMemory+2), *((unsigned long *)*(_pMemory)), *((unsigned long *)*(_pMemory)+1));
    

      输出结果如下:

    test of virtual table for single inherit
    m0:4157d0, m1:c, m2:cccccccc, *m0:4116b0, 0
    m0:4157f0, m1:b, m2:cccccc32, *m0:411720, 0
    
    test of virtual table for multiple inherit
    CJ      m0:41583c, m1:cccccccc, m2:cccccccc, *m0:411770, 416654
    CK      m0:4158b4, m1:2, m2:41584c, *m0:4116b0, 0
    

    多重继承时,内存的布局是先进行最先继承的(第一个父类)父类的布局,所以子类和该父类有相同的起始地址。然后依次是第二个,第三个父类的内存布局。存取第二个或者之后的父类中的数据成员时,不需要付出额外的代价,数据成员的位置在编译时就已经确定了。

    p117: class如果内含一个或者多个virtual base class subobjects,一般的实现方法是类被分割为两部分,一个不变局部和一个共享局部。不变局部的数据,不管后继如何演化,总是拥有固定的offset,所以这一部分数据可以被直接存取。至于共享局部,所表现的就是virtual base class subobject。这一部分的数据,其位置会因为每次的派生操作而有变化,所以他们只可以被间接存取。

    通过查看内存的方式,看了下在虚拟继承下,VC的内存布局。比较乱,也没有时间细看,所以就先没看,但是基本的原理了解了一些。将虚拟继承的含义抄在下面。

    虚继承的定义: 虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类(virtual base class)

    p129: 虚拟继承的效率令人失望,这主要是由于为了维护虚拟继承的结构,编译器一般会将一些成员决议操作放在运行时来进行间接访问。

    p130: &Point::z; 获取到z坐标在class object中的偏移量。 C++要求同一个access level 中的成员的排列次序应该和其声明顺序相同。可以参看p98页的笔记(本文上面)。

  • 相关阅读:
    HDU 1813 Escape from Tetris
    BZOJ 2276 Temperature
    BZOJ 4499 线性函数
    BZOJ 3131 淘金
    HDU 5738 Eureka
    POJ 2409 Let it Bead
    POJ 1286 Necklace of Beads
    POJ 1696 Space Ant
    Fox And Jumping
    Recover the String
  • 原文地址:https://www.cnblogs.com/kwliu/p/2405339.html
Copyright © 2011-2022 走看看