C++中类的大小与什么有关

本文仅讨论64位的机器

一. 类中数据成员

二. 内存对齐

大多数编译器会将类的大小对齐到其最大成员的对齐要求或者指针大小,可以见下面的例子

三. 继承相关

1.  虚函数

        https://blog.youkuaiyun.com/movefisher/article/details/146633397?spm=1011.2415.3001.10575&sharefrom=mp_manage_linkhttps://blog.youkuaiyun.com/movefisher/article/details/146633397?spm=1011.2415.3001.10575&sharefrom=mp_manage_link任何有虚函数的类都有一个指向虚函数表的指针 ,指针占有八字节

class Base {
    int value // 四字节
    virtual fun1() {}
}

该Base类的大小为16字节,value占四字节,vptr占八字节,根据内存对齐要求补足四字节,所以总共为16字节

 2. 继承

若是基类函数有虚函数,则派生类函数即使自己暂时没有虚函数,也要维护一个vptr,方便编译器查找要执行的函数。

class Base {
    int value;
    virtual void func1() {}
};

class Derived : public Base{
    int key;
};

Base类与Derived类大小都是16字节,Base是value四字节+vptr八字节+内存对齐补四字节,Derived是Base内存+key四字节20字节,但编译器会优化,让key去占用补齐的四字节,所以最后还是16字节

多重继承 派生类会拥有多个虚函数指针,例如:

class Base {
    int value;
    virtual void func1() {}
};

class Base2 {
    virtual void func2() {}
};

class Derived : public Base, public Base2{ // 4 + 4 + 8 + 8
    int key;
    virtual void func1() override {}
};

其实派生类自己并不真正拥有两个虚函数指针,而是继承了Base 和 Base2的虚函数指针

此处Base 的大小为16 Base2大小为8,Derived大小为32

对于基类:

  • Base 类: 16字节
    • 虚函数表指针 (vfptr) - 8字节
    • int value - 4字节
    • 填充 - 4字节
  • Base2 类: 8字节
    • 虚函数表指针 (vfptr) - 8字节

对于派生类 Derived

  1. Base 的部分: 16字节
    • Base的vfptr - 8字节 (虽然重写了func1,但vfptr本身并不会变)
    • int value - 4字节
    • 填充 - 4字节
  2. Base2 的部分: 8字节
    • Base2的vfptr - 8字节
  3. Derived 自己的成员: 8字节
    • int key - 4字节
    • 填充 - 4字节 (为了保持整体对齐)

 

3. 虚继承

class A {
public:
    int value; // 四字节
    virtual void funA() {}
};
    
class B : virtual public A { };  // 虚继承
class C : virtual public A { };  // 虚继承
class D : public B, public C { };  // 不再有歧义

A大小16 B、C大小24,D大小32

当使用虚继承时,编译器会为每个虚继承的派生类添加额外的数据结构,主要包括:

  1. 虚基类表指针 (vbptr, virtual base table pointer) - 通常是8字节(在64位系统上)
  2. 虚函数表指针 (vfptr, virtual function table pointer) - 通常是8字节(在64位系统上)
  3. 额外的偏移量信息和对象布局控制数据

所以B、C的内存布局大致为:

  • vfptr (8字节) - 来自A的虚函数表指针
  • vbptr (8字节) - 虚基类表指针,用于定位虚基类A
  • 可能的填充和对齐数据
  • 虚基类A的实例数据 (4字节,即int value)
  • 额外的内存对齐填充

不同编译器可能会有略微不同的内存布局实现,但总的来说,虚继承的开销主要来自这些额外的指针和控制结构。

具体到24字节,这可能是由于内存对齐要求和特定编译器实现的细节决定的。通常,对象大小会被对齐到某个字节边界(如8字节),这也会增加最终的对象大小。

这也是为什么在C++中,虚继承虽然解决了菱形继承问题,但也带来了额外的内存和性能开销,因此只在必要时才使用。

我使用的GCC编译器,内存布局其实与上面说的有差异,详见这篇文章

 

https://blog.youkuaiyun.com/Poo_Chai/article/details/100316040?fromshare=blogdetail&sharetype=blogdetail&sharerId=100316040&sharerefer=PC&sharesource=movefisher&sharefrom=from_link

 

B或C单独存在时的内存布局 (24字节)

当B或C作为独立对象时:

  1. 控制部分 (8字节):
    • 自己的虚函数表指针(vptr) - 8字节
  2. 虚基类表指针部分 (8字节):
    • 虚基类表指针(vbptr) - 8字节(在GCC中可能不直接可见,但功能上存在)
  3. 虚基类A的实例 (8字节):
    • A的虚函数表指针 - 不单独计入(通过虚基类机制访问)
    • int value - 4字节
    • 填充 - 4字节

总计:8 + 8 + 8 = 24字节

D类内存布局分析 (32字节)

在这个菱形继承结构中,D的内存布局如下:

  1. B子对象部分 (8字节):
    • B的虚函数表指针(vptr) - 8字节
    • B被标记为"nearly-empty",因为它不包含虚基类A的数据
  2. C子对象部分 (8字节):
    • C的虚函数表指针(vptr) - 8字节
    • C同样被标记为"nearly-empty"
  3. 共享的虚基类A (16字节):
    • A的虚函数表指针(vptr) - 8字节
    • int value - 4字节
    • 填充 - 4字节(为了保证对齐)

总计: 8 + 8 + 16 = 32字节

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值