对象内存布局
C++中,类对象的内存布局是一个非常基础但重要的概念,尤其在深入理解对象模型、性能优化和调试时具有很大意义。类对象的内存布局主要包括以下几个部分:
- 非静态数据成员:类中的普通成员变量。
- 继承相关数据:包括基类的成员和虚基类指针。
- 虚函数表指针(vptr):用于支持动态多态性。
- 其他可能的内存填充:对齐填充、编译器特性等。
# 一、基本内存布局
# 1. 普通类
对于一个普通的、不使用继承、不含虚函数的类,其内存布局通常只包含非静态数据成员,按照声明顺序依次排列。
class Simple {
public:
int a;
double b;
char c;
};
int main() {
Simple obj;
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
内存布局:
a
: 4 bytes (int)b
: 8 bytes (double)c
: 1 byte (char)- 可能存在的对齐填充:为了优化内存访问速度,编译器可能会在
c
之后增加3字节的填充,使整个对象对齐到8字节边界。
# 二、内存对齐
- 内存对齐:编译器可能会在成员变量之间插入额外的字节(填充字节),以确保每个成员变量都能被高效访问。这些填充字节不一定是显式的,但它们影响对象的内存大小。
class Align {
public:
char c; // 1 byte
int i; // 4 bytes
double d; // 8 bytes
};
int main() {
Align obj;
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
内存布局:
c
: 1 byte (char)填充
: 3 bytes (为了使i
对齐到4字节边界)i
: 4 bytes (int)d
: 8 bytes (double)
# 三、继承下的内存布局
# 1. 单继承
在单继承的情况下,派生类对象的内存布局包括基类的成员以及派生类自身的成员。基类的成员会按照基类声明的顺序首先被存储,然后是派生类的成员。
class Base {
public:
int x;
};
class Derived : public Base {
public:
int y;
};
int main() {
Derived obj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
内存布局:
Base::x
: 4 bytes (int)Derived::y
: 4 bytes (int)
# 2. 多重继承
在多重继承的情况下,派生类对象的内存布局包含所有基类的成员,按继承顺序依次排列。
class Base1 {
public:
int a;
};
class Base2 {
public:
double b;
};
class Derived : public Base1, public Base2 {
public:
char c;
};
int main() {
Derived obj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
内存布局:
Base1::a
: 4 bytes (int)Base2::b
: 8 bytes (double)Derived::c
: 1 byte (char)- 可能存在的对齐填充:为了对齐
b
,a
之后可能会有4字节的填充。
# 四、虚函数与虚函数表指针(vptr)
当类中包含虚函数时,编译器会为该类的每个对象添加一个隐藏的指针 vptr
,指向虚函数表(vtable)。
class Base {
public:
virtual void func() {}
int x;
};
class Derived : public Base {
public:
int y;
};
int main() {
Derived obj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
内存布局:
vptr
: 8 bytes (在64位系统上)Base::x
: 4 bytes (int)Derived::y
: 4 bytes (int)- 可能存在的对齐填充:为对齐内存,可能会在
vptr
后有填充字节。
# 五、虚继承
虚继承用于解决“菱形继承”问题(多继承中,同一个基类通过不同路径多次继承)。虚继承会在对象布局中添加一个指向虚基类的指针。
class A {
public:
int x;
};
class B : virtual public A {
public:
int y;
};
class C : virtual public A {
public:
int z;
};
class D : public B, public C {
public:
int w;
};
int main() {
D obj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
内存布局:
vptr(B的虚基类指针)
: 8 bytesvptr(C的虚基类指针)
: 8 bytesA::x
: 4 bytesB::y
: 4 bytesC::z
: 4 bytesD::w
: 4 bytes
# 六、对象大小计算
在考虑类对象的大小时,必须包括所有的非静态数据成员、对齐填充、vptr
和虚基类指针。可以使用 sizeof
操作符计算对象的实际大小。
std::cout << "Size of Derived: " << sizeof(Derived) << std::endl;
1
# 总结
- 非静态数据成员:按声明顺序排列,并受内存对齐影响。
- 继承:派生类对象包含所有基类的成员。
- 虚函数表指针:对于包含虚函数的类,每个对象都有一个指向虚函数表的指针
vptr
。 - 虚继承:派生类对象可能包含指向虚基类的指针,用于解决多重继承中的“菱形问题”。
- 内存对齐:编译器可能插入填充字节来优化内存访问。
理解类对象的内存布局不仅有助于优化代码性能,还能帮助在调试和理解复杂继承结构时避免潜在问题。
编辑 (opens new window)
上次更新: 2024/09/13, 11:59:12