代码改变世界

C++对象模型

2025-05-08 21:58  一只老老老菜鸟  阅读(32)  评论(0)    收藏  举报

单个类对象:

在C++中,有两种 class data members:static 和 nonstatic,以及三种 classmember functions:static、nonsiatic 和 virtual.

已知下面这个 class Point 声明:

class Point {
public:
    Point() :_x(1.0) {}
    virtual ~Point() {}
    double x() const {}
    static int PointCount() {}
protected:
    virtual void p() const {}
    double _x;
    static int _point_conut;
};

 

Point pt;

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

Nonstatic data members 被配置于每一个 class object 之内,static data members 则被存放在所有的 class object 之外。Static 和 nonstatic function members 也被放在所有的 class object 之外,Virtual functions 则以两个步骤支持之:
1.每一个 class 产生出一堆指向 virtual functions 的指针,放在表格之中。这个表格被称为 virtual table(vtbl)
2.每一个 class object 被添加了一个指针,指向相关的 virtual table.通常这个指针被称为 vptr。vptr 的设定(setting)和重置(resetting)都由每一个 class 的 constructor、destructor 和 copy assignment 运算符自动完成,每一个class 所关联的 type_info object(用以支持 runtime type identification,RTTI)也经由 virtual table 被指出来,通常是放在表格的第一个 slot处。

 
成员类型存储位置对象内存占用共享性
non-static 成员变量 对象内存布局 ✔️ 对象独享
static 成员变量 全局/静态数据区 所有对象共享
non-static 成员函数 代码区 共享
static 成员函数 代码区 共享
virtual 成员函数 代码区(通过虚表访问) 共享

pt对象实例的内存布局(32位编译器)16byte:

偏移量内容大小(字节)
0 虚表指针(vptr) 4
4 对齐填充 4
8

double _x

8
16 (结束)  

需要多少内存才能够表现一个 class object?一般而言要有:
1.其 nonstatic data members 的总和大小;
2.加上任何由于 alignment(内存对齐)的需求而填补(padding)上去的空间(可能存在于 members之间,也可能存在于集合体边界)
3.加上为了支持 virtual 而由内部产生的任何额外负担(overhead)

继承:

在 C++ 继承模型中,一个 derived class object 所表现出来的东西,是其自己的members 加上其 base class(es)members 的总和。至于 derived class members和 base class(es)members 的列次序并未在 C++ Standard 中强制指定;理论上编译器可以自由安排之。在大部分编译器上头,base class members 总是先出现但属于 virtual base class 的除外(一般而言,任何一条规则一旦碰上 virtual baseclass 就没辄儿,这里亦不例外).

C++ 语言保证“出现在 derived class中的 base class subobject 有其完整原样性”.子类会完全继承父类的对象模型。

为什么子类会设计这种膨胀空间的方式呢?优点:父子类间copy很方便恰当,不会出错。否则会出现如下错误。

 

 

继承加多态:

程序会生成一个虚表(virtual table),用来存放它所声明的每一个virtual functions的地址,再加上一个slots(用以支持 runtimetype identification)

在每一个 class object 中导入一个 虚表指针vptr,指向虚表,提供执行期的链接,使每一个object 能够找到相应的 virtual table.

把 vptr 放在 class object 的前端,对于“在多重继承之下,通过指向 classmembers 的指针调用 virtual function”,会带来一些帮助。
否则,不仅“从 class object 起始点开始量起”的 ofset 必须在执行期备妥,甚至与 class vptr 之间的 offset 也必须备妥.

多重继承:

多重继承的问题主要发生于 derived ciass objects 和其第二或后继的 baseclass objects 之间的转换:不论是直接转换或是经由其所支持的 virtual function 机制做转换

对一个多重派生对象,将其地址指定给“最左端(也就是第一个)base class 的指针”,情况将和单一继承时相同,因为二者都指向相同的起始地址。需付出的成本只有地址的指定操作而已,至于第二个或后继的 base class 的地址指定操作,则需要将地址修改过:加上(或减去,如果downcast 的话)介于中间的 base class subobject(s)大小,例如:

 

虚拟继承:

暂略

 

引申:

C++不允许一个对象的大小为0,不同对象的地址不能具有相同的地址。

这是因为new需要分配不同的内存地址,不能分配内存大小为0的空间,避免除以sizeof(T)引发除零异常。

所以一个没有数据成员的空类,编译器会为其分配1字节的内存空间,即空类的大小为1。

空基类被继承后,如果派生类有自己的数据成员,那么空基类这1个字节不会添加到派生类中。

 

 

一个指向 Zoo4nimal 的指针是如何地与一个指向整数的指针或一个指向 template Array(如下,与一个 Sring 一并产生)的指针有所不同呢?
ZooAnimal *px;
int *pi;
Array< string > *pta;
以内存需求的观点来说,没有什么不同!它们三个都需要有足够的内存来放置一个机器地址.
“指向不同类型之各指针”间的差异,既不在其指针表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的 object 类型不同。
也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小

嗯,那么,一个指向地址1000而类型为void*的指针,将涵盖怎样的地址空间呢?
是的,我们不知道!这就是为什么一个类型为void*的指针只能够含有一个地址,而不能够通过它操作所指之 object 的缘故,
所以,转型(cast)其实是一种编译器指令。大部分情况下它并不改变一个指针所含的真正地址,它只影响“被指出之内存的大小和其内容”的解释方式,