首页 > 代码库 > 《深度探索c++对象模型》chapter1关于对象对象模型

《深度探索c++对象模型》chapter1关于对象对象模型

在c++中,有2种class data member:static和nostatic,以及3钟class member function:static,nostatic和virtual。已知下面这个class Point声明:

class Point
{
public:
    Point(float xval);
    virtual ~Point();

    float x() const;
    static int PointCount();

protected:
    virtual ostream& print(ostream& os) const;

    float _x;
    static int _point_count;
};

这个class Point在机器中将会怎样被表现呢?也就是说,我们如何建模(modeling)出各种data member设function members呢?

1.简单对象模型(a simple object model)

简单对象模型:一个C++对象存储了所有指向成员的指针,而成员本身不存储在对象中。也 就是说不论数据成员还是成员函数,也不论这个是普通成员函数还是虚函数,它们都存储 在对象本身之外,同时对象保存指向它们的指针。

 

在这个模型中,member并不放在object中,只有”指向mmeber的指向“才放在object之内,这么做可以避免”members有不同的类型,因而需要不同的存储空间“所招致的问题。object中member是以slot的索引值来寻址,本例中_x的索引值是6,—point_count是7.一个class object的大小很容易计算出来,”指针代销,乘以class中所声明的members数目“便是。

 

2.表格驱动对象模型(a table-driver object model)

为了对所有class的所有object都有一致的表达方式,另一种对象模型是把所有与members相关的信息抽出来,放在一个data member table和一个member function table之中。class object本身含义指向这2个表格的指针。member function table是一系列的slots,每个slot支持一个member function,data member table则直接含有data本身。

 

3.c++对象模型(the c++ object model)。

如下图:class ojbect含有一个vptr,指向vtbl,vtbl第一个一般是type_info object。

这个模型主要优点是在于它的空间和存取时间的效率,主要缺点是:如果应用程序代码本身未曾改变,但所用到的class object的non

static data member有所修改(可能增删改)。那么那些应用程序代码同样得重新编译。关于这一点,前面的双表格模型提供了较大的弹性,因为他多提供了一层间接性,不过它也因此付出空间和执行效率两方面的代价就是了。

下图给出VS2010中class Point的内存布局:

可以发现,
 (1)float _x;被放在class Point之内。
(2) static int _point_count; Point (float xval); float x() const;static int PointCount();不在class Point之内,即放在所有class object外面。 
(3)virtual table有两项,表示 virtual ~Point();virtual ostream& print(ostream &os) const;
(4)VS编译器添加了指向相关virtual table的指针vfptr放在class object中,位置在data members的前面。
在“Microsoft Visual C++”的编译环境中,我们可以利用编译器“cl”、链接器“link”、可执行文件查看器“dumpbin”来查看Windows下可执行文件(COFF格式)的变量、函数怎么存储。
(
    “cl”即Visual C++ 的编译器,即“Compiler”的缩写。在Visual Studio 2010安装完后,会有一个批处理文件用来建立运行这些工具所需要的环境。它位于开始/程序/Microsoft Visual Studio 2010/Visual Studio Tools/Viusual Studio 2010 Command Prompt,这样我们就可以利用命令行使用VC++的编译器了。
      在“cl”编译器中有个编译选项可以查看C++类的内存布局,使用如下:打开Visual Studio的命令行提示符即Viusual Studio 2010 Command Prompt,cd到文件目录下后,按如下格式输入:
>cl [name.cpp] /d1reportSingleClassLayout[classname]
)
 

加上继承

(单一继承、多重继承、虚继承[注])
      设想一下两种可能的模型:
(1)简单对象模型,derived class object内的一个slot指出每一个base class (该slot内含base class subobject的地址)。优点是class object的大小不会因base classes的改变而受到影响。缺点是因为间接性而导致空间和存取时间上的额外负担。
(2)base table模型:很像virtual table 内含每一个virtual function的地址,base class table的每一个slot含有一个相关的base class地址。每一个class object内含有一个bptr,被初始化后指向其base class table。 
      不管采用那种模型,“间接性”的级数都将因为继承的深度而增加。

单一继承、多重继承:

      C++最初采用的继承模型并不运用任何间接性:即base class subobject的data members被直接放在derived class object中。这(直接复制模型)提供了对base class 最紧凑而最有效率的存取。缺点是base class members的任何改变后,所有用到此base class或其derived class的objects者必须重新编译。

虚继承:

      那么对于C++2.0新加入的virtual base class呢?
      需要一些间接性的base class表现方法。其原始模型是在class object中为每一个有关联的virtual base class 加上一个指针。其他演化的模型则若不是导入一个virtual base class table,就是扩充原来已经存在的virtual table,以便维护每一个virtual base class的位置。
总结:
      对于单一继承、多重继承采用直接复制模型,对于虚继承则(在直接复制模型的基础上)选择两种间接性模型之一
      那么VS编译器选择哪种继承模型呢?
      答案是:virtual base class table模型,具体分析见下一篇
注:
      在虚继承的情况下,base class不管在继承串链中被派生多少次,永远只会存在一个实体(subobject)
 
 

一个类的对象的内存大小包括:

  • 所有非静态数据成员的大小。nonstatic data member
  • 由内存对齐而填补的内存大小。(任何由于alignment的需求而填补padding上去的空间,可能存在于members之间,也可能存在于集合体边界)
  • 为了支持virtual有内部产生的额外负担。(overhead)
class ZooAnimal {  
public:  
   ZooAnimal();  
   virtual ~ZooAnimal();  
   virtual void rotate();  
protected:  
   int loc;  
   String name;  
};

在32位计算机上所占内存为16字节:int四字节,String8字节(一个表示长度的整形,一个 指向字符串的指针),以及一个指向虚函数表的指针vptr。对于继承类则为基类的内存大小 加上本身数据成员的大小。在cfront中其内存布局如下图:

 

指针的类型

指针的大小都是4字节,表示一个地址。指针的类型并不影响指针的大小,只是用来告诉编译器如何解释这个地址开始的一块内存内容及其大小。

 

C++中语法上的多态要用指针或者引用。一个指针(pointer)或者引用(reference)之所以支持多态,是因为他们并不引发内存中任何“与类型有关的内存委托操作(type-dependent commitment)”。会受到改变的只是它们所指向的内存的“大小和内容解释方式”而已。