爱技术 & 爱分享
爱蛋蛋 & 爱生活

对象的内存表示(简述)

在开始详细介绍之前,咱们先来看一下关于对象内存的简介

首先!需要多少内存才能表现一个 Class Object ?

这个问题简单的讲就是,一个对象在内存中的表现需要哪些部分组成?

答案是:

  • 其 nonstatic data members 的总和大小
  • 加上任何由于 alignment(内存对齐)的需求而填补(padding)上去的空间(可能存在于members之间,也可能存在于边界,亦或者都存在)
  • 加上为了支持virtual 而由内部产生的任何额外负担,顺便说一下,不同编译器可能采用不同的实现方法,所以所占的内存空间大小也会有些不同,主要是在虚继承,多继承上会有所差别。

可见,上面这几点并未包括,static members(静态数据成员)以及所有的 function ,这不是废话嘛,function 肯定是跟 object 分开的呀,并不需要任何存储开销在 object 上,不管是 static 还是 nonstatic (by the way,相比于 static function , nonstatic 不过是多传递一个 this 指针用于访问对象数据而已)。

指针的类型(The 指针的类型(The Type of a Pointer)

一个指针(或是一个reference。本质上一个reference 通常是以一个指针来实现的),不管它指向哪一种数据类型,指针本身所需的内存大小是固定的(一般为机器位数,即32位是 4字节,64位是8字节)。

现在请看一个问题,一个指向int的指针如何与一个指向char的指针有和不同?

其实以内存需求来说,没有什么不同!它们三个都需要满足有足够的内存来存放一个机器地址(通常为一个 word ,前面讲过)

“指向不同类型的指针”间的差异,既不在其指针表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的 object 类型不同。也就是说,“指针类型” 会教导编译器如何解释某个特定地址中的内存内容以及大小。

例: 一个指向地址1000的int指针,在32位机器上,将覆盖的是地址空间1000-1003(32位机器上是4 – bytes)

那么我们来考虑一个特别的指针,void* 指针。

一个指向地址1000而类型为 void* 的指针,将覆盖怎样的地址空间? 没猜错,我(lao)们(zi)很(bu)清(zhi)楚(dao)。这也是为什么一个类型为 void* 的指针只能够持有一个地址,而不能通过它操作所指向的object的缘故。

所以,指针的转换,其实是一种编译指令(这里考虑C风格的强制转换)。大部分情况下它不能改变一个指针所含的真正地址,只影响“被指出的内存的大小以及内容”的解释方式。

来我们换个话题:

//看下面的两个类
class Base
{
public:
    Base();
    ~Base();
    void Function1();
    virtual void Function2();
    int m_BaseMembers1;
};

class Drived:public Base
{
public:
    Drived();
    ~Drived();
    void Function2();
    int m_BaseMembers2;
};

当有一个基类指针 bPtr,和一个派生类指针 dPtr,同时指向派生类时。

Base* bPtr;
Drived* dPtr;
Drived drived;
bPtr=dPtr=&drived;

他们都指向Base Object的第一个byte。差别是bPtr所覆盖的地址是 Base Object 部分,而 dPtr 则覆盖整个 Drived Object 的部分。

类型覆盖

除了Base Class 中出现的members,你不能够使用 bPtr 来处理 Drived Class 的任何 members。当然聪明的老铁一定想到了一个例外,那就是 virtual 机制。

当我们使用一个user defined class的指针 ptr 时,在编译期将会确定以下两点

  • 固定的可用接口。也就是说,当前ptr所属类型能够调用的 public 接口。
  • 该接口的access level。

上面两点分别从所属类型和访问权限上来说的,看起来稍微有点怪。不过也可以总结成一句话“指针所属类型的可访问接口”。

现在,请看下面这种情况。

Drived drived;
Base base=drived;   //对象被 sliced
Base.Function2();   //调用Base Class 的Function2

为什么调用的是Base 的 Function2 而不是 Drived 的呢? 而且以子类对象来初始化父类对象,将一个 object 内容完整的拷贝到另一个 object 去,为什么 vptr 不指向 Base 的 virtual table?

第二个问题的答案是,编译器在(1)初始化和(2)赋值操作之间做出仲裁。编译器必须确保如果某个 object 含有一个及以上 vptrs ,那些 vptrs 的内容不会被 base class object 初始化或者改变

第一个问题的答案是,base 的类型始终是 Base 跟 Drived 毫无关系(发生sliced)。多态所造成的“一个以上的类型”的潜作用,并不能够实际发挥在“直接存取 objects ”这件事情上。

当一个 base class object 被直接初始化为一个 drived class object 时,drived object 就会被 sliced 以塞入到小的 base type内存中,drived type 将没有留下任何蛛丝马迹。多态将不再呈现,而一个严格的编译器可以在编译时期解析一个“通过此object而出发的virtual function 调用”,因而回避 virtual 机制。如果 virtual function 被定义为 inline ,则更有效率上的提升。简单来说呢,就是如果编译器够严格的话,就会解析出发生sliced ,virtual机制会被屏蔽。

所以C++多态支持方式就只剩下 pointer 和 reference 。

赞(0) 传送门
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。编程日志 » 对象的内存表示(简述)
分享到: 更多 (0)

游戏 && 后端

传送门传送门