c++类在内存中的布局

内存神奇又神秘,让我们一探究竟吧!
VS打开工程,右键工程名称—>属性—>c/c++—>命令行—> /d1 reportAllClassLayout

空类

class emptyClass{};

编译该类发现空类大小为1字节,如下图所示:
空类大小为1
为什么空类大小为1字节:因为c++有规定,任何不同的对象都应该有不同的地址。空类虽然没有内容,但是仍旧需要分配地址。

基础类

class commonClass
{
    int i;
    char c;
};

该类占用8个字节(四字节对齐,所以需要八个字节),如下图所示:
这里写图片描述

带虚函数的类

class commonClass
{
    int i;
    char c;
    virtual void virt_fun1(){}
    virtual void virt_fun2(){}
    void comm_fun1(){}
    void comm_fun2(){}
};

该类占用12个字节,如下图所示:
这里写图片描述
四字节对其2个变量共占用8个字节,由于存在虚函数,所以在类开始位置插入了一个虚函数指针,该指针占用4个字节,共计12字节。类的非虚函数其实不占用类对象的内存(函数编译后形成二进制文件放在内存中的代码段区)

简单继承

class baseClass
{
    int i;
    char c;
};

class inheritClass : public baseClass
{
    char c;
};

inheritClass占用12个字节,并且内存中基类在派生类的前面。从内存角度来看,这也是类向上转换的基础。
这里写图片描述

基类带有虚函数

派生类和基类存在同名的虚函数。由于派生类实现了基类的虚函数,所以即使是向上转换,也会调用派生类的虚函数

#include "iostream"
using namespace std;

class baseClass
{
private:
    int i;
    char c;
public:
    virtual void virt_fun1(){ cout << "baseClass virt_fun1" << endl; }
    virtual void virt_fun2(){ cout << "baseClass virt_fun2" << endl; }
    void comm_fun1(){ cout << "baseClass comm_fun1" << endl; }
    void comm_fun2(){ cout << "baseClass comm_fun2" << endl; }
};

class inheritClass : public baseClass
{
private:
    char c;
public:
    virtual void virt_fun1(){ cout << "inheritClass virt_fun1" << endl; }
    virtual void virt_fun3(){ cout << "inheritClass virt_fun2" << endl; }
    void comm_fun1(){ cout << "inheritClass comm_fun1" << endl; }
    void comm_fun3(){ cout << "inheritClass comm_fun3" << endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    inheritClass a;
    a.virt_fun1();

    baseClass *b = new inheritClass();
    b->virt_fun1();
    b->comm_fun1();

    inheritClass *c = (inheritClass*)b;
    c->virt_fun1();
    c->comm_fun1();
    c->comm_fun2();
    c->comm_fun3();

    return 0;
}

b->virt_fun1()调用派生类的虚函数,程序输出如下:
这里写图片描述
派生类在内存中占用16字节,内存分布如下:
这里写图片描述
从内存分布中可以看到:
1:非虚继承时基类在派生类的前面,故可以向上转换,同时也可将向上转换后的指针强制转换为派生类指针。
2:基类和派生类使用同一个虚函数表。虚函数表的排列顺序遵循先基类后派生类,由于基类和派生类存在同名的虚函数(virt_fun1),所以派生类的虚函数覆盖了基类的同名虚函数从而排到了最前面。
3:

虚继承

#include "iostream"
using namespace std;

class baseClass
{
private:
    int i;
    char c;
public:
    void virt_fun1(){ cout << "baseClass virt_fun1" << endl; }
    void virt_fun2(){ cout << "baseClass virt_fun2" << endl; }
    void comm_fun1(){ cout << "baseClass comm_fun1" << endl; }
    void comm_fun2(){ cout << "baseClass comm_fun2" << endl; }
};

class inheritClass : public virtual baseClass
{
private:
    char c;
public:
    void virt_fun1(){ cout << "inheritClass virt_fun1" << endl; }
    void virt_fun3(){ cout << "inheritClass virt_fun2" << endl; }
    void comm_fun1(){ cout << "inheritClass comm_fun1" << endl; }
    void comm_fun3(){ cout << "inheritClass comm_fun3" << endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    inheritClass a;
    a.virt_fun1();

    baseClass *b = new inheritClass();
    b->virt_fun1();
    b->comm_fun1();

    // inheritClass *c = (inheritClass*)b; 无法直接向上转换

    inheritClass *c = (inheritClass*)((char*)b - 8);
    c->virt_fun1();
    c->comm_fun1();
    c->comm_fun2();
    c->comm_fun3();
    return 0;
}

程序输出:
这里写图片描述
派生类在内存中占用16字节,内存分布如下:
这里写图片描述
从内存分布可以看出:
1:由于基类和派生类都没有虚函数,当派生类虚继承基类时,派生类的内存中增加了一个虚函数表
2:派生类在内存中的位置排在了基类的前面,所以可以向上转换,但是向上转换后的指针无法再强制类型转换为派生类指针。(由于基类在内存中的地址和派生类的地址不同,所以无法直接强制类型转换)
3:由于向上转换后,基类地址比派生类地址多了8个字节,所以可以通过将基类地址减去8的方法找到派生类地址

带虚函数的虚继承

#include "iostream"
using namespace std;

class baseClass
{
private:
    int i;
    char c;
public:
    virtual void virt_fun1(){ cout << "baseClass virt_fun1" << endl; }
    virtual void virt_fun2(){ cout << "baseClass virt_fun2" << endl; }
    void comm_fun1(){ cout << "baseClass comm_fun1" << endl; }
    void comm_fun2(){ cout << "baseClass comm_fun2" << endl; }
};

class inheritClass : public virtual baseClass
{
private:
    char c;
public:
    virtual void virt_fun1(){ cout << "inheritClass virt_fun1" << endl; }
    virtual void virt_fun3(){ cout << "inheritClass virt_fun2" << endl; }
    void comm_fun1(){ cout << "inheritClass comm_fun1" << endl; }
    void comm_fun3(){ cout << "inheritClass comm_fun3" << endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    inheritClass a;
    a.virt_fun1();

    baseClass *b = new inheritClass();
    b->virt_fun1();
    b->comm_fun1();

    // inheritClass *c = (inheritClass*)b; 

    inheritClass *c = (inheritClass*)((char*)b - 12);
    c->virt_fun1();
    c->comm_fun1();
    c->comm_fun2();
    c->comm_fun3();

    return 0;
}

程序输出:
这里写图片描述
派生类在内存中占用24字节,内存分布如下:
这里写图片描述
从内存分布中可以看出:
1:基类和派生类都有独立虚函数表vbptr,由于是虚继承,所以此时派生类增加一个vfptr的指针
2:虚继承时派生类的地址和基类地址不同,基类地址高于派生类地址
3:虚继承依旧支持向上转换。但是向上转换后的指针不能直接强制类型转换为派生类的指针
4:可以通过地址偏移将向上转换后的基类指针强制类型转换为派生类指针

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值