基本原则
- 成员函数不占类对象的内存空间:成员函数的代码存储在代码段中,所有对象共享这段代码。
- 一个类对象至少占1个字节内存空间:即使一个类没有成员变量,编译器也会分配至少1个字节的空间。
- 成员变量是包含在每个对象中的,占用字节:每个对象的成员变量在对象中独立存在。
- 成员函数与类对象的关系:成员函数定义在类中,但成员函数不占用类对象的内存。成员函数与类关联,与对象无关,不论创建多少个对象,成员函数的代码只存储一份。
静态成员与虚函数
- 静态成员变量不计算在对象的
sizeof
内:静态成员变量属于类本身,而不是类的对象。 - 普通成员函数和静态成员函数不计算在
sizeof
内:它们与类对象的内存占用无关。 - 虚函数增加对象的内存大小:虚函数表指针(vptr)需要额外的内存,通常是8字节(在64位系统上)。
- 虚函数表基于类存在:虚函数表(vtable)是类级别的,而不是对象级别的。
内存对齐和填充字节
-
内存对齐:
- 为了访问速度和性能,编译器可能会在成员变量之间插入填充字节。举例:
class Example { char a; // 1字节 int b; // 4字节 };
- 为了对齐,
char a
可能会被填充为4字节,所以对象大小是8字节而不是5字节。
-
指针的大小:指针类型的大小在64位系统上是8字节,在32位系统上是4字节。
对象大小的组成
- 非静态成员变量所占的内存总量:包括这些成员变量之间内存字节对齐所额外占用的内存。
- 虚函数表指针(vptr):
- 当一个类包含虚函数时,每个该类的对象都会有一个指向虚函数表(vtable)的指针,称为
vptr
。 - 不论该类定义了多少个虚函数,所有对象只会占用一个
vptr
的内存大小。
- 当一个类包含虚函数时,每个该类的对象都会有一个指向虚函数表(vtable)的指针,称为
- 构造函数和析构函数的影响:
- 虽然构造函数和析构函数本身不直接影响对象的内存大小,但它们的存在可能会影响虚函数表的生成,尤其是在使用虚函数时。
- 继承的影响:
- 如果一个类从另一个类继承,子类的大小不仅包括自己的成员变量,还包括父类的成员变量。此外,如果父类有虚函数,子类也会包含一个指向虚函数表的指针。
- 内存布局的优化:
- 通过合理的成员变量顺序来减少填充字节。例如,将相同类型的成员变量放在一起,可能会减少内存占用。
示例验证
可以通过实际代码和sizeof
操作符来验证:
// 以下注释是基于64位系统
#include <iostream>
class MyClass1 {
public:
int a; // 4字节
void func() { a = 10; } // 不占用对象内存
};
class MyClass2 {
public:
static int b; // 不占用对象内存
static void func() { b = 10; } // 不占用对象内存
};
int MyClass2::b = 0;
class MyClass3 {
public:
virtual void func() {} // 有虚函数,增加vptr
int a; // 4字节
};
class Base {
public:
virtual void func() {} // 有虚函数,增加vptr
int x; // 4字节
};
class Derived : public Base {
public:
double y; // 8字节
};
int main() {
std::cout << "Size of MyClass1: " << sizeof(MyClass1) << " bytes" << std::endl; // 应为 4
std::cout << "Size of MyClass2: " << sizeof(MyClass2) << " bytes" << std::endl; // 应为 1
std::cout << "Size of MyClass3: " << sizeof(MyClass3) << " bytes" << std::endl; // 应为 16 (8 + 4 + 4)
std::cout << "Size of Base: " << sizeof(Base) << " bytes" << std::endl; // 应为 16 (8 + 4 + 4)
std::cout << "Size of Derived: " << sizeof(Derived) << " bytes" << std::endl; // 应为 24 (8 + 8 + 4 + 4)
return 0;
}
运行以上代码可以验证上述总结的正确性和实际效果。
总结
理解C++类对象的内存占用涉及多个因素,包括成员变量、内存对齐、虚函数表指针等。通过掌握这些原则,可以更好地设计和优化类的内存布局,从而提高程序的性能和效率。