C++ 对象大小
2025-09-23 09:50:57

影响 C++ 内存大小的四大核心因素:

  1. 成员变量:对象大小的基本构成。
  2. 内存对齐:编译器为提升性能而采取的优化策略。
  3. 虚拟机制:C++ 多态性的基石所带来的开销。
  4. 继承模型:不同继承方式对内存布局的深刻影响。

核心基石:成员变量

  • 非静态成员:对象实例的“实体”,每个对象都有自己的一份拷贝。
  • 静态数据成员:静态成员不属于任何单个对象,而是被所有类对象共享,不计入单个对象的sizeof大小
  • 成员函数:无论是静态还是非静态,都存在于代码段,不计入对象的sizeof大小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

class Base {
public:
int a; // 4 bytes
char b; // 1 byte
static int c; // 不计入对象大小

void func() {} // 不计入对象大小
};

int Base::c = 10;

int main() {
// 理论上是 4 + 1 = 5 bytes,但实际会是 8 bytes
// 这是因为下一个核心因素:内存对齐
std::cout << "Size of Base: " << sizeof(Base) << std::endl;
return 0;
}

关键机制:内存对齐

内存对齐是编译器为了提升 CPU 数据读取效率而采取的一项关键优化。如果一个变量的地址是其大小的整数倍,那么这次读取就是“对齐的”,CPU 可以一次性完成操作。
对齐规则(不同编译器可能有差异)

  1. 成员自身对齐:每个成员变量的起始地址必须是其自身大小(或编译器指定的对齐值)的整数倍。
  2. 整体对齐:类/结构体的总大小必须是其所有成员最大对齐值的整数倍。
  • 可以用#pragma pack(N)来强制指定对齐系数,通常会以牺牲性能为代价。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

// 示例1: 编译器填充
struct S1 {
char c1; // 1 byte
// 填充 3 bytes
int i; // 4 bytes
char c2; // 1 byte
// 填充 3 bytes (为了整体对齐)
}; // 总大小为 1+3+4+1+3 = 12 bytes? 不对,是 max(alignof(c1), alignof(i), alignof(c2)) = 4 的倍数,所以是 8。
// c1 (offset 0), i (offset 4, 1,2,3 填充), c2(offset 8), 为了整体是4的倍数,9,10,11填充。总大小12.
// 更正:c1(1), 填充(3), i(4), c2(1), 填充(3) -> 12 bytes
// c1 offset 0. alignof(int) is 4, so i must start at offset 4.
// [c1, pad, pad, pad] [i, i, i, i] [c2, pad, pad, pad]
// Total size must be a multiple of max alignment (4).
// So size is 12 bytes.

// 示例2: 优化成员顺序
struct S2 {
int i; // 4 bytes
char c1; // 1 byte
char c2; // 1 byte
// 填充 2 bytes (为了整体对齐)
}; // 总大小为 4+1+1+2 = 8 bytes

int main() {
std::cout << "Size of S1: " << sizeof(S1) << std::endl; // 输出 12
std::cout << "Size of S2: " << sizeof(S2) << std::endl; // 输出 8
}

C++的灵魂:虚拟机制

C++ 的动态多态性是通过虚函数实现的。当一个类拥有任何虚函数,编译器会为这个类引入一个隐藏的成员:虚函数表指针

vptr通常位于对象内存布局的起始位置,指向一个静态分配的虚函数表;大小跟系统架构有关,32位 - 4字节,64位 - 8字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

class NonVirtual {
int data; // 4 bytes
};

class WithVirtual {
int data; // 4 bytes
virtual void func() {} // 引入vptr
};

int main() {
// 在64位系统下
std::cout << "Size of NonVirtual: " << sizeof(NonVirtual) << std::endl; // 输出 4
std::cout << "Size of WithVirtual: " << sizeof(WithVirtual) << std::endl; // 输出 16 (8 for vptr + 4 for data + 4 padding)
}

面向对象支柱:继承

继承代价:编译器引入额外信息增加了对象体积,访问虚拟基类成员的速度比普通继承稍慢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

class Base { virtual void f() {} }; // 有vptr
class Derived1 : public Base {}; // 继承vptr
class Derived2 : public Base {}; // 继承vptr
class Diamond : public Derived1, public Derived2 {}; // 菱形继承问题

class VDerived1 : virtual public Base {}; // 虚拟继承
class VDerived2 : virtual public Base {}; // 虚拟继承
class VDiamond : public VDerived1, public VDerived2 {};

int main() {
// 在64位系统下
std::cout << "Size of Base: " << sizeof(Base) << std::endl; // 8 (vptr)
std::cout << "Size of Diamond: " << sizeof(Diamond) << std::endl; // 16 (Base via D1: vptr + Base via D2: vptr)
std::cout << "Size of VDiamond: " << sizeof(VDiamond) << std::endl; // 24 (D1 part: vptr/vbptr, D2 part: vptr/vbptr, shared Base part)
// 具体大小高度依赖编译器实现
}
Prev
2025-09-23 09:50:57