欢迎光临
我们一直在努力

memset 清不净——sizeof(指针) 只清 4 字节,传真实结构体大小

memset 清不净——sizeof(指针) 只清 4 字节,传真实结构体大小

文章标签:#嵌入式开发 #单片机 #C语言 #野指针 #内存越界

摘要:本文从一次刷卡门禁的"串号"事故切入,剖析 memset 清零结构体时只清了 4 字节的经典陷阱。核心在于 sizeof(指针) 永远是指针宽度而非对象大小——32 位机恒为 4。文章从指针本质讲起,对比 sizeof(p)、sizeof(*p)、sizeof(类型) 三种写法,用表格和决策图帮你快速判断,并附错误复现与正确代码实测对比,助你彻底避开"清了个寂寞"的坑。

一、开篇:那个"清了跟没清一样"的凌晨

去年做一款刷卡门禁控制器,每次重新配置用户参数前我会调用 memset(&user, 0, sizeof(user)) 把结构体清零。本地自测一切正常,可现场反馈:偶尔新录入的用户会"继承"上一个人的部分卡片号,导致刷错卡开门。复现率约千分之二,且只在连续快速换人录入时出现。

如果你写过"先清空再赋值"的代码,这一讲能帮你彻底避开 memset 只清了 4 字节这个经典陷阱。我会从指针本质讲起,给你一套不会翻车的清空套路,并附上对比实测。

全文导航:入门认知(指针与 sizeof 的本质)→ 进阶原理(为什么只清 4 字节)→ 高级应用(错误写法复现 + 正确清空代码 + 实测对比)。

二、入门认知:memset 到底在清什么

memset 的原型是 void *memset(void *s, int c, size_t n),第三个参数是"要清的字节数"。陷阱就在这个 n——很多人习惯写 sizeof(ptr),但 ptr 是指针变量时,sizeof(ptr) 是"指针本身的字节数"(32 位机 4、64 位机 8),而不是它指向的结构体大小。

类比:你让保洁阿姨"把那个文件袋清空",结果你递给她的是"写着文件袋地址的便签纸",她只把便签纸擦干净了,文件袋纹丝不动。

如图 1 展示指针变量与所指向结构体在内存中的关系:

#mermaid-svg-QYzgcrKUvo47Hq30{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QYzgcrKUvo47Hq30 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QYzgcrKUvo47Hq30 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QYzgcrKUvo47Hq30 .error-icon{fill:#552222;}#mermaid-svg-QYzgcrKUvo47Hq30 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QYzgcrKUvo47Hq30 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QYzgcrKUvo47Hq30 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QYzgcrKUvo47Hq30 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QYzgcrKUvo47Hq30 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QYzgcrKUvo47Hq30 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QYzgcrKUvo47Hq30 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QYzgcrKUvo47Hq30 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QYzgcrKUvo47Hq30 .marker.cross{stroke:#333333;}#mermaid-svg-QYzgcrKUvo47Hq30 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QYzgcrKUvo47Hq30 p{margin:0;}#mermaid-svg-QYzgcrKUvo47Hq30 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-QYzgcrKUvo47Hq30 .cluster-label text{fill:#333;}#mermaid-svg-QYzgcrKUvo47Hq30 .cluster-label span{color:#333;}#mermaid-svg-QYzgcrKUvo47Hq30 .cluster-label span p{background-color:transparent;}#mermaid-svg-QYzgcrKUvo47Hq30 .label text,#mermaid-svg-QYzgcrKUvo47Hq30 span{fill:#333;color:#333;}#mermaid-svg-QYzgcrKUvo47Hq30 .node rect,#mermaid-svg-QYzgcrKUvo47Hq30 .node circle,#mermaid-svg-QYzgcrKUvo47Hq30 .node ellipse,#mermaid-svg-QYzgcrKUvo47Hq30 .node polygon,#mermaid-svg-QYzgcrKUvo47Hq30 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QYzgcrKUvo47Hq30 .rough-node .label text,#mermaid-svg-QYzgcrKUvo47Hq30 .node .label text,#mermaid-svg-QYzgcrKUvo47Hq30 .image-shape .label,#mermaid-svg-QYzgcrKUvo47Hq30 .icon-shape .label{text-anchor:middle;}#mermaid-svg-QYzgcrKUvo47Hq30 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QYzgcrKUvo47Hq30 .rough-node .label,#mermaid-svg-QYzgcrKUvo47Hq30 .node .label,#mermaid-svg-QYzgcrKUvo47Hq30 .image-shape .label,#mermaid-svg-QYzgcrKUvo47Hq30 .icon-shape .label{text-align:center;}#mermaid-svg-QYzgcrKUvo47Hq30 .node.clickable{cursor:pointer;}#mermaid-svg-QYzgcrKUvo47Hq30 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QYzgcrKUvo47Hq30 .arrowheadPath{fill:#333333;}#mermaid-svg-QYzgcrKUvo47Hq30 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QYzgcrKUvo47Hq30 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QYzgcrKUvo47Hq30 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QYzgcrKUvo47Hq30 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QYzgcrKUvo47Hq30 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QYzgcrKUvo47Hq30 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QYzgcrKUvo47Hq30 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QYzgcrKUvo47Hq30 .cluster text{fill:#333;}#mermaid-svg-QYzgcrKUvo47Hq30 .cluster span{color:#333;}#mermaid-svg-QYzgcrKUvo47Hq30 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QYzgcrKUvo47Hq30 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QYzgcrKUvo47Hq30 rect.text{fill:none;stroke-width:0;}#mermaid-svg-QYzgcrKUvo47Hq30 .icon-shape,#mermaid-svg-QYzgcrKUvo47Hq30 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QYzgcrKUvo47Hq30 .icon-shape p,#mermaid-svg-QYzgcrKUvo47Hq30 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QYzgcrKUvo47Hq30 .icon-shape .label rect,#mermaid-svg-QYzgcrKUvo47Hq30 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QYzgcrKUvo47Hq30 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QYzgcrKUvo47Hq30 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QYzgcrKUvo47Hq30 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

指向

只清了这 4 字节

清整个结构体

指针变量 ptr(占 4 字节, 存地址 0x20001000)

结构体 user(占 64 字节)

memset(ptr,0,sizeof(ptr))

memset(ptr,0,sizeof(*ptr))

图 1:指针变量与所指向对象的内存关系,sizeof(指针)≠sizeof(对象)

适用边界:memset 适合清零/填充连续的裸内存;如果结构体含指针成员(如 char *name),memset 只能清掉指针值,不会释放它指向的堆内存,那属于另一类泄漏问题。

三、进阶原理:为什么只清了 4 字节

在 32 位 MCU(如 STM32F1/F4)上,任何指针变量 T *p 的 sizeof(p) 恒为 4。当你写下:

memset(p, 0, sizeof(p)); // 实际只清 4 字节 —— p 自己的值被置 0 了!

更糟的是,这里把指针本身清成了 NULL,后续若再用 p 访问结构体,直接硬 Fault。

而正确的两种写法:

memset(p, 0, sizeof(*p)); // 解引用取对象,得到真实结构体大小
memset(p, 0, sizeof(StructType)); // 用类型名,安全但需保证类型一致

易混淆点用表格对比:

写法sizeof 结果(32位)实际清掉的内存后果
sizeof(p) 4 仅指针变量自身 结构体没清,指针变 NULL
sizeof(*p) 结构体大小 整个结构体 正确清零
sizeof(StructType) 结构体大小 整个结构体 正确,但类型名易写错

[坑] 高频翻车点:把"数组名"和"指针"记混。数组名 arr 的 sizeof(arr) 是数组总字节数(正确),但函数参数里的 void *buf 退化成指针,sizeof(buf) 又变 4。一维数组传参进函数后同样会踩坑。

四、高级应用:复现与根治

错误写法演示

typedef struct {
uint32_t card_id[2];
uint8_t name[16];
uint8_t level;
} User_t; // 共 8+16+1 = 25 字节(含对齐约 28)

void reload_user(User_t *p)
{
memset(p, 0, sizeof(p)); // 错误:只清 4 字节,p 自身变 NULL
p->card_id[0] = read_card(); // 此时 p 已是 NULL,访问即 HardFault
}

逐行推演:p 是指针,sizeof(p)=4,memset 把 p 指向的"指针变量的 4 字节存储位置"清零——注意 p 是局部形参,清的是栈上那份 p 副本,原函数里调用方的指针不受影响,但 p 在函数内变 NULL,下一行解引用直接崩。

正确完整代码

#include <string.h>

typedef struct {
uint32_t card_id[2];
uint8_t name[16];
uint8_t level;
} User_t;

/* 写法一:解引用,推荐,类型随 p 自动跟随 */
void reload_user_safe(User_t *p)
{
if (p == NULL) return;
memset(p, 0, sizeof(*p)); // 清整个结构体
p->card_id[0] = read_card();
p->level = 1;
}

/* 写法二:传结构体本身(避免指针歧义) */
void clear_user(User_t *p)
{
if (p) memset(p, 0, sizeof(User_t));
}

改前 vs 改后实测对比

指标错误写法 sizeof§正确写法 sizeof(*p)
清零字节数(结构体28B) 4 28
连续换人录入串号概率 千分之二 0
是否 HardFault 是(指针变 NULL) 否

[提示] 如果结构体含柔性数组或动态指针成员,memset 后需单独重新赋值指针/重新分配,切勿以为"全清了就安全"。

下面这张决策图帮你快速判断该用哪种清零写法:

#mermaid-svg-PGpiZkr5mtlUexQs{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PGpiZkr5mtlUexQs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PGpiZkr5mtlUexQs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PGpiZkr5mtlUexQs .error-icon{fill:#552222;}#mermaid-svg-PGpiZkr5mtlUexQs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PGpiZkr5mtlUexQs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PGpiZkr5mtlUexQs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PGpiZkr5mtlUexQs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PGpiZkr5mtlUexQs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PGpiZkr5mtlUexQs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PGpiZkr5mtlUexQs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PGpiZkr5mtlUexQs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PGpiZkr5mtlUexQs .marker.cross{stroke:#333333;}#mermaid-svg-PGpiZkr5mtlUexQs svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PGpiZkr5mtlUexQs p{margin:0;}#mermaid-svg-PGpiZkr5mtlUexQs .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-PGpiZkr5mtlUexQs .cluster-label text{fill:#333;}#mermaid-svg-PGpiZkr5mtlUexQs .cluster-label span{color:#333;}#mermaid-svg-PGpiZkr5mtlUexQs .cluster-label span p{background-color:transparent;}#mermaid-svg-PGpiZkr5mtlUexQs .label text,#mermaid-svg-PGpiZkr5mtlUexQs span{fill:#333;color:#333;}#mermaid-svg-PGpiZkr5mtlUexQs .node rect,#mermaid-svg-PGpiZkr5mtlUexQs .node circle,#mermaid-svg-PGpiZkr5mtlUexQs .node ellipse,#mermaid-svg-PGpiZkr5mtlUexQs .node polygon,#mermaid-svg-PGpiZkr5mtlUexQs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PGpiZkr5mtlUexQs .rough-node .label text,#mermaid-svg-PGpiZkr5mtlUexQs .node .label text,#mermaid-svg-PGpiZkr5mtlUexQs .image-shape .label,#mermaid-svg-PGpiZkr5mtlUexQs .icon-shape .label{text-anchor:middle;}#mermaid-svg-PGpiZkr5mtlUexQs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PGpiZkr5mtlUexQs .rough-node .label,#mermaid-svg-PGpiZkr5mtlUexQs .node .label,#mermaid-svg-PGpiZkr5mtlUexQs .image-shape .label,#mermaid-svg-PGpiZkr5mtlUexQs .icon-shape .label{text-align:center;}#mermaid-svg-PGpiZkr5mtlUexQs .node.clickable{cursor:pointer;}#mermaid-svg-PGpiZkr5mtlUexQs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PGpiZkr5mtlUexQs .arrowheadPath{fill:#333333;}#mermaid-svg-PGpiZkr5mtlUexQs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PGpiZkr5mtlUexQs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PGpiZkr5mtlUexQs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PGpiZkr5mtlUexQs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PGpiZkr5mtlUexQs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PGpiZkr5mtlUexQs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PGpiZkr5mtlUexQs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PGpiZkr5mtlUexQs .cluster text{fill:#333;}#mermaid-svg-PGpiZkr5mtlUexQs .cluster span{color:#333;}#mermaid-svg-PGpiZkr5mtlUexQs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PGpiZkr5mtlUexQs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PGpiZkr5mtlUexQs rect.text{fill:none;stroke-width:0;}#mermaid-svg-PGpiZkr5mtlUexQs .icon-shape,#mermaid-svg-PGpiZkr5mtlUexQs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PGpiZkr5mtlUexQs .icon-shape p,#mermaid-svg-PGpiZkr5mtlUexQs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PGpiZkr5mtlUexQs .icon-shape .label rect,#mermaid-svg-PGpiZkr5mtlUexQs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PGpiZkr5mtlUexQs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PGpiZkr5mtlUexQs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PGpiZkr5mtlUexQs :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

数组名

指针

是, 已退化成指针

否, 局部指针

要清零的对象是什么?

是数组名还是指针?

sizeof(arr) 是数组总字节数可直接用

在函数参数里?

sizeof(buf) 恒为 4/8需单独传 size 参数

用 sizeof(*p) 解引用得到真实结构体大小

正确清零

图 4:清零写法决策流程图

五、收尾:要点复盘与最佳实践

核心要点:① sizeof(指针) 永远是指针宽度,不是对象大小;② 清零对象用 sizeof(*p) 或 sizeof(类型);③ 数组传参退化成指针后 sizeof 失效,函数内应额外传入长度。

最佳实践清单(可收藏):

  • 清零对象统一用 memset(p, 0, sizeof(*p)),类型自动跟随最安全。
  • 函数内清数组,一定要单独传 size 参数,别信 sizeof(buf)。
  • 结构体含指针成员时,memset 不清堆内存,需单独处理。
  • 把 sizeof(p) 当字节数用前,先问自己"p 是指针还是数组名"。
  • 关键清零逻辑加静态断言:_Static_assert(sizeof(*p)==预期, "大小不符")。

进阶延伸:C11 的 memset_s 可防止被编译器优化掉(敏感数据擦除场景必用);想整块清零大缓冲区,DMA 清内存比 CPU 循环更快。

互动:你踩过 sizeof(指针) 的坑吗?评论区聊聊你见过的"清了个寂寞"现场。

赞(0)
未经允许不得转载:171主机测评 » memset 清不净——sizeof(指针) 只清 4 字节,传真实结构体大小
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址