C++对象的内存结构的实现
作者:bkspiderx
在C++中,对象的内存结构并非由语言标准强制规定具体细节(不同编译器可能有细微差异),但遵循一些通用规则。其内存布局主要受成员变量、虚函数、继承关系等因素影响,核心是为了高效存储数据和支持面向对象的核心特性(如封装、继承、多态)。
一、基础情况:无继承、无虚函数的对象
对于一个仅包含非静态成员变量的简单类,对象的内存结构本质上是成员变量的有序集合,不包含成员函数(成员函数存放在代码区,被所有对象共享)。
示例:
class Person {
private:
int age; // 4字节
char gender; // 1字节
double height; // 8字节
public:
void print() {} // 成员函数,不占对象内存
};
内存结构:
对象的内存中仅包含 age、gender、height 三个成员变量,按声明顺序排列,但会受到内存对齐影响(编译器为提高访问效率,会在成员间插入填充字节)。
假设按8字节对齐(常见编译器默认),内存布局可能为:
偏移量 内容
0-3 age(4字节)
4-7 填充(4字节,因gender仅1字节,需对齐到下一个8字节边界)
8-8 gender(1字节)
9-15 填充(7字节,为height的8字节对齐)
16-23 height(8字节)
总大小:24字节(而非4+1+8=13字节,填充字节是为了让CPU更高效地访问多字节类型)。
二、含静态成员的对象
静态成员(变量/函数)属于类级别的共享资源,不存储在对象的内存中,而是存放在全局数据区(或静态存储区),所有对象共享同一份。
示例:
class Person {
private:
int age; // 非静态成员,属于对象
static int count; // 静态成员,不属于对象
public:
static void incr() {} // 静态函数,不属于对象
};
int Person::count = 0; // 静态成员的定义
内存结构:
对象的内存中只包含非静态成员age,count和incr()存放在全局区,与对象内存无关。
三、含虚函数的对象(支持多态)
当类中包含虚函数(或继承了虚函数)时,对象会额外包含一个虚函数表指针(vptr),用于支持多态特性。
核心概念:
- 虚函数表(vtable):每个包含虚函数的类会有一个全局唯一的vtable,本质是一个函数指针数组,存储该类所有虚函数的地址。
- 虚函数表指针(vptr):每个对象都会包含一个vptr(通常在对象内存的开头),指向所属类的vtable。当通过基类指针调用虚函数时,编译器会通过vptr找到vtable,再调用实际的函数(实现动态绑定)。
示例:
class Animal {
public:
virtual void eat() {} // 虚函数
int weight; // 成员变量
};
内存结构:
偏移量 内容 0-7 vptr(8字节,指向Animal的vtable) 8-11 weight(4字节,int类型)
- vtable的内容:
[&Animal::eat](存储eat()函数的地址)。 - 若派生类重写了虚函数:Cat类的vtable会替换为
class Cat : public Animal { public: void eat() override {} // 重写虚函数 };[&Cat::eat],Cat对象的vptr指向该vtable,实现“基类指针调用派生类函数”的多态。
四、继承关系下的对象内存结构
继承会导致派生类对象包含基类子对象(基类的成员变量+可能的vptr),再加上派生类自己的成员变量。
1. 单继承(无虚函数覆盖)
class Base {
public:
int a;
};
class Derived : public Base {
public:
int b;
};
Derived对象的内存结构:
偏移量 内容
0-3 a(基类成员)
4-7 b(派生类成员)
2. 单继承(含虚函数)
class Base {
public:
virtual void f() {}
int a;
};
class Derived : public Base {
public:
virtual void g() {} // 新增虚函数
int b;
};
Derived对象的内存结构:
偏移量 内容
0-7 vptr(指向Derived的vtable)
8-11 a(基类成员)
12-15 b(派生类成员)
Derived的vtable内容:[&Base::f, &Derived::g](包含基类和派生类的虚函数)。
3. 多继承(最复杂的情况)
多继承时,派生类对象会包含所有基类的子对象,每个基类若有虚函数,可能对应一个vptr(导致对象内存中存在多个vptr)。
示例:
class Base1 {
public:
virtual void f() {}
int a;
};
class Base2 {
public:
virtual void g() {}
int b;
};
class Derived : public Base1, public Base2 {
public:
int c;
};
Derived对象的内存结构(可能):
偏移量 内容
0-7 vptr1(指向Derived的vtable1,对应Base1的虚函数)
8-11 a(Base1的成员)
12-19 vptr2(指向Derived的vtable2,对应Base2的虚函数)
20-23 b(Base2的成员)
24-27 c(Derived的成员)
- vtable1:
[&Derived::f](继承自Base1的虚函数) - vtable2:
[&Derived::g](继承自Base2的虚函数)
多继承可能导致指针调整:当用Base2*指向Derived对象时,指针会自动偏移到Base2子对象的起始位置(上例中偏移12字节)。
4. 虚继承(解决菱形继承问题)
菱形继承(两个派生类继承同一基类,再被一个类继承)会导致基类成员在最终派生类中存在多份副本(数据冗余+二义性)。虚继承通过让基类子对象只存一份解决此问题。
示例(菱形继承):
class A { public: int x; };
class B : virtual public A { public: int y; }; // 虚继承A
class C : virtual public A { public: int z; }; // 虚继承A
class D : public B, public C { public: int w; };
D对象的内存结构(简化):
偏移量 内容
0-7 vptr_B(B的虚表指针)
8-11 y(B的成员)
12-19 vptr_C(C的虚表指针)
20-23 z(C的成员)
24-27 w(D的成员)
28-31 x(A的成员,仅一份,被B和C共享)
- 虚继承通过在派生类中添加“虚基类指针”(或偏移量)定位共享的基类子对象,避免数据冗余。
总结
C++对象的内存结构核心由以下部分构成(按影响优先级):
- 非静态成员变量:按声明顺序排列,受内存对齐影响。
- 虚函数表指针(vptr):当类有虚函数时存在,用于多态,数量取决于继承关系(多继承可能有多个vptr)。
- 基类子对象:继承时包含,顺序与继承声明一致,虚继承会优化为共享副本。
静态成员、成员函数不占用对象内存,存放在全局区或代码区。不同编译器(如GCC、MSVC)在细节上(如vptr位置、对齐规则)可能有差异,但核心逻辑一致。
到此这篇关于C++对象的内存结构的实现的文章就介绍到这了,更多相关C++对象内存结构内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
