C++类对象在内存中的布局

目录

一、前言

二、C++ 类对象的内存布局

2.1 只有数据成员的对象

2.2 没有虚函数的对象

2.3 拥有仅一个虚函数的类对象

2.4 拥有多个虚函数的类对象

三、继承关系中的C++类对象内存分布

3.1 存在继承关系且本身不存在虚函数的派生类的内存布局

​3.2  本身不存在虚函数(不严谨)但存在基类虚函数覆盖的单继承类的内存布局

3.3 定义了基类没有的虚函数的单继承的类对象布局

3.4 多继承且存在虚函数覆盖同时又存在自身定义的虚函数的类对象布局

3.5 如果第1个直接基类没有虚函数

3.6 两个基类都没有虚函数表

​3.7 如果有三个基类: 虚函数表分别是有, 没有, 有!

四、C++中父子对象指针间的转换与函数调用

一、前言

大家都应该知道C++的精髓是虚函数吧? 虚函数带来的好处就是:可以定义一个基类的指针,其指向一个继承类,当通过基类的指针去调用函数时,可以在运行时决定该调用基类的函数还是继承类的函数。虚函数是实现多态(动态绑定)/接口函数的基础。可以说: 没有虚函数, C++将变得一无是处!

二、C++ 类对象的内存布局

要想知道C++对象的内存布局, 可以有多种方式, 比如:

  1. 输出成员变量的偏移, 通过offsetof宏来得到
  2. 通过调试器查看, 比如常用的VS

2.1 只有数据成员的对象

类实现如下:

class Base1
{
public:
    int base1_1;
    int base1_2;
};

对象大小及偏移:

sizeof(Base1) 8
offsetof(Base1, base1_1) 0
offsetof(Base1, base1_2) 4

可知对象布局:

 可以看到, 成员变量是按照定义的顺序来保存的, 最先声明的在最上边, 然后依次保存!
类对象的大小就是所有成员变量大小之和.

2.2 没有虚函数的对象

类实现如下:

class Base1
{
public:
    int base1_1;
    int base1_2;

    void foo(){}
};

结果如下:

sizeof(Base1) 8
offsetof(Base1, base1_1) 0
offsetof(Base1, base1_2) 4

和前面的结果是一样。
因为如果一个函数不是虚函数,那么他就不可能会发生动态绑定,也就不会对对象的布局造成任何影响。
当调用一个非虚函数时,那么调用的一定就是当前指针类型拥有的那个成员函数。这种调用机制在编译时期就确定下来了。

2.3 拥有仅一个虚函数的类对象

类实现如下:

class Base1
{
public:
    int base1_1;
    int base1_2;

    virtual void base1_fun1() {}
};

结果如下:

sizeof(Base1) 12
offsetof(Base1, base1_1) 4
offsetof(Base1, base1_2) 8

从图可以看出多了4个字节,且 base1_1 和 base1_2 的偏移都各自向后多了4个字节!
说明类对象的最前面被多加了4个字节的内容。
定义

Base1 b1;

我们通过VS来瞧瞧类Base1的变量b1的内存布局情况:
(由于我没有写构造函数, 所以变量的数据没有根据, 但虚函数是编译器为我们构造的, 数据正确!)
(Debug模式下, 未初始化的变量值为0xCCCCCCCC, 即:-858983460)

看到没? base1_1前面多了一个变量 __vfptr(常说的虚函数表vtable指针), 其类型为void**, 这说明它是一个void*指针(注意:不是数组).

再看看[0]元素, 其类型为void*, 其值为 ConsoleApplication2.exe!Base1::base1_fun1(void), 这是什么意思呢? 如果对WinDbg比较熟悉, 那么应该知道这是一种惯用表示手法, 她就是指 Base1::base1_fun1() 函数的地址。

可得, __vfptr的定义伪代码大概如下:

void*   __fun[1] = { &Base1::base1_fun1 };
const void**  __vfptr = &__fun[0];

值得注意的是:

上面只是一种伪代码方式, 语法不一定能通过

该类的对象大小为12个字节, 大小及偏移信息如下:

sizeof(Base1) 12
offsetof(__vfptr) 0
offsetof(base1_1) 4
offsetof(base1_2) 8

大家有没有留意这个__vfptr? 为什么它被定义成一个指向指针数组的指针, 而不是直接定义成一个指针数组呢?

我为什么要提这样一个问题? 因为如果仅是一个指针的情况, 您就无法轻易地修改那个数组里面的内容, 因为她并不属于类对象的一部分。
属于类对象的, 仅是一个指向虚函数表的一个指针__vfptr而已, 下一节我们将继续讨论这个问题。

注意到__vfptr前面的const修饰。她修饰的是那个虚函数表, 而不是__vfptr。

现在的对象布局如下:

虚函数指针__vfptr位于所有的成员变量之前定义.

注意到: 我并未在此说明__vfptr的具体指向, 只是说明了现在类对象的布局情况.
接下来看一个稍微复杂一点的情况, 我将清楚地描述虚函数表的构成。

2.4 拥有多个虚函数的类对象

和前面一个例子差不多, 只是再加了一个虚函数. 定义如下:

class Base1
{
public:
    int base1_1;
    int base1_2;

    virtual void base1_fun1() {}
    virtual void base1_fun2() {}
};

 大小以及偏移信息如下:

有情况!? 多了一个虚函数, 类对象大小却依然是12个字节!

再来看看VS形象的表现:

呀, __vfptr所指向的函数指针数组中出现了第2个元素, 其值为Base1类的第2个虚函数base1_fun2()的函数地址.

现在, 虚函数指针以及虚函数表的伪定义大概如下:

void* _
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值