C++ 中,结构体(struct)和 类(class)的大小受到多种因素的影响,主要包括成员变量、对齐规则、继承关系等
1.类/结构体的大小主要受以下因素影响
1.1 成员变量
成员变量会直接影响类的大小,类的大小通常是其成员变量大小之和(但会受到对齐规则影响)
不同类型的成员变量占用的空间不同,编译器会为它们分配适当的内存。
函数成员不算作类或结构体的成员变量,也不会影响类的大小。
类的大小只由其非静态成员变量、对齐规则和虚函数表指针(如果有虚函数)等因素决定
1.2 对齐规则
C++ 中通常使用 内存对齐 来提高内存访问效率。
对齐会引入 填充字节(padding),导致类的实际大小可能大于所有成员变量大小之和。
常见对齐规则:
struct Example {
char a; // 1 字节
int b; // 4 字节
}; // 实际大小为 8 字节(对齐填充)
每个成员变量的地址必须是其类型大小的倍数。
类的大小必须是最大对齐边界的倍数。
在设置了内存对齐之后会有不同的结果
#include<iostream>
#pragma pack(push, 1) // 设置为 1 字节对齐
class Example1
{
int a; //4
char b;//1
};
#pragma pack(pop) // 恢复默认对齐
int main() {
std::cout << sizeof(Example1); //5
return 0;
}
1.3 虚函数
如果类定义了虚函数,编译器会为类添加一个虚函数表指针(vptr),这通常会增加类的大小。
vptr
的大小与指针大小相同(通常为 4 字节或 8 字节,取决于平台)
class Example
{
int a;
char b;
virtual void test1() {
}
};
class Example_1
{
int a;
char b;
void test1() {
}
};
sizeof(Example); // 64位为16 32位为12(虚表指针收到平台的影响)
sizeof(Example_1); // 没有虚表指针 为8
1.4 继承
基类的成员变量会影响派生类的大小。
如果基类有虚函数,派生类会继承虚函数表指针。
多继承时,每个基类的虚函数表可能需要单独的指针
1.5 编译器优化
不同的编译器可能会对类的布局进行优化,影响类的大小。
使用编译器选项(如
#pragma pack
)可以改变类的对齐方式。
2. 空类的大小
class Empty {};
空类的大小不会是 0 字节,而是至少为 1 字节。
原因:为了确保每个对象有唯一的地址(C++ 标准规定),即使类不包含任何数据。
实际上,这个 1 字节没有数据含义,只是为了让空类实例可区分。
#include <iostream>
class Empty {};
int main() {
Empty e1, e2;
std::cout << "Size of Empty class: " << sizeof(Empty) << std::endl;
std::cout << "Address of e1: " << &e1 << ", e2: " << &e2 << std::endl;
return 0;
}
//输出
Size of Empty class: 1
Address of e1: 0x7ffee49214c0, e2: 0x7ffee49214c1
3.为什么函数不影响类的大小
函数代码存储在代码段:类的成员函数是代码,它们存储在可执行文件的代码段中,而不是类的对象中。
成员函数通过隐式指针调用:成员函数的访问通过隐式的
this
指针操作类的成员变量,this
指向具体的对象,函数本身并不占用对象的内存。
4.静态成员变量和函数也不影响类的大小
静态成员函数没有隐式的 this
指针,不与具体对象关联,因此它们也不影响类的大小
静态成员变量的存储位置:
静态成员变量是属于类本身,而不是属于类的某个对象。
它们被存储在全局数据区(通常是静态存储区或 BSS/数据段),与类的对象无关。
因此,静态成员变量不会包含在对象实例中,也不会影响类的大小。
class MyClass {
int a; // 非静态成员变量,占用对象空间
static int staticVar; // 静态成员变量,不占用对象空间
static void test1(){
}
};
int main() {
MyClass obj;
std::cout << sizeof(obj) << std::endl; // 仅包含非静态成员变量的大小 4
return 0;
}