C++对象模型学习
2012-03-22 22:43
357 查看
《深度探索C++对象模型》这本书看了2遍了,第一遍时很多东西懵懵懂懂,似懂非懂,在看时就比较清楚了。想着对比书上的理论,写点代码来验证一下,应该理解会更深刻些。这篇博客中先记录一下第三章的内容-data语意学。所有的代码都是在vs2008中编译。
p84(1):一个空的class内存占用是一个字节,这样的两个空对象就能够在内存中各自分配一个独一无二额地址
p84(2): 如下代码的大小是4个字节(ms vc 环境下)
这个CSecond的大小受3个因素的影响:
语言本身所造成的额外负担
编译器对于特殊情况所做的优化处理
Alignment的限制
CSecond虚继承自CTst,为了支持virtual base,系统会在CSecond数据内分配一个4字节的内存来保存这种关系virtual base,后面会介绍到。Alignment的限制是系统一般都会对数据区进行取整。当我将CTst的定义改成如下时:
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中的名称会在他们第一次遇到时被决议出来。
上面的代码中,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”。
每一个nonstatic的数据成员的偏移量(offset)可以再编译时就获知。因此存取一个nonstatic类成员和存取一个C struct成员的效率是一样的。
p99: “从对象存取”和“从指针存取”有什么重大的差异?
当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的信息。
写了一个测试用程序,如下:
输出结果是:
这个将CH和CI类对象的内存数据输出出来分别是m0, m1, m2,同时,还把vptr(在对象的开始存放)所指向的内存输出了出来,输出了2个字节,是*m0。 从这个结果可以看出vc中,CH和CI对应虚表不一样,但是他们的表中的第一项都是指向同一个地址,也就是同一个函数。当我在CI中复写printName方法后,输入结果如下:
vptr中的第一项不一样了。
以上是单一继承时的情况,对于多重继承,在以上代码的基础上,在增加如下代码
输出结果如下:
多重继承时,内存的布局是先进行最先继承的(第一个父类)父类的布局,所以子类和该父类有相同的起始地址。然后依次是第二个,第三个父类的内存布局。存取第二个或者之后的父类中的数据成员时,不需要付出额外的代价,数据成员的位置在编译时就已经确定了。
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页的笔记(本文上面)。
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页的笔记(本文上面)。
相关文章推荐
- C++对象模型学习笔记(二)--默认构造函数
- boolan——c++学习笔记之多态对象模型
- 深度探索C++对象模型学习笔记——Function语意学
- C++对象模型学习笔记
- 深度探索C++对象模型复习和学习 第七章:站在对象模型的尖端
- C++ 对象模型学习记录(3)--- 第1章 关于对象(未完)
- C++对象模型学习系列(一)
- C++对象模型学习笔记(一)
- 深度探索C++对象模型复习和学习 第二章:构造函数语义学(The Semantics of Constructors)
- 《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记
- C++对象模型学习笔记(二)--默认构造函数
- C++学习5 对象模型
- 《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记
- C++ 对象模型 学习笔记(1)
- C++学习之C++对象内存模型(上)
- 深度探索C++对象模型复习和学习 第三章 Data 语义学(The Semantics of Data )
- 《深度探索C++对象模型(Inside The C++ Object Model )》学习笔记
- 深度探索C++对象模型复习和学习 第五章:构造、析构、拷贝、语意学
- 深度探索C++对象模型复习和学习 第六章:执行期语意学
- C++对象模型学习——Data语意学