• 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 5501
    CF #324 DIV2 E题
    CF #324 DIV2 C题
    利用位操作实现加减运算(不用+ -号)
    【Leetcode】120. 三角形最小路径和
    删除排序链表中的重复元素
    【python】二分查找
    如何在不添加新数组的情况下移除元素?
    三/四 数之和,双指针法,细节很多
    【转】字符串相关操作
  • 原文地址:https://www.cnblogs.com/kwliu/p/2405339.html
Copyright © 2020-2023  润新知