news 2026/10/1 4:24:21

C语言sizeof深度解析:从运算符本质到内存对齐实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言sizeof深度解析:从运算符本质到内存对齐实战

C语言里的 sizeof,你真正弄懂了吗?我在不少技术交流群里见过这样的场景:有人问sizeof(int)和sizeof(int*)是不是一回事,底下能吵好几页。还有人写代码时用sizeof(指针)去算数组长度,结果跑出个固定 8,找半天不知道挂在哪儿。更有面试题问sizeof(++i)里i到底加没加,能难倒一片学了一两年 C 的人。

这个关键字太有迷惑性了:它写出来像函数调用,实际上是运算符;它看起来是"算大小",却涉及到编译期求值、类型推断、对齐填充这些底层机制。把 sizeof 吃透,你才算真正跨过了 C 语言基础到进阶之间的那道坎。这篇文章我打算把它彻底拆开揉碎,从运算符本质讲到各类型的大小计算,从sizeof和strlen的分家说到实战中的数组长度宏、内存分配,再附上我自己踩过的坑和一套面试高频题。不管你是刚接触指针的大一新生,还是被结构体对齐折磨过的老手,这篇都能给你点新东西。

1. sizeof 的本质:一个运算符,不是函数,也不在 stdio.h 里

1.1 为什么总有人把它当成函数?

先说个最基础的误会。很多人第一次见到sizeof(int),看到后面的括号,自然就把它归到了函数那一类。再加上网上不少地方会说"使用 sizeof 函数需要头文件",这就更误导了。实际上 sizeof 是 C 语言的关键字,是 33 个运算符之一,和+、*、&属于同一类东西,根本不需要任何头文件,stddef.h里只是定义了它的结果类型size_t而已。

区分它到底是不是函数,有个最简单的办法:函数调用必须带括号,而 sizeof 带不带括号分情况。正常写法是sizeof(int),但如果你对一个变量用,完全可以写成sizeof a,注意这里没有括号,照样合法。反过来,sizeof int在语法上反而是错的——因为它后面的表达式是一个"类型名"时,必须用括号括起来。这条规则细想很有意思:sizeof(int)里的括号并不是函数调用的语法,而是类型转换运算符()的变体。和它类似的还有(、)这类强制转换写法,都是单目运算符的语法结构。

我见过一种特别直观的理解方式:sizeof 就好比停车场入口的电子牌,上面显示"本车位最多停 5 米长的小车"。它读取的是车辆的"规格型号",而不是真的把车开进去量一圈。这里的"规格型号"就是 C 语言里的类型。

1.2 编译期求值:它什么时候会"算"?

大多数情况下,sizeof 的求值发生在编译期。编译器看到sizeof(int),直接就在目标代码里把它替换成一个常量 4(x86-64 Linux 平台),根本不会留到运行时。这意味着sizeof的结果可以用在要求编译期常量的地方,比如声明数组长度:

int a[sizeof(int) * 4]; // 编译通过,等价于 int a[16];(x86-64 下)

这在写底层代码时特别方便。举个我实际用过的例子:写通讯协议时,我需要定义一个缓冲区,大小正好能装下 4 个时间戳结构体,如果直接写4 * sizeof(MyTimeStruct),后人改结构体大小,缓冲区会自动跟着变,不用来回改两处。

唯一的例外是 C99 引入的变长数组(VLA)。请看:

int n = 5; int arr[n]; // 变长数组,长度运行时才确定 printf("%zu\n", sizeof(arr)); // 这个 sizeof 是运行时求值

VLA 的大小要等到n真正有值才能算出来,所以这里的sizeof(arr)必须在运行时计算。有些教材为了简化说"sizeof 永远是编译期求值",考试时你可以答"绝大多数情况是,但 VLA 是特例",这个细节能让你在讨论中显得比一般人扎实。

1.3 一个隐藏很深的特性:sizeof 不执行表达式

这个特性可以说是 sizeof 最反直觉的地方。看这段代码:

int i = 5; sizeof(++i); printf("%d\n", i); // 输出是 5,不是 6

sizeof(++i)并不会真的让i自增。原因在于 sizeof 的语义是"根据表达式的类型推断大小",它只需要知道++i的类型是int,就能确定大小是 4,至于这个表达式是否真的执行,编译器根本不关心。你可以把这里的表达式理解成一个"类型签名",sizeof 只拿签名,不跑代码。

我拿这个点考过带的新人,他给出的解释是"这可能是编译器优化掉了吧",实际上跟优化无关,而是语言标准的规定。明白了这条,你就知道sizeof(i++)、sizeof(a = b)这类东西全都不产生副作用。写代码时有个坑跟着就来了:如果你想用sizeof顺便实现什么计数、赋值操作,那纯属想多了。

2. 各类型的大小到底怎么算?从 int 到结构体

2.1 基础类型:标准只保证最小值

很多人问"int 在 C 语言里到底占几个字节",标准答案不是 4,而是"至少 2 个字节"。C 标准给的是下限保证,真正的值取决于编译器、平台和 ABI。在目前主流的 x86-64 平台上,常见的 Linux + GCC 环境下实测是这样的:

类型大小(字节)
char1
short2
int4
long8(Windows 上为 4)
long long8
float4
double8
size_t8(32 位平台为 4)
任意指针8(32 位平台为 4)

注意long在 Windows 和 Linux 上不一样,这是我在做跨平台库时踩过的雷。同一份代码,Windows 下写的二进制结构在 Linux 下读出来全错位。从那以后我把凡是跨平台的文件格式一律用int32_t、int64_t这类固定宽度类型,而不是int、long。sizeof 在这里的作用就是帮你确认这些固定宽度类型的实际大小。

sizeof(char)永远等于 1,这是 C 标准里写死的。所以sizeof(char) * 100其实就是 100,但写出来更清晰,也能提示读者这是 100 个 char 元素的字节数。我习惯在分配 char 缓冲区时都写上sizeof(char),虽然有点仪式感,但代码更显性。

2.2 数组:sizeof 里,数组名不会退化

数组名在 C 语言里是个磨人的小妖精。日常写代码时,a传给函数、赋给指针,都会"退化"成指向首元素的指针。但有两个场景它不退化:一个是取地址&a,另一个就是 sizeof。

int a[10]; printf("%zu\n", sizeof(a)); // 40,整个数组的大小 printf("%zu\n", sizeof(a[0])); // 4,单个元素大小 printf("%zu\n", sizeof(&a[0])); // 8,指针大小 printf("%zu\n", sizeof(&a)); // 8,指向数组的指针大小

多维数组也遵循同样的逻辑。int b[3][4]的类型是int[3][4],sizeof 直接得到3 * 4 * 4 = 48。如果你不小心取了b[0],sizeof 结果就是 16(一行的大小),b[0][0]是 4(一个元素)。这套递推关系其实就是"数组名是整体"在 sizeof 眼中的体现。

这里有个最关键的应用场景:为什么在函数里用sizeof(arr)/sizeof(arr[0])求数组长度会失效?因为函数形参里的数组声明只是个语法糖:

void foo(int arr[]) { // 形参 arr 其实是个 int*,不是数组 printf("%zu\n", sizeof(arr)); // 8(64位平台) }

不管调用方传入多大的数组,arr在函数内部都是一个指针变量,sizeof 拿到的是指针大小。这就是"数组传参必须带长度参数"的根本原因。我见过一个同学把int arr[]看成数组,在函数里大用sizeof(arr)/sizeof(arr[0]),结果数组长度永远是 1(因为 8/4=2 或 8/8=1),循环少跑好几轮,数值算出来全错。这个教训很典型。

2.3 结构体、联合体和位域:你以为算好了,编译器偷偷填充了

结构体的 sizeof 是初学者的重灾区。看一个例子:

struct A { char c; // 1 字节 int i; // 4 字节 }; printf("%zu\n", sizeof(struct A)); // 结果是 8,不是 5

为什么是 8?因为结构体成员要满足内存对齐规则。i是 4 字节类型,它的起始地址必须是 4 的整数倍,于是c后面被编译器塞了 3 个填充字节。这就像排队买票,每个人必须站在自己那组格子起点,排不齐就空几个位置。这些空出来的字节,在 sizeof 里是要算进去的。

联合体(union)就好算了:它所有成员共享一块内存,所以大小等于最大成员的大小(还要考虑整个联合体的对齐需求)。如果成员里有数组,情况就变成"最大成员"是那个数组。举两个实际算过的例子:

union B { int n; // 4 字节 char buf[10]; // 10 字节 }; // sizeof(union B) == 12,而不是 10 // 因为 union 的对齐要求是 4,10 会填充到 12 struct C { char c; int n; char buf[5]; }; // 第一个布局:c 占1字节,填充3字节,n 占4字节,buf占5字节,整体还要对齐到4,所以结果是 16

结构体成员顺序也会影响 sizeof。把大类型往前放、小类型往后排,有时能减少填充,把 16 变成 12。我写嵌入式代码时经常刻意调整成员顺序来压缩结构体大小,这在资源紧张的 MCU 上不是洁癖,而是实打实的 RAM 优化。

位域的大小规则又是另一套。struct { int a : 3; int b : 5; };里两个成员加起来 8 位,但 sizeof 结果通常是 4(一个int的存储单元),因为位域被整体塞进了一个 int 里。搞清楚这点,设计协议头的标志位时就能准确预测结构体大小。

还有个冷知识:标准 C 不允许空结构体(大小为 0),但 GCC 的 GNU 扩展允许,且sizeof(空结构体)等于 0。这个知识点在很多编译原理的讨论里会出现,虽然日常写业务代码用不上,但面试时能提一嘴会显得你确实读过标准的边边角角。

3. sizeof 和 strlen:一字之差,天壤之别

3.1 一个看类型,一个数到 \0

这是 C 语言里最经典的一对"易混兄弟"。先看代码:

char str[] = "Hello"; printf("%zu\n", sizeof(str)); // 6,数组本身的大小,包括结尾的 '\0' printf("%zu\n", strlen(str)); // 5,数到 '\0' 之前有多少个字符

sizeof(str)回答的是"这个变量占几个字节",答案从头到尾不变;strlen(str)回答的是"这个字符串有多长",它要运行时从头开始遍历,直到碰到'\0'才停。一个是编译期静态结论,一个是运行期扫描行为,本质完全不同。

把这个概念推到指针上,差别就更明显了:

char *p = str; printf("%zu\n", sizeof(p)); // 8(64位平台),p 是一个指针变量 printf("%zu\n", strlen(p)); // 5,还是数到 '\0',跟 p 是不是指针无关

sizeof(p)是"这个指针变量本身占几字节",跟它指向什么完全无关。我见过有人写sizeof(p)想拿字符串长度,拿回来个 8,然后一头雾水。分清这两者是 C 语言学习的分水岭,我甚至觉得比搞懂指针还重要。

3.2 实际使用时的差别坑

处理字符串拷贝时,strcpy和memcpy的选择就受这对概念影响:

char src[] = "Hi"; char dst[8]; strcpy(dst, src); // 把 'H' 'i' '\0' 三个字节拷贝过去 memcpy(dst, src, sizeof(src)); // 同样的效果,因为 sizeof(src) == 3 // 但如果你写 memcpy(dst, src, strlen(src)); // 只拷了 'H' 'i' 两个字节,dst 结尾没有 '\0',后续 printf 会越界读到随机内存

这个坑在拼接字符串时极常见。比如两个字符串拼接,用strcpy之后再strcat,逻辑正常;但如果有人用strncpy或memcpy指定长度时选了strlen的结果,丢掉了结尾的\0,拼接结果就变成一个没有终止符的"野字符串"。写协议解析时比较字符串也要注意:memcmp(a, b, sizeof(a))和memcmp(a, b, strlen(a))的结果可能不一样,因为 sizeof 版本会把\0也参与比较。

3.3 常见误用场景:拿 sizeof(指针) 当长度

最经典也是最危险的误用,是函数传数组后想用 sizeof 求长度。前面已经提过形参退化的坑,这里补充一个完整例子:

void print_array(int arr[]) { for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } } int main() { int data[] = {1, 2, 3, 4, 5}; print_array(data); // 期望打印 5 个元素,实际只打印 1~2 个(取决于指针大小和元素大小的比值) }

在 64 位平台上,sizeof(arr) / sizeof(arr[0])是8 / 4 = 2,循环只跑两遍;在 32 位平台是4 / 4 = 1,只打印第一个元素。这种 bug 非常隐蔽,因为它不崩溃、不报错,只是结果乖乖地缺了一段。排查起来往往要花半天。

我的建议是:函数接口里如果要传数组,就老老实实两个参数一起给——数组指针 + 元素个数。比如:

void print_array(int *arr, size_t n);

这样调用方用print_array(data, sizeof(data) / sizeof(data[0]));来传长度,清晰又安全。就算有一天你学会了更高级的"宏推断数组长度"技巧,也别在跨函数场景里用,那是给自己埋雷。

4. sizeof 的实战应用:从数组长度宏到内存分配

4.1 最经典的数组长度宏

C 语言没有内置的"获取数组元素个数"的方法,所以社区约定俗成用一个宏:

#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))

注意最后那个(a)[0]的括号,不是随手写的。如果传入的a是一个复杂表达式,比如matrix[i + 1],不加括号会被拆错。而且这种写法还能在编译期顺带检查a是不是数组——如果传入指针,sizeof(a)和sizeof((a)[0])的比例就不对,虽然不会编译失败,但结果一眼就能看出问题。

实际用起来,它能帮你省掉一票魔法数字:

int scores[] = {78, 92, 85, 63}; for (size_t i = 0; i < ARRAY_SIZE(scores); i++) { printf("%d\n", scores[i]); // 以后往数组里加元素,循环自动适应 }

改数组内容加元素,不用改循环次数,这是我最享受的"改一处、全自动"体验。写命令表、配置表、状态机跳转表的时候,这个宏几乎是必需品。比如状态机里每个状态对应一个处理函数,用这个宏算表长,加新状态只改表本身,跳转逻辑完全不用动。

4.2 malloc 分配内存时少写死 4

新手写malloc(n * 4)是个常见毛病,这背后默认了 int 是 4 字节。可一旦移植到 16 位 DSP 或某些嵌入式平台,int 可能是 2 字节,分配的内存就少了一半,程序运行时数组越界,崩溃得莫名其妙。正确写法是:

int *p = (int*)malloc(n * sizeof(int)); if (p == NULL) { // 处理分配失败 }

这里sizeof(int)的意义不是"显得专业",而是让分配大小自动跟随平台的 int 大小。C 语言特别强调可移植性,sizeof 就是最基础的可移植性工具之一。

还有一个容易漏掉的细节:malloc的参数类型是size_t,你传n * sizeof(int)时,如果n是int且很大,n * sizeof(int)这个乘法先按int运算,可能溢出成负数,再隐式转成巨大的 size_t,导致 malloc 返回 NULL。我写这类代码时习惯把n也定义成size_t,或者先强转一下:

int *p = (int*)malloc((size_t)n * sizeof(int));

4.3 结构体拷贝、序列化和文件读写

结构体的整体拷贝在 C 里很常用,最安全的方式是 memcpy:

struct Point { int x; int y; }; struct Point a = {3, 5}; struct Point b; memcpy(&b, &a, sizeof(a));

这里用sizeof(a)而不是写死 8,是因为如果以后给 Point 加了字段,比如char label[16];,拷贝代码不用改,还会自动带上填充字节一起拷贝。反过来,如果你用sizeof(a)做整体比较,memcmp(&a, &b, sizeof(a))时,填充字节的随机值可能导致比较失败。尴尬的地方来了:结构体里明明字段全相等,memcmp 却返回不相等。我调试这类问题时的经验是:该比较就不要用 memcmp,逐字段比较才行。

文件读写也有同样的坑:

fwrite(&point, sizeof(point), 1, fp);

直接写结构体到文件,效率很高,但文件格式依赖结构体布局。如果你的程序只在同一个平台自己读写,没问题;但如果是协议文件要给别的程序解析,填充字节会带来格式不确定性。我在做跨端同步功能时,就吃过这个亏:A 端写的二进制文件,B 端因为对齐方式不同,读出来全错。后来统一改用逐字段序列化,输出固定字节序。虽然代码多写几行,但一劳永逸。

4.4 缓冲区剩余空间计算

写字符串拼装、日志输出、协议打包时,经常要算缓冲区还剩多少空间。这时候 sizeof 能优雅地完成计算:

char buffer[1024]; size_t pos = 0; snprintf(buffer + pos, sizeof(buffer) - pos, "name=%s", name); pos = strlen(buffer); snprintf(buffer + pos, sizeof(buffer) - pos, "&age=%d", age);

每次 snprintf 的第二个参数都从"缓冲区总大小 - 已用位置"算剩余空间,防止越界写。如果缓冲区大小变化,只要改声明处的数组大小,后面所有计算自动跟着变。这种写法在嵌入式日志系统里特别实用,我写过一版只靠 1KB 内存完成多级日志缓冲,靠的就是这招。

4.5 一个容易被忽略的小技巧:用 sizeof 模板泛型编程

C 语言没有真正的泛型,但宏 + sizeof 可以实现简单的类型自适应。比如一个宏:

#define MAX_OF(a, b) ((a) > (b) ? (a) : (b))

看起来啥类型都能比,但有个隐藏问题:a、b可能被求值两次(有副作用)。如果用 sizeof 结合_Generic,就能做类型分派:

#define TYPE_TAG(x) _Generic((x), \ int: 1, \ long: 2, \ double: 3, \ default: 0)

虽然这已经超出 sizeof 本身,但 sizeof 在宏里天然跟类型打交道,理解这点能帮你写出类型安全的宏。

5. 面试与考试高频考点:sizeof 最容易考倒人的地方

5.1 经典送命题:sizeof 和 ++ 的组合

前面已经提过sizeof(++i)不执行副作用,这是面试官最爱出的点。延伸出来的变体还有:

int a = 1, b = 2; sizeof(a = b); // a 和 b 都不变 sizeof(a++); // a 不变 sizeof(a + b); // 这俩当然更不会变,因为本来就没有副作用

真正理解的原因还是那句话:sizeof 在编译期只关心表达式的类型,不产生运行时求值。所以表达式的副作用对 sizeof 来说完全是空头支票。如果你被问到"sizeof 会不会导致未定义行为",比如sizeof(i)其中 i 是未初始化的,答案是:不会,因为 sizeof 不读取 i 的值,它只是查类型。

5.2 C 和 C++ 的 sizeof 差异:字符字面量

这个考点容易让人猝不及防:

printf("%zu\n", sizeof('a'));

在 C 语言里,字符字面量'a'的类型是int,所以结果是 4(x86-64 下)。在 C++ 里,'a'的类型是char,结果是 1。同一个表达式,两种语言给出不同答案。

这个差异背后是 C 的历史包袱:C 语言诞生时没有函数重载,字符字面量按 int 处理可以在函数调用时方便做类型提升。到了 C++,因为有了重载和模板,字符字面量保持 char 更精确。平时写 C 代码没感觉,一旦你写跨语言的项目,或者面试时被问到,这一下就能看出功底。

5.3 函数、不完全类型和空数组

sizeof(printf("hi")); // 这是非法的,printf 返回类型是 int,但 sizeof(函数调用) 不合法 sizeof(void); // 标准 C 非法,GCC 扩展返回 1 int arr[]; // 不完全类型,在声明处 sizeof(arr) 非法

函数调用不能作为 sizeof 的操作数,因为 C 语言要求 sizeof 的操作数是"完整类型的表达式",函数调用虽然有返回值类型,但函数本身不属于对象类型。而int arr[]这种未指定长度的数组声明属于"不完全类型",sizeof 拿不到大小,只能在别处初始化后才有长度。

5.4 结构体对齐导致的结果变化

我再出一个实战型考点:

struct X { char a; // 1 int b; // 4 char c; // 1 }; printf("%zu\n", sizeof(struct X));

直觉会以为是 6(1+4+1),实际是 12。原因:b 要 4 字节对齐,所以 a 后补 3 字节,c 占 1 字节,整个结构体还要是 4 的倍数,所以再补 3 字节。12 字节。如果调整顺序:

struct Y { int b; // 4 char a; // 1 char c; // 1 }; printf("%zu\n", sizeof(struct Y)); // 8

同样是三个成员,换个顺序就从 12 变成 8。内存对齐是 C 语言里性价比极高的优化点,嵌入式开发中这种调整能省下不少 RAM。

5.5 一套自测代码

我把常考的点整合成一段代码,建议读者亲手编译运行,再对照答案:

#include <stdio.h> #include <string.h> struct S { char c; int i; char buf[5]; }; union U { int n; char bytes[10]; }; int main(void) { int a[10]; char str[] = "Hello"; char *p = str; int i = 5; printf("%zu\n", sizeof(a)); // 40 printf("%zu\n", sizeof(a[0])); // 4 printf("%zu\n", sizeof(p)); // 8(指针大小) printf("%zu\n", sizeof(str)); // 6 printf("%zu\n", strlen(str)); // 5 printf("%zu\n", sizeof(++i)); // 4,i 仍为 5 printf("%d\n", i); // 5 printf("%zu\n", sizeof(struct S)); // 16(前文推演过) printf("%zu\n", sizeof(union U)); // 12 return 0; }

每个结果的来历,如果你能不看答案说出来,sizeof 的基本功就算过关了。

5.6 常见错误速查表

错误写法问题原因正确做法
sizeof(arr)求数组长度(函数内)形参退化为指针传入长度参数
strlen(buf)求缓冲区大小缓冲区没有\0就崩用 sizeof
malloc(n * 4)平台 int 不是固定 4malloc(n * sizeof(int))
printf("%d", sizeof(x))格式符与 size_t 不匹配printf("%zu", sizeof(x))
sizeof(a = b)想顺便赋值sizeof 不执行表达式先赋值,再 sizeof
sizeof(void)标准 C 非法用sizeof(char)替代

6. sizeof 相关的踩坑实录与实用技巧

6.1 打印 size_t 必须用 %zu

这是我最开始写代码时最容易撞的坑。printf("%d", sizeof(x))在 64 位平台上能跑,但结果是错的——因为sizeof返回size_t(约等于unsigned long),用%d读取时格式不对齐,轻则打印出负数或乱码,重则可能影响后续参数解析。编译器开-Wall也会警告。正确写法是%zu,这里的z表示 size_t 类型。如果编译器比较老不支持%zu,也可以强转成unsigned long:

printf("%lu", (unsigned long)sizeof(x));

6.2 类型定义与 typedef 的配合

写大型项目时,我习惯给复杂类型加 typedef,然后用 sizeof 时直接用类型名:

typedef struct _Node { struct _Node *next; int data; } Node; Node *node = (Node*)malloc(sizeof(Node));

这样比sizeof(struct _Node)简短,改类型名时所有分配代码自动适应。但有个副作用:如果某天Node从结构体变成指针类型(比如改成别名),sizeof(Node)就从结构体大小变成指针大小,行为大变。所以 typedef 与 sizeof 搭配时,要留意类型变更带来的连锁影响。

6.3 sizeof 和宏参数的括号强迫症

还记得ARRAY_SIZE(a)宏里面(a)[0]的括号吗?这个括号必须加。假设我把它写成:

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))

看起来没问题,但如果调用时传入的是matrix[i+1],展开后就成了:

sizeof(matrix[i+1]) / sizeof(matrix[i+1][0]) // 少了括号,双重下标解析错乱

所以宏参数能加括号就一定加括号,这是写 C 宏的铁律,跟 sizeof 一起用更是如此。用宏返回类型大小的时候同理,尽量把所有参数都括起来。

6.4 struct 末尾的柔性数组,sizeof 不算它

C99 出了一个叫"柔性数组"的特性:结构体最后一个成员可以是不定长数组,而 sizeof 计算时不包含它的空间。

struct Packet { int len; char data[]; // 柔性数组,必须放最后 }; printf("%zu\n", sizeof(struct Packet)); // 4(只算 len,不包含 data)

实际用的时候,你分配sizeof(struct Packet) + actual_data_len的内存,把数据塞进data里。这个特性在协议缓冲区、消息队列里极其好用。很多人在面试里提到"结构体大小会不会算上柔性数组",说不会,但再追问一句"为什么",就答不上来了——因为柔性数组不占存储空间,它是个占位符号,指向的是紧跟结构体剩余空间的开头。

6.5 一次真实的对齐 bug 排查

最后分享一个我亲手排查过的大坑,也算把对齐问题讲得更有画面感。当年做一个网络通信模块,结构体长这样:

struct Msg { uint8_t type; // 1 uint32_t seq; // 4 uint16_t crc; // 2 };

在 x86 上编译,sizeof 是 12(因为 seq 要 4 字节对齐,type 后面补 3 字节,crc 后还要补 2 字节让整体对齐到 4)。可我一拍脑袋,想着按 1+4+2=7 字节的布局去解析网络数据,结果后排字段全部错位,CRC 校验永远失败。后来打印sizeof才发现问题。用#pragma pack(1)能强制紧凑排列,但代价是访问速度和跨平台兼容性都会受影响。所以后来我加了个编译期静态断言:

_Static_assert(sizeof(struct Msg) == 7, "Msg must be 7 bytes");

如果哪天成员改动导致大小变了,编译直接报错,提醒你该检查协议了。这种"以编译期错误代替运行期崩溃"的思路,在 C 语言工程里非常值得推广。把 sizeof 写进断言,等于给代码上了一道保险。

一些个人的体会

我写 C 年头不算短,回头看 sizeof,它真的是一个很"基础但不过时"的知识点。你甚至可以靠它检验自己的 C 功底:能准确说出sizeof在各种场景下的结果、能解释 VLA 的运行时求值、知道sizeof为什么不执行表达式、能在工程里用好数组长度宏和结构体对齐,这些加起来基本就是 C 语言"指针+内存"这一块的中级水平了。

最后再分享一个我自己写代码时的习惯:凡是涉及内存分配、数组遍历、结构体拷贝、协议打包的地方,我都会先在注释或者脑里过一遍"这行代码的 sizeof 和 strlen 分别是多少",然后再写下去。这个小习惯帮我挡掉了大量 bug,有时候一分钟的思考能省下半天调试。把这个思维方式内化,你就能从"写出能跑的代码"进阶到"写出不出错的代码"。

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

第一次作业不用慌:一套从拆解到交付的完整执行框架

“第一次作业”这四个字&#xff0c;恐怕是学生时代到职场生涯里&#xff0c;出现频率最高、也最容易让人心里发慌的场景了。我到现在还记得自己交第一份课程论文时的状态&#xff1a;材料下载了十几个&#xff0c;文档打开了一上午&#xff0c;光标在空白页上闪了一整天&#…

作者头像 李华
网站建设 2026/10/1 4:23:49

12dB全向铜丝天线DIY:9段螺旋+3mm铜管的高增益物理实现

简介&#xff1a;本资源是一份面向无线通信DIY爱好者与入门级射频实践者的高增益全向天线制作教程&#xff0c;聚焦解决低成本、易上手实现12dB增益全向辐射的现实难题。针对开槽天线成本高、电缆天线精度难控等痛点&#xff0c;方案采用铜丝铜管PVC管等常见材料&#xff0c;完…

作者头像 李华
网站建设 2026/10/1 4:22:13

端到端数字图像水印CNN毕设源码复现:从模型训练到避坑指南

简介&#xff1a;这份资源是围绕卷积神经网络实现端到端数字图像处理的代码复现项目&#xff0c;面向计算机相关专业正在做毕业设计、期末大作业或课程设计的学生&#xff0c;以及需要项目实战练习的学习者。项目经导师指导并认可&#xff0c;评审分达98分&#xff0c;可作为高…

作者头像 李华
网站建设 2026/10/1 4:21:51

AI Skills赋能数竞教研:学案制作一体化实战指南

学案的革命&#xff08;数竞版&#xff09;&#xff1a;竞赛教研与学案制作一体化 skills接触数学竞赛教研的老师应该都有同感&#xff1a;每周最耗时的事情&#xff0c;不是上课&#xff0c;而是做学案。找题、对难度、配解析、调格式、作图、排版&#xff0c;一套二试几何专题…

作者头像 李华
网站建设 2026/10/1 4:21:47

小程序开发全流程解析:合肥企业数字化转型的实用指南

1. 先聊清楚&#xff1a;数字化转型为什么要从小程序切入1.1 很多合肥老板问的第一个问题在合肥做本地化服务这行&#xff0c;这几年我见过太多老板拿着手机问我&#xff1a;我们公司到底要不要做小程序&#xff1f;做了能干嘛&#xff1f;说实话&#xff0c;这个问题背后藏着的…

作者头像 李华
网站建设 2026/10/1 4:20:32

垃圾桶边秒查分类:社区垃圾分类查询工具的设计与实现

帮住垃圾桶纠偏&#xff1a;我做了个社区垃圾分类指导工具&#xff0c;输入垃圾名直接出分类、时间、投放点社区里垃圾分类执行了大半年&#xff0c;桶前的督导员撤了之后&#xff0c;准确率肉眼可见往下掉。尤其是早晚高峰&#xff0c;厨余垃圾里混着塑料袋&#xff0c;可回收…

作者头像 李华