写 C/C++ 一定会撞上sizeof,但很多人把它当函数用,甚至为了它特地去查要不要引头文件。实际上sizeof是 C/C++ 语言内置的运算符,不需要任何头文件,只要你写了代码,它就一直在那儿。这篇小结不是给新手背结论的,而是把sizeof的本质、常见用法、以及我在项目里真正踩过的坑,一次性讲透。无论你是刚入门的学生,还是整天跟指针、结构体、内存打交道的嵌入式或服务端开发,这篇文章都能让你重新审视这个熟悉又陌生的老朋友。
1. sizeof是什么以及为什么每个C/C++程序员都要吃透它
1.1 sizeof的本质:一个运算符,不是一个函数
很多人第一次见sizeof是在书上的示例里,写法多半是sizeof(int),于是下意识以为它是标准库提供的函数。这是一个流传很广的误解。
sizeof是 C 和 C++ 里的一元运算符,和&、*、!是同一类东西。它最大的特点是在编译期处理,不是运行期函数调用。编译器拿到sizeof后面的操作数,会直接算出这个类型或表达式所占的字节数,然后把它当成一个整数常量替换到代码里。
看个最简单的例子:
#include <stdio.h> int main(void) { int a = 10; size_t s1 = sizeof(int); size_t s2 = sizeof a; size_t s3 = sizeof(a); printf("%zu %zu %zu\n", s1, s2, s3); return 0; }这段代码能说明两件事:
sizeof int这种写法在语法上其实是允许的,因为sizeof是运算符,不是函数,后面不一定要加括号;- 但碰到类型名,比如
sizeof int,编译器会要求必须写成sizeof(int),不然语法分析会出错。所以业界习惯一律加括号,主要是为了统一和可读性。
我建议你写代码时统一采用sizeof(类型)或sizeof(表达式)的格式,不要为了秀操作写sizeof a。真到维护的时候,别人看得省心,你自己也少踩坑。
1.2 sizeof需要头文件吗:和size_t要分清
网络热搜里经常有人问“sizeof函数需要头文件吗”,答案非常明确:不需要。因为sizeof是语言核心的一部分,只要编译器能识别 C/C++ 语法,它就能工作。就算你写一个空文件,里面只有:
int x; unsigned long long y = sizeof(x);编译器也完全认识sizeof。
不过有一个特别容易被混淆的点:sizeof的运算结果类型是size_t,而size_t是一个类型别名,定义在<stddef.h>、<stdio.h>、<stdlib.h>这些头文件里。所以如果你写了size_t s = sizeof(int);,那么编译器需要看到size_t的声明,这时代码里必须包含某个提供size_t的头文件,或者用unsigned int、unsigned long去接。
实际开发里,我几乎不会单独为了size_t去引<stddef.h>,因为项目里大概率已经引了<stdio.h>或<stdlib.h>。但在一些极简嵌入式代码里,你可能会看到:
#include <stddef.h> unsigned int size = sizeof(int);这不是因为sizeof需要头文件,而是size_t需要。这个区别如果你能跟别人讲清楚,面试官基本就认定你不是背书的。
1.3 sizeof是在编译期求值的
既然说sizeof是编译期运算符,那它必然有一个很反直觉的特性:不会真的去计算表达式。
#include <stdio.h> int main(void) { int i = 0; size_t sz = sizeof(i++); printf("i = %d, sz = %zu\n", i, sz); return 0; }这段代码输出结果是i = 0, sz = 4(32 位int环境)。也就是说,i++根本没执行,sizeof只看i++这个表达式的类型是int,然后就返回了sizeof(int)。
这给了我们一个很实用的小技巧:在 C++ 里,你完全可以在不构造对象的情况下获取某个类型实例的大小。比如:
struct MyClass { int x; double y; char name[64]; }; size_t size = sizeof(MyClass{}.name); // 这里并没有真正生成一个完整的 MyClass 对象留在栈上当然,正常写sizeof(MyClass::name)在 C++ 里不一定合法,因为非静态成员不能单独取偏移,但sizeof(MyClass{}.name)这种写法偶尔能在泛型代码里派上用场。
1.4 sizeof返回的类型:size_t而不是int
还有一个细节点:sizeof的结果类型是size_t,是无符号类型,不是int。很多人打印时用%d,在某些编译器上会导致格式字符串和参数类型不匹配,轻则打印出乱码,重则触发警告甚至未定义行为。
正确打印方式是%zu(C99 起):
#include <stdio.h> int main(void) { printf("%zu\n", sizeof(int)); return 0; }如果你的编译器比较老,不支持%zu,可以强转成unsigned long再打印:
printf("%lu\n", (unsigned long)sizeof(int));这点在写跨平台日志时很重要,别问我怎么知道的,都是被报警告烦出来的。
2. 基础类型的sizeof结果与平台差异
2.1 典型32/64位平台上的基础类型大小
很多初学者会问:int到底占 4 字节还是 8 字节?答案是不一定。C 标准只规定了最小范围和相对关系,没有强制规定每种整数类型必须是多少字节。
以最常见的 x86 和 x86-64 平台为例,我标一下常见的字节数:
| 类型 | 32位平台常见值 | 64位平台常见值 | 备注 |
|---|---|---|---|
char | 1 | 1 | 标准恒定为1 |
short | 2 | 2 | 至少16位 |
int | 4 | 4 | 至少16位,通常32位 |
long | 4 | 在 Linux 上8,在 Windows 上4 | 历史差异最大 |
long long | 8 | 8 | C99标准引入 |
float | 4 | 4 | 通常 IEEE 754 单精度 |
double | 8 | 8 | 通常 IEEE 754 双精度 |
指针 | 4 | 8 | 取决于地址空间 |
所以,如果你在做网络协议解析、文件格式读写、或者跨进程通信,不要假设long一定是 8 字节或 4 字节。正确做法是用<stdint.h>/<cstdint>里的int32_t、uint64_t,这些类型在不同平台上都有固定的位宽。
顺便说一个亲测的场景:之前我在 Windows 上写了一个共享内存的结构体,里面有个long字段,本机测试一切正常。后来把同一份代码放到 Linux 上,发现共享内存里的字段偏移全部错位,排查了半天,最后发现就是long在 Windows 是 4 字节、在 Linux 是 8 字节。从那以后,我二进制布局里的整数类型全部改成int32_t/uint64_t,再也没出过这种问题。
2.2 char永远是1,但不等于一定是8位
C 标准规定sizeof(char)永远等于 1。这里的关键点是:sizeof得到的数值是以char大小为单位的,所以sizeof(char)必然等于 1。但这不代表char一定是 8 位。
在一些极其小众的 DSP 平台或者老式嵌入式处理器上,char可能是 16 位甚至 32 位。C 标准只保证CHAR_BIT >= 8,具体多少由平台决定。你可以在<limits.h>里查看CHAR_BIT。
这个特性对绝大多数开发者来说没有实际影响,但理解它能帮你明白一件事:sizeof的“1”是语言定义的抽象字节,不是物理上的 8 bit。真要写底层的位运算、编解码协议,该用CHAR_BIT的地方不要偷懒。
2.3 用sizeof在编译期保护平台假设
既然平台大小有差异,那怎么防止代码被错误地移植到别的架构?C11 提供了_Static_assert,C++11 提供了static_assert,可以在编译期检查sizeof结果:
#include <stdint.h> _Static_assert(sizeof(int32_t) == 4, "int32_t must be 4 bytes"); _Static_assert(sizeof(uint64_t) == 8, "uint64_t must be 8 bytes");在 C++ 里可以写成:
static_assert(sizeof(int) == 4, "This code assumes int is 32-bit");这种断言在写跨平台网络库时特别有用。你可以在公共头文件里放一组static_assert,一旦有人把代码拖到某些奇怪平台上编译,编译器会直接报错,而不是等程序跑到某个协议解析的地方才崩溃。
3. 数组、字符串与sizeof:最容易踩坑的重灾区
3.1 数组名与sizeof:不退化原则
数组名在绝大多数表达式中会“退化”成指向首元素的指针,但有一个非常重要的例外:当sizeof的操作数是数组名时,不会发生退化。
#include <stdio.h> int main(void) { int arr[10]; printf("sizeof(arr) = %zu\n", sizeof(arr)); printf("sizeof(arr[0]) = %zu\n", sizeof(arr[0])); return 0; }siziof(arr)得到的是整个数组占用的字节数,比如10 * 4 = 40。而sizeof(arr[0])是单个元素的字节数。两者一除,就是数组元素个数:
size_t count = sizeof(arr) / sizeof(arr[0]);这是 C 语言里最经典、也最常用的数组长度计算方式。注意,这种方法只能用于“真正的数组”,不能用于指针,也不能用于已经退化成指针的函数参数。
3.2 字符串字面量与sizeof:别漏了末尾的'\0'
字符串是数组的另一种表现形式,所以在sizeof面前同样有坑。
#include <stdio.h> #include <string.h> int main(void) { char str[] = "hello"; const char *p = "hello"; printf("sizeof(str) = %zu\n", sizeof(str)); // 6 printf("strlen(str) = %zu\n", strlen(str)); // 5 printf("sizeof(p) = %zu\n", sizeof(p)); // 8(64位平台指针大小) printf("sizeof(\"hello\") = %zu\n", sizeof("hello")); // 6 return 0; }"hello"字面量实际是char[6],因为末尾还藏着一个'\0'。所以sizeof("hello")等于 6,而strlen("hello")只数到结尾之前的 5 个字符。
很多人写序列化或网络包时,喜欢用sizeof("hello")来算字符串常量长度,这是可行的,因为它包含'\0',可以直接用来memcpy或fwrite。但如果你用char *p指向同一个字符串常量,sizeof(p)只是指针大小,永远拿不到字符串长度。这是面试里高频出现的区别。
3.3 sizeof和strlen的区别:一个编译期,一个运行期
sizeof和strlen虽然都能和字符串扯上关系,但完全是两回事:
| 对比项 | sizeof | strlen |
|---|---|---|
| 性质 | 运算符,编译期求值 | 库函数,运行期遍历 |
| 是否需要头文件 | 不需要 | 需要<string.h> |
| 统计对象 | 类型或表达式的存储大小 | 字符串的字符个数 |
是否包含'\0' | 如果对象是数组,会包含 | 不包含 |
| 操作数 | 类型或表达式 | 只能传指针 |
实际开发中,我经常看到有人在缓冲区管理时用错:
char buf[64]; strcpy(buf, "hello"); size_t a = sizeof(buf); // 64,缓冲区容量 size_t b = strlen(buf); // 5,当前字符串长度这两个都很有用,但用途完全不同。复制或写入时要限制的是缓冲区容量,应该用sizeof(buf)来算剩余空间;打印或发送时要确定内容长度,才用strlen(buf)。搞反了轻则浪费空间,重则缓冲区溢出。
3.4 二维数组和多维数组的sizeof
多维数组的sizeof规律和普通数组类似,只是要一层一层剥。
#include <stdio.h> int main(void) { int matrix[3][4]; printf("sizeof(matrix) = %zu\n", sizeof(matrix)); // 3*4*4 printf("sizeof(matrix[0]) = %zu\n", sizeof(matrix[0])); // 4*4 printf("sizeof(matrix[0][0]) = %zu\n", sizeof(matrix[0][0])); // 4 return 0; }用sizeof可以安全地计算行数和列数:
size_t rows = sizeof(matrix) / sizeof(matrix[0]); size_t cols = sizeof(matrix[0]) / sizeof(matrix[0][0]);有一个和函数参数相关的经典问题:如果把二维数组传给函数,比如:
void process(int m[][4], size_t rows);在函数内部,sizeof(m)并不是整个二维数组的大小,因为形参m已经退化成“指向数组的指针”,也就是int (*)[4]。所以函数里算列数没问题,编译器知道第二维是 4,但算行数就不再用sizeof(m) / sizeof(m[0])了,必须把rows显式传进来。
4. 指针和动态内存:为什么sizeof(指针)总让你意外
4.1 指针的sizeof是固定的吗
在绝大多数主流平台上,所有类型的指针大小都一样:32 位系统是 4 字节,64 位系统是 8 字节。它指向的是char、int还是某个结构体,不影响指针本身的大小。
#include <stdio.h> int main(void) { char *pc; int *pi; void *pv; double *pd; printf("%zu %zu %zu %zu\n", sizeof(pc), sizeof(pi), sizeof(pv), sizeof(pd)); return 0; }这段代码在主流 64 位平台上一律打印8。这里的本质原因是指针保存的是地址,地址宽度由平台决定,和指向对象的类型无关。
不过,“主流”不等于“所有”。在一些特殊架构上,函数指针可能和数据指针大小不同,某些 Harvard 架构的嵌入式芯片上还有更奇怪的情况。所以在写极度可移植的代码时,不要把“所有指针大小相同”当成绝对真理。但在日常服务器开发和桌面开发中,你可以放心用sizeof(指针)做序列化估算。
4.2 sizeof不能探测动态分配内存的大小
这是sizeof被误解最深的一个点。
int *p = malloc(100 * sizeof(int)); size_t n = sizeof(p); // 结果不是 400,而是 8(64位平台)malloc返回的是一块连续内存的首地址,当你把它赋值给指针变量后,sizeof(p)只能得到指针变量的存储大小,得不到这块内存的大小。C 标准并没有规定库函数要记录你分配了多少字节,更不存在标准接口去查询malloc分配的大小。
所以实际开发里,所有动态分配的内存长度都必须由程序员自己保存。我习惯用一个轻量结构体来封装:
#include <stdlib.h> typedef struct { int *data; size_t len; } IntArray; IntArray create_int_array(size_t n) { IntArray arr; arr.data = malloc(n * sizeof(int)); arr.len = n; return arr; }这才是真正可靠的长度管理方式。不要幻想sizeof能帮你记住malloc的分配结果。
4.3 数组作为函数参数时的退化问题
C 语言里写函数参数时,void f(int arr[])和void f(int *arr)是完全等价的。数组参数被“调整”成了指针参数,所以函数内部对arr使用sizeof,得到的永远是指针大小。
#include <stdio.h> void print_size(int arr[]) { printf("inside function: %zu\n", sizeof(arr)); } int main(void) { int data[10]; printf("outside: %zu\n", sizeof(data)); // 40 print_size(data); // 8 return 0; }这个问题的根源是效率:如果直接把整个数组拷贝进函数,参数传递开销太大,所以 C 设计成传首地址。
要正确处理数组长度,最常用的办法是调用点算好长度再传进去:
void print_size(int arr[], size_t n) { printf("array bytes maybe: %zu\n", n * sizeof(arr[0])); }这里sizeof(arr[0])还是安全的,因为arr退化成指针后,arr[0]的类型是int,不是数组。C++ 开发者可以用模板引用捕获数组维度,做到更省心:
template <typename T, size_t N> void print_size(T (&arr)[N]) { std::cout << sizeof(arr) << '\n'; // 这里是真正的数组大小 }但在 C 里,老老实实传长度是最稳的做法。
4.4 malloc时用sizeof *p可以避免类型重复
在malloc分配指针数组时,很多人会写:
int *p = malloc(10 * sizeof(int));这没有错,但如果你把类型从int改成long,这里要改两处:一处是p的声明,一处是sizeof(int)。我习惯写成:
int *p = malloc(10 * sizeof(*p));*p的类型就是int,所以sizeof(*p)等价于sizeof(int)。这样以后如果把p改成long *,malloc 这一行不用动。这算是我个人很喜欢的一个小习惯,能少改一行是一行,还能避免类型不同步造成的 bug。
5. 结构体、联合体与对齐:sizeof背后的对齐规则
5.1 结构体sizeof不等于成员之和
任何一个在 C 语言里写过结构体的人,迟早都会遇到这个经典的“意外”:
#include <stdio.h> struct S { char c; int i; }; int main(void) { printf("%zu\n", sizeof(struct S)); return 0; }看起来char占 1 字节,int占 4 字节,加起来应该是 5。但实际输出往往是 8。
原因是“对齐”(alignment)。很多处理器访问特定类型数据时,要求地址必须是 2、4 或 8 的倍数,否则轻则性能下降,重则直接触发硬件异常。编译器为了让结构体成员满足各自的自然对齐,会在成员之间插入填充字节(padding)。
在上面的例子里,结构体布局是:
- 偏移 0:
char c - 偏移 1~3:填充
- 偏移 4~7:
int i
结构体总大小还要满足结构体自身对齐要求,而结构体对齐要求通常等于最大成员对齐要求,也就是int的 4 字节,因此总大小是 8,而不是 5。
5.2 联合体sizeof的规则
联合体union和结构体的规则相反:所有成员共享同一块内存,所以sizeof(union)等于最大成员的大小,同时还要满足最大成员的对齐要求。
#include <stdio.h> union U { char c; int i; double d; }; int main(void) { printf("%zu\n", sizeof(union U)); // 通常 8 return 0; }这里double是 8 字节,所以联合体大小至少是 8。如果换成char和long long,结果也会跟着最大成员走。
联合体常用于协议解析、类型双关、或者需要在同一块内存里复用不同布局的场景。但在 C++ 里,用带非平凡构造函数的对象作为union成员会有限制,更安全的替代方案是std::variant。C 语言里倒是直接很多,我常在嵌入式通信协议里用union把字节数组映射到不同字段视图。
5.3 手动优化结构体布局的实践
因为sizeof受对齐影响,所以结构体成员顺序不同,占用的空间可能差别很大:
struct BadLayout { char c1; int i; char c2; }; // 常见大小 12 struct GoodLayout { int i; char c1; char c2; }; // 常见大小 8第一个结构体里,char c1后面填充 3 字节,int i放在偏移 4 的位置,之后char c2再占 1 字节,最后还要补齐到 4 的倍数,所以变成 12。第二个结构体里,int放最前面占 4 字节,两个char占 2 字节,总大小 6,对齐到 4 倍数是 8。
所以一个非常实用的经验是:把结构体成员按占用空间从大到小排列,通常能减少填充。
不过这只是经验,不是绝对定理。因为有的时候你还要考虑成员顺序的语义可读性。比如一个网络协议头,字段顺序必须和字节流完全一致,这时候为了保持布局,你宁愿牺牲一些填充,甚至用#pragma pack强制紧凑排列:
#pragma pack(push, 1) struct ProtocolHeader { uint8_t version; uint16_t length; uint32_t seq; }; #pragma pack(pop)#pragma pack(1)会让所有成员按 1 字节对齐,结构体大小等于成员大小之和。代价是某些平台可能产生非对齐访问,性能下降甚至崩溃,所以只能在明确需要紧凑二进制布局时使用。
如果你想看每个成员的偏移量,可以用offsetof宏:
#include <stdio.h> #include <stddef.h> struct S { char c; int i; }; int main(void) { printf("offset c = %zu\n", offsetof(struct S, c)); printf("offset i = %zu\n", offsetof(struct S, i)); return 0; }注意:offsetof定义在<stddef.h>中,sizeof不需要头文件,但offsetof需要。很多人把这两者搞混,实际上它们是不同层面的东西。
6. sizeof与表达式求值:不执行表达式的特性
6.1 sizeof(表达式)不计算表达式
这是sizeof最容易被忽略的语义:它只关心类型,不关心值,所以表达式里的副作用不会发生。
#include <stdio.h> int foo(void) { printf("foo called\n"); return 0; } int main(void) { int i = 0; printf("sizeof(i++) = %zu\n", sizeof(i++)); printf("sizeof(foo()) = %zu\n", sizeof(foo())); printf("i = %d\n", i); return 0; }上面这段代码,foo called不会打印,i仍然等于 0。因为sizeof根本不需要执行foo()或i++,它只要知道foo()的返回类型是int、i++的类型是int,就能返回大小。
这个特性在泛型代码里很有用。比如 C++ 的模板元编程里,经常可以看到:
template <typename T> struct TypeSize { static constexpr size_t value = sizeof(T); };它不用创建任何T类型的对象,也不会真的去构造对象,就能拿出sizeof(T)。
6.2 VLA的sizeof例外:运行期求值
C99 引入了变长数组(VLA),允许在运行时用变量指定数组大小:
int n = 20; int arr[n]; printf("%zu\n", sizeof(arr)); // 运行期计算,等于 20 * sizeof(int)这里的sizeof(arr)不再是一个编译期常量,因为编译器在编译时不知道n的值。所以 VLA 的sizeof是 C 标准里一个难得的例外,它会在运行期被计算。
VLA 虽然在 C 里合法,但我不建议在需要长期维护的代码里滥用,原因有两个:一是 VLA 在栈上分配,数组长度不可控时容易爆栈;二是 C++ 标准并不支持 VLA,写出的代码没法在 C++ 编译器里直接复用。如果确实需要动态长度数组,优先考虑malloc或 C++ 的std::vector。
6.3 sizeof与运算符优先级的坑
sizeof的优先级和一元运算符同级,从右往左结合。所以sizeof a + 1会被解析成(sizeof a) + 1,而不是sizeof(a + 1)。
如果想让sizeof作用于整个表达式,括号不能省:
int a = 10; size_t s1 = sizeof a + 1; // 等价于 sizeof(int) + 1 size_t s2 = sizeof(a + 1); // 求 a+1 的类型大小,通常还是 sizeof(int)类似的还有:
sizeof *p + 1很多人本意可能是sizeof(*p) + 1,但如果你写成了sizeof *p + 1,结果其实也是sizeof(*p) + 1,因为*的一元优先级高于+。这里真正容易踩的是下面这种:
int *p; size_t s = sizeof p + 1; // 指针大小 + 1 size_t t = sizeof(p + 1); // 表达式 p+1 的类型仍然是 int*,所以还是指针大小最后一条稍稍有点反直觉:无论 p 有没有实际指向有效内存,sizeof(p + 1)都不会解引用 p,也不会做加法。C++ 里这样写甚至不能为未定义行为负责,因为加法本身没执行。所以别试着用sizeof去“试探”表达式会不会崩,它不干活。
7. sizeof的常见面试题与实战排查
7.1 常见面试题速查表
我整理了实际面试和被问时最高频的一组例子,假设环境是 64 位平台、int4 字节、指针 8 字节:
| 代码 | 结果 | 考察点 |
|---|---|---|
sizeof(char) | 1 | char恒定为1 |
sizeof("hello") | 6 | 字符串字面量含'\0' |
char buf[20]; sizeof(buf) | 20 | 数组名不退化 |
char *p = buf; sizeof(p) | 8 | 指针大小与指向类型无关 |
void f(int a[]) { sizeof(a); } | 8 | 数组参数退化为指针 |
int a[10]; sizeof(a)/sizeof(a[0]) | 10 | 经典数组长度公式 |
struct { char c; int i; }; | 8(常见) | 结构体对齐填充 |
union { char c; int i; double d; }; | 8(常见) | 联合体取最大成员 |
int i=0; sizeof(i++) | 4,i仍为0 | 表达式不求值 |
如果你能完全说清每一行的来龙去脉,sizeof这一关基本就算过了。
7.2 实际项目中的避坑清单
结合这些年做项目和看别人代码的经验,我把最容易踩的坑整理成一份清单:
- 不要用
sizeof判断动态内存长度。malloc出来的内存没有长度元信息,必须自己维护长度字段。 - 不要在函数参数里用
sizeof(arr)/sizeof(arr[0])。参数已经退化成指针,结果只会是指针大小除以元素大小,完全是错的。 - 跨平台的时候不要写死
sizeof(int)和sizeof(long)。用<stdint.h>里的固定宽度类型更稳妥。 - 结构体序列化别图方便直接
fwrite(&obj, sizeof(obj), 1, fp)。填充字节和对齐在不同平台上不一致,除非你用#pragma pack或者明确知道布局。 - C++ 多态类上不要用
sizeof(*basePtr)去拿动态类型大小。sizeof是编译期机制,只能得到静态类型大小,无法知道虚函数背后真正的派生类大小。 sizeof不需要头文件,但size_t需要。这是两个问题,别在面试时漏嘴。malloc时用sizeof(*p)而不是sizeof(type),改类型时少改一处,也不容易出错。
C++ 里还有个常见误区:很多人问sizeof能不能代替std::size来拿数组大小。C++17 提供了std::size(arr),返回元素个数,而sizeof(arr)返回字节数。两者用途不同,一般建议在 C++ 里直接写std::size(arr)更语义化,C 代码里再用sizeof(arr)/sizeof(arr[0])。
7.3 什么时候不要用sizeof
不是所有场景都该第一时间想到sizeof。我个人的习惯是这么区分的:
如果我要知道“这块缓冲区有多大”,用sizeof;如果我要知道“这个字符串有几个字符”,用strlen;如果我要知道“这个std::vector里存了几个元素”,用.size();如果我要知道“这个指针指向的堆内存有多大”,对不起,标准字节没有这种能力,靠自己的长度字段。
还有一个容易被忽视的场景:不要拿sizeof去比较表达式类型是否相同。比如:
if (sizeof(int) == sizeof(long)) { ... }这在类型宽度相同的平台上是成立的,但你其实无法确定int和long是同一个类型。如果代码后续要做重载或模板特化,这种判断会误导人。正确的做法是用类型特征,比如 C++ 里的std::is_same_v<int, long>。
7.4 我最后想分享的一个习惯
我在项目里通常会把数组元素个数计算封装成宏或函数模板,而不是散落在业务代码里到处写sizeof(arr) / sizeof(arr[0])。C 语言里可以这样:
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))然后要求团队规则:这个宏只能用在“当前作用域里能看到的真实数组”上,绝不能用在指针上。配合编译器的-Wsizeof-pointer-div警告,在 GCC 上还能提前拦截一部分问题。
用 C++ 的话,直接写:
template <typename T, size_t N> constexpr size_t ArraySize(T (&)[N]) noexcept { return N; }这样数组引用不会退化成指针,比宏更安全。函数参数传数组进去,也能正确得到元素个数。
说到底,sizeof不是难懂的天书,它只是特别讲究“场景”:是数组还是指针?是编译期还是运行期?有没有对齐填充?把这些边界想清楚,这个运算符就能成为你写安全代码的利器,而不是面试背完就忘的知识点。