本文仅讨论64位的机器
一. 类中数据成员
二. 内存对齐
大多数编译器会将类的大小对齐到其最大成员的对齐要求或者指针大小,可以见下面的例子
三. 继承相关
1. 虚函数
https://blog.youkuaiyun.com/movefisher/article/details/146633397?spm=1011.2415.3001.10575&sharefrom=mp_manage_link
https://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:
Base的部分: 16字节- Base的vfptr - 8字节 (虽然重写了func1,但vfptr本身并不会变)
- int value - 4字节
- 填充 - 4字节
Base2的部分: 8字节- Base2的vfptr - 8字节
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
当使用虚继承时,编译器会为每个虚继承的派生类添加额外的数据结构,主要包括:
- 虚基类表指针 (vbptr, virtual base table pointer) - 通常是8字节(在64位系统上)
- 虚函数表指针 (vfptr, virtual function table pointer) - 通常是8字节(在64位系统上)
- 额外的偏移量信息和对象布局控制数据
所以B、C的内存布局大致为:
- vfptr (8字节) - 来自A的虚函数表指针
- vbptr (8字节) - 虚基类表指针,用于定位虚基类A
- 可能的填充和对齐数据
- 虚基类A的实例数据 (4字节,即int value)
- 额外的内存对齐填充
不同编译器可能会有略微不同的内存布局实现,但总的来说,虚继承的开销主要来自这些额外的指针和控制结构。
具体到24字节,这可能是由于内存对齐要求和特定编译器实现的细节决定的。通常,对象大小会被对齐到某个字节边界(如8字节),这也会增加最终的对象大小。
这也是为什么在C++中,虚继承虽然解决了菱形继承问题,但也带来了额外的内存和性能开销,因此只在必要时才使用。
我使用的GCC编译器,内存布局其实与上面说的有差异,详见这篇文章
B或C单独存在时的内存布局 (24字节)
当B或C作为独立对象时:
- 控制部分 (8字节):
- 自己的虚函数表指针(vptr) - 8字节
- 虚基类表指针部分 (8字节):
- 虚基类表指针(vbptr) - 8字节(在GCC中可能不直接可见,但功能上存在)
- 虚基类A的实例 (8字节):
- A的虚函数表指针 - 不单独计入(通过虚基类机制访问)
- int value - 4字节
- 填充 - 4字节
总计:8 + 8 + 8 = 24字节
D类内存布局分析 (32字节)
在这个菱形继承结构中,D的内存布局如下:
- B子对象部分 (8字节):
- B的虚函数表指针(vptr) - 8字节
- B被标记为"nearly-empty",因为它不包含虚基类A的数据
- C子对象部分 (8字节):
- C的虚函数表指针(vptr) - 8字节
- C同样被标记为"nearly-empty"
- 共享的虚基类A (16字节):
- A的虚函数表指针(vptr) - 8字节
- int value - 4字节
- 填充 - 4字节(为了保证对齐)
总计: 8 + 8 + 16 = 32字节


被折叠的 条评论
为什么被折叠?



