sizeof
1. 概念与目的
sizeof
是 C++ 中的一个一元运算符 (unary operator),也是一个关键字。它的主要目的是查询一个数据类型或一个表达式的类型所占用的内存大小(以字节 (bytes) 为单位)。
关键点:
-
sizeof
的结果是在编译时 (compile time) 确定的。 - 它返回的是类型或对象在内存中分配的静态大小,不考虑动态分配的内容(例如
std::vector
的sizeof
只包含其管理成员的大小,不包括堆上存储元素的大小)。
2. 语法
sizeof
有两种使用形式:
-
sizeof(type)
:- 应用于一个数据类型名称。
- 括号是必需的。
- 示例:
sizeof(int)
,sizeof(double)
,sizeof(MyStruct)
,sizeof(char*)
-
sizeof expression
:- 应用于一个表达式。
sizeof
会计算该表达式结果的类型所占的大小。 -
表达式本身在运行时不会被求值! 这是非常重要的一点,意味着表达式中的任何副作用(如
++
,--
, 函数调用)都不会发生。 - 括号是可选的,但通常为了清晰起见会加上:
sizeof(expression)
。 - 示例:
int x = 10; double y = 3.14; struct Point { double x, y; }; Point p; size_t size1 = sizeof x; // 等价于 sizeof(int) size_t size2 = sizeof(y); // 等价于 sizeof(double) size_t size3 = sizeof(p); // 等价于 sizeof(Point) size_t size4 = sizeof(x + y); // x+y 的结果是 double, 等价于 sizeof(double) size_t size5 = sizeof(p.x); // p.x 是 double, 等价于 sizeof(double) int i = 0; size_t size_int = sizeof(i++); // i++ 的类型是 int. i++ 不会被执行! i 的值仍然是 0. // size_int 会得到 int 的大小。
- 应用于一个表达式。
3. 返回值
-
类型:
sizeof
的返回类型是std::size_t
。这是一个无符号整数类型,定义在头文件<cstddef>
(C++) 或<stddef.h>
(C) 中。std::size_t
足够大,可以表示任何对象在内存中的大小。 -
值: 返回的是对象或类型占用的字节数。
- C++ 标准规定
sizeof(char)
,sizeof(signed char)
,sizeof(unsigned char)
必须为 1。 - 其他基本类型(如
int
,short
,long
,float
,double
,bool
, 指针等)的大小是实现定义 (implementation-defined) 的,取决于编译器、目标 CPU 架构(例如 32 位 vs 64 位)和操作系统。 - 例如,在常见的 x86-64 架构(如现代 Windows, Linux, macOS)上,通常:
-
sizeof(short)
= 2 -
sizeof(int)
= 4 -
sizeof(long)
= 4 (Windows) 或 8 (Linux, macOS) -
sizeof(long long)
= 8 -
sizeof(float)
= 4 -
sizeof(double)
= 8 -
sizeof(bool)
= 1 (通常) -
sizeof(void*)
(任何指针类型) = 8 (在 64 位系统上) 或 4 (在 32 位系统上)
-
- 对于结构体 (
struct
) 或类 (class
),sizeof
返回的是包括任何填充字节 (padding) 在内的总大小。
- C++ 标准规定
4. 常见用途
-
内存管理: 虽然 C++ 中
new
会自动计算大小,但在使用 C 风格的内存分配函数(如malloc
,calloc
)时,sizeof
至关重要。int* arr = (int*)malloc(10 * sizeof(int)); // 分配足够存放 10 个 int 的内存
-
计算数组元素个数: 这是 C/C++ 中的一个经典技巧,仅适用于在作用域内定义的真实数组(不是指向数组的指针)。
int myArray[] = {1, 2, 3, 4, 5}; size_t numElements = sizeof(myArray) / sizeof(myArray[0]); // 计算数组元素数量 (5) // sizeof(myArray) = 5 * sizeof(int) // sizeof(myArray[0]) = sizeof(int)
-
数据序列化/反序列化: 在读写二进制文件或网络传输数据时,需要知道读写多少字节。
struct DataPacket { int id; double value; }; DataPacket packet; // 假设从文件或网络读取数据到 packet 中 fread(&packet, sizeof(DataPacket), 1, filePtr); // 读取一个 DataPacket 大小的数据块
- 平台相关代码: 检查特定类型的大小以适配不同的系统。
- 模板元编程: 在编译时进行基于类型大小的计算。
5. 重要细节与陷阱
-
数组 vs 指针:
-
sizeof(array)
: 返回整个数组占用的总字节数。 -
sizeof(pointer)
: 只返回指针变量本身的大小(通常 4 或 8 字节),不返回指针指向的数据块的大小。当数组名作为函数参数传递时,它会退化 (decay) 为指针,在函数内部sizeof
该参数将得到指针的大小,而不是原始数组的大小。
void processArray(int arrPtr[]) { // arrPtr 实际上是 int* // sizeof(arrPtr) 在这里将返回指针的大小 (例如 4 或 8), 而不是原始数组大小! // 这就是为什么通常需要将数组大小作为单独参数传递给函数。 }
-
-
函数类型: 不能对函数类型本身使用
sizeof
(会导致编译错误)。可以对函数指针使用sizeof
。 -
引用 (Reference): 对引用使用
sizeof
得到的是被引用对象类型的大小。int x = 10; int& refX = x; // sizeof(refX) 等价于 sizeof(x),即 sizeof(int)
-
void
类型:sizeof(void)
在标准 C++ 中通常是不允许的或被定义为 1(作为 GCC 等编译器的扩展)。 -
字符字面量: 在 C++ 中,
sizeof('a')
的结果是sizeof(int)
,而不是sizeof(char)
(这是从 C 继承的历史原因)。而sizeof(char)
始终是 1。 -
位域 (Bit-fields): 不能对结构体或类中的位域成员直接使用
sizeof
。 -
空结构体/类:
sizeof
一个没有任何成员的struct
或class
的结果至少为 1。这是为了确保该类型的不同对象在内存中拥有不同的地址。
总结: sizeof
是一个在编译时计算类型或表达式类型大小(以字节为单位)的基本 C++ 运算符。它的返回值类型是 std::size_t
。理解 sizeof
的行为,特别是它在数组、指针上的不同表现以及其编译时特性,对于编写健壮和高效的 C++ 代码至关重要。记住其结果(除 char
外)是实现定义的。
想象一下,电脑的内存就像一个超级大的玩具储物柜,里面有很多很多标准大小的小储物格 (Bytes - 字节)。每个小储物格就是最小的空间单位。
电脑里存放的各种数据(比如数字、字母、我们设计的“魔法盒子”结构体)就像是不同种类的玩具:
- 一个字母 (
char
) = 一颗小弹珠 - 一个整数 (
int
) = 一辆玩具小汽车 - 一个带小数的数 (
double
) = 一辆玩具大卡车 - 一个包含很多信息的“神奇宝贝卡片”盒子 (
struct PokemonCard
) = 一个定制的玩具套装礼盒
sizeof
测量师的工作:
在你把玩具放进储物柜之前,你需要知道这个玩具需要占几个小储物格,对吧?sizeof
就扮演了这个测量师的角色!
你可以这样问 sizeof
测量师:
-
问关于“玩具种类”: “测量师先生,请问 一辆玩具小汽车 (
int
) 需要占几个标准储物格(字节)?”- 测量师可能会回答:“需要 4 个储物格!” (
sizeof(int)
返回 4) - “那 一颗小弹珠 (
char
) 呢?” - “哦,那个最小,只需要 1 个储物格!” (
sizeof(char)
返回 1) - “那个定制的玩具套装礼盒 (
struct PokemonCard
) 呢?” - “嗯,我看看它的设计图... 它需要,比如说,20 个储物格!” (
sizeof(PokemonCard)
返回 20,包含了所有零件和可能需要的额外空间)
- 测量师可能会回答:“需要 4 个储物格!” (
-
问关于“你手里的具体玩具”: 你手里拿着一个玩具小汽车变量
myCar
。你可以问:“测量师先生,请看看我手里这个东西,它这种类型的玩具需要占几个储物格?”- 测量师看了一眼
myCar
(发现它是一辆玩具小汽车),然后告诉你:“你手里这种是玩具小汽车,需要 4 个储物格!” (sizeof myCar
或sizeof(myCar)
返回 4)
- 测量师看了一眼
测量师的特点:
-
只看不碰 (编译时计算): 测量师非常厉害,他只需要看一眼玩具的种类或者你手里玩具的标签(类型),不需要真的把玩具拿去摆弄,就能知道它需要多少空间。如果你让他测量
sizeof(我的弹珠数量 + 1)
,他只关心结果会是哪种数字(可能还是int
),不会真的去做加法。 - 单位是“字节”: 他总是用“标准储物格(字节 Byte)”作为单位来告诉你大小。
测量一整盒玩具 vs. 一个地址牌:
-
一整盒玩具 (数组 Array): 如果你有一整盒弹珠
myMarbles[10]
(10 颗弹珠)。你问sizeof(myMarbles)
,测量师会告诉你整个盒子需要多少储物格(比如 10 颗 * 1 格/颗 = 10 格)。 -
一个地址牌 (指针 Pointer): 如果你只有一个写着“弹珠在那里”的小牌子
marbleLocation
(char*
),你问sizeof(marbleLocation)
,测量师只测量这个小牌子本身需要多少空间(比如 4 个或 8 个储物格),他不管那个牌子指向的弹珠有多大!
重点: sizeof
是一个工具,用来问电脑某种数据类型或某个变量的类型需要占用多少内存空间(以字节为单位)。它在程序运行前就算好了,并且不会改变变量本身。
画出测量过程
画出储物柜和小格子 (内存与字节): 画一个大柜子,里面画满小方格,每个方格写上 "Byte"。
-
画出玩具和它们的尺寸:
- 画一颗弹珠 ('A'),旁边放 1 个 "Byte" 方格。标注
sizeof(char) = 1
。 - 画一辆玩具小汽车 (123),旁边放 4 个 "Byte" 方格。标注
sizeof(int) = 4
(例子)。 - 画一辆玩具大卡车 (3.14),旁边放 8 个 "Byte" 方格。标注
sizeof(double) = 8
(例子)。
- 画一颗弹珠 ('A'),旁边放 1 个 "Byte" 方格。标注
画
sizeof
测量师: 画一个小人拿着尺子或放大镜,正在“测量”这些玩具(或者玩具的标签/类型)。-
画数组 vs 指针:
-
数组: 画一个打开的盒子
marbles[3]
,里面有 3 颗弹珠。测量师正在测量整个盒子。旁边画 3 x 1 = 3 个 "Byte" 方格。标注sizeof(marbles) = 3
。 -
指针: 画一个地址牌/箭头
location
指向一颗远处的弹珠。测量师只测量这个地址牌。旁边画 (比如) 8 个 "Byte" 方格 (假设是 64 位系统)。标注sizeof(location) = 8
。用醒目的方式强调 8 和弹珠的 1 是不同的!
-
数组: 画一个打开的盒子
画编译时特性: 画测量师看着
sizeof(i++)
的代码,旁边画一个禁止标志或者叉号覆盖在i++
上,表示“这个加加不会真的做!”。
看图理解: 图画能直观地显示不同“玩具”占用不同数量的“格子”。数组和指针的区别通过测量整个盒子和只测量地址牌来体现。禁止标志说明了 sizeof
不会执行里面的操作。
现在我们用电脑的语言来总结 sizeof
:
- 为什么需要? 电脑在运行程序前,需要规划好内存空间 (储物柜) 如何使用。它要知道每种数据要预留多大的地方。
-
sizeof
操作符: C++ 提供了一个叫sizeof
的操作符 (Operator),就像一个内置的计算器,专门用来计算“空间需求”。 -
两种用法:
-
sizeof(数据类型)
:直接问一种类型(比如int
,float
,MyStruct
)需要多少字节。 -
sizeof 变量或表达式
:问一个已经存在的变量(或一个表达式的结果)的类型需要多少字节。
-
-
结果:
sizeof
返回一个数字,表示所需空间的字节 (Byte) 数量。这个数字的类型是std::size_t
。除了char
类型的大小固定为 1 字节外,其他类型的大小可能因电脑或编译器而异。 -
编译时工作:
sizeof
的计算工作是在编译程序的时候(程序运行前)就完成了。它非常快,因为它不涉及程序运行时的复杂操作。放在sizeof
里的表达式通常不会被执行。 -
用途: 程序员用
sizeof
来:- 确保为数据分配了足够的内存。
- 弄清楚一个数组里有多少个元素。
- 准确地读取或写入文件中的数据块。
-
注意区分:
-
sizeof(数组名)
得到整个数组的大小。 -
sizeof(指针名)
只得到指针本身的大小。
-
记住: sizeof
是 C++ 用来在编译时确定任何数据类型占用多少字节内存空间的方便工具,帮助电脑和程序员管理内存。