在C++里,sizeof是一个编译期一元运算符,用来获取类型或对象在内存中占用的字节数。它既不像函数那样有运行时开销,也不关心对象里存了什么值,只根据类型信息在编译阶段算出大小。理解它的计算规则,对内存布局、网络传输和结构体内存优化都十分关键。
一、sizeof的基本语法与常见用法
sizeof有两种写法:一种是跟类型名,必须加括号,例如sizeof(int);另一种是跟表达式或变量,括号可省略,例如sizeof x。因为sizeof是运算符而非函数,所以后面接变量时括号不是语法强制要求,但接类型时不能省。编译器在生成代码前就把它替换成常量整数。
下面代码展示了基础类型的尺寸差异,不同平台结果可能不同,但规律一致:
#include <iostream>
int main() {
std::cout << "sizeof(char) = " << sizeof(char) << std::endl;
std::cout << "sizeof(int) = " << sizeof(int) << std::endl;
std::cout << "sizeof(double) = " << sizeof(double) << std::endl;
// 表达式形式,括号可省略
int a = 10;
std::cout << "sizeof a = " << sizeof a << std::endl;
return 0;
}
在常见64位Linux环境下,char为1字节,int为4字节,double为8字节。由于sizeof是编译期常量,它还可以用于定义数组长度,例如char buf[sizeof(int) * 4];,这样写能让数组大小跟随类型变化自动调整。
需要注意,对引用类型使用sizeof,得到的是被引用对象的大小,而不是引用本身的实现大小。引用在语言层面只是别名,不额外占用存储概念上的独立空间。
二、数组与指针的sizeof差异
这是最容易出错的地方。当对数组名直接使用sizeof时,得到的是整个数组的字节数;而当数组退化为指针后,sizeof只能得到指针本身的大小。很多初学者误以为传参后sizeof还能算出数组长度,结果引发逻辑错误。
看下面示例,同样叫arr,在main里和函数参数里表现完全不同:
#include <iostream>
void printSize(int arr[]) {
// 此处arr已退化为指针
std::cout << "in func sizeof(arr) = " << sizeof(arr) << std::endl;
}
int main() {
int arr[10];
std::cout << "in main sizeof(arr) = " << sizeof(arr) << std::endl;
printSize(arr);
return 0;
}
在64位系统,main中输出40(10乘以4字节),而函数内输出8,因为参数int arr[]等价于int* arr。若想在函数里获知数组元素个数,应额外传入长度,或用模板引用形参保留数组类型信息。
利用模板可以在编译期保留数组维度,例如:
template <typename T, size_t N>
size_t arrayLen(T (&)[N]) {
return N;
}
这种方式通过引用数组避免退化,N直接由编译器推导,比在函数内用sizeof安全得多。
三、结构体内存对齐对sizeof的影响
结构体的sizeof通常不是成员大小简单相加,编译器会按对齐规则插入填充字节。对齐目的是让CPU更高效地访问数据,不同平台默认对齐值不同,一般按成员中最大基础类型对齐。
观察下面两个成员顺序不同的结构体:
#include <iostream>
struct A {
char c;
int i;
short s;
};
struct B {
char c;
short s;
int i;
};
int main() {
std::cout << "sizeof(A) = " << sizeof(A) << std::endl;
std::cout << "sizeof(B) = " << sizeof(B) << std::endl;
return 0;
}
在默认对齐下,A中char后填3字节让int对齐,short后可能再填2字节凑整,大小常为12;B中char和short共用前4字节,int紧跟其后,大小常为8。可见重排成员能明显压缩空间。
可用#pragma pack或alignas改变对齐方式,但过分压缩可能降低访问速度,且与外部数据格式交互时要格外小心,避免读写错位。
四、空类与继承下的sizeof
空类对象在C++中大小至少为1字节,目的是让不同对象有唯一地址。若类里有虚函数,会额外增加虚表指针,64位下通常加8字节。继承时,基类子对象也参与布局。
#include <iostream>
class Empty {};
class WithVirtual {
virtual void foo() {}
};
int main() {
std::cout << "sizeof(Empty) = " << sizeof(Empty) << std::endl;
std::cout << "sizeof(WithVirtual) = " << sizeof(WithVirtual) << std::endl;
return 0;
}
运行后Empty多为1,WithVirtual多为8。当空类作为基类且被派生类继承时,若满足空基类优化条件,派生类不一定要为它分配那1字节,这也是标准鼓励的内存优化手段。
掌握这些sizeof行为,能帮助你在写底层库、序列化协议或内存池时,精确控制对象体积,不至于因误判大小造成溢出或兼容问题。