news 2026/10/1 1:11:24

C/C++ sizeof运算符详解:编译期求值、数组指针陷阱与内存对齐

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C/C++ sizeof运算符详解:编译期求值、数组指针陷阱与内存对齐

写 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位平台常见值备注
char11标准恒定为1
short22至少16位
int44至少16位,通常32位
long4在 Linux 上8,在 Windows 上4历史差异最大
long long88C99标准引入
float44通常 IEEE 754 单精度
double88通常 IEEE 754 双精度
指针48取决于地址空间

所以,如果你在做网络协议解析、文件格式读写、或者跨进程通信,不要假设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虽然都能和字符串扯上关系,但完全是两回事:

对比项sizeofstrlen
性质运算符,编译期求值库函数,运行期遍历
是否需要头文件不需要需要<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)1char恒定为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不是难懂的天书,它只是特别讲究“场景”:是数组还是指针?是编译期还是运行期?有没有对齐填充?把这些边界想清楚,这个运算符就能成为你写安全代码的利器,而不是面试背完就忘的知识点。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 1:10:18

DNF单机版搭建全流程:服务端架构、环境配置与连接排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:10:14

AI工程从零开始:数据契约、模型版本与确定性推理实战

1. 这不是“搭个LLM API”——AI工程从零开始的真实成本清单很多人看到“AI Engineering from Scratch”第一反应是&#xff1a;不就是调用OpenAI API、写个Flask后端、套个Streamlit前端&#xff1f;三小时搞定&#xff0c;发个GitHub仓库&#xff0c;标题一写“手把手教你构建…

作者头像 李华
网站建设 2026/10/1 1:09:46

基于YOLOv9的脑肿瘤检测系统:数据转换、训练与GUI部署实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 1:08:39

AI API安全实战:成本治理、限流与密钥管理的完整防线

1. 为什么先做这三块&#xff1a;AI接口安全的真实威胁模型先说一个我亲眼见过的事故。某个做内容审核的团队&#xff0c;接入大模型 API 做摘要提取&#xff0c;第一周风平浪静&#xff0c;第二周账单突然从每天几十块冲到上千块&#xff0c;月底一算超支七万。排了一整天&…

作者头像 李华