realloc 失败时原指针仍然有效:`p = realloc(p, n)` 这一行漏掉了一个临时变量 本文首发于 CSDN。结论都能用手册原文核对代码可以照着跑。一段跑了两年的日志采集程序某天被投诉「偶尔丢日志」。不是全丢是每次 OOM 前后丢掉最后几行。查了两天。程序的写法是这样的char*bufmalloc(1024);size_tcap1024;/* ... 读数据满了就扩容 ... */bufrealloc(buf,cap*2);cap*2;strcpy(bufused,line);重试逻辑对扩容逻辑对读逻辑也对。问题在那句赋值上realloc失败时返回NULL赋回buf之后原来那块内存的地址就丢了——既没释放也再也拿不到。紧接着strcpy往NULL上写程序崩在无关的地方而崩之前那几行日志已经写进「丢失的旧块」里。手册对失败的定义比大多数人以为的严格realloc的 RETURN VALUE 有一句话很多人从没读到那一行If these functions fail, the original block is left untouched; it is not freed or moved.失败时原块保持不变既不释放也不移动。这句话反过来读就是失败之后你还持有原指针它仍然有效你可以继续用它或者主动free掉。丢失地址不是realloc干的是那句赋值干的。正确的写法只有一个形状——先收进临时变量void*tmprealloc(buf,cap*2);if(tmpNULL){/* 原块还在buf 仍可用这里决定是退出还是保留旧容量 */free(buf);return-1;}buftmp;cap*2;还有三个附带结论同样写在手册里第一realloc可能搬家。the returned pointer may be the same as p if the allocation was not moved … or different from p if the allocation was moved to a new address.所以任何缓存了旧指针的地方链表节点、外部结构体、别的线程在realloc之后都必须更新否则就是悬垂指针。第二realloc(p, 0)在 glibc 上是个坑。手册的 BUGS 一节写得很直接The glibc implementation of realloc() is not consistent with that, and as a consequence, it is dangerous to call realloc(p, 0) in glibc.给出的绕法也很简单realloc(p, size ? size : 1)。真要释放就直接free别用realloc(p, 0)表达「缩到零」。第三reallocarray能挡住乘法溢出。手册对malloc(n * size)这种写法明确提了If the multiplication of n and size would result in integer overflow, then calloc() returns an error. By contrast, an integer overflow would not be detected in the following call to malloc()reallocarray(p, n, size)做的就是把这道检查加上。扩容逻辑里凡是「元素个数 × 元素大小」的乘法都该用它。为什么这个 bug 只在 OOM 时出现因为它需要一个「分配失败」的条件才会触发。条件触发概率现象机器内存充足几乎为零完全正常测试环境永远看不到容器内存接近上限低偶发崩溃日志里最后几条丢失开了 overcommit更低realloc返回非空真正写的时候才 OOMKill有RLIMIT_AS限制中可稳定复现但需要专门构造最后一行值得多说。手册 NOTE 里写了 Linux 默认的乐观分配策略when malloc() returns non-NULL there is no guarantee that the memory really is available也就是说realloc返回非空不代表内存真的到手了。这条会让「用返回值判断是否 OOM」的思路在高负载下失效进程可能直接被 OOM killer 干掉连失败分支都走不到。真正可控的复现方式是用setrlimit设一个RLIMIT_AS上限把分配压到失败。我们后来把它写进了单测每个扩容点都跑一次「限制到刚好不够」的用例。换成预分配加分段能省掉什么如果逻辑允许把「反复扩容」换成「按块分配 块指针数组」就不需要realloc了structchunk{char*p;size_tlen;};staticstructchunkchunks[64];/* 每块固定 64KB满了就开下一块永不 realloc */代价是读取时要跨块拼接收益是没有搬家、没有失败分支、没有悬垂指针。日志采集这种「只追加、不随机访问」的场景分段几乎总是更省心。顺带说两句工具valgrind能查泄漏但查不出「丢了指针」——因为那块内存在进程退出时还在只是没人引用。-fsanitizeaddress能抓住「往 NULL 上写」这个后果但抓不住根因。真正能抓住根因的是代码审查规则凡是对realloc的返回值直接赋给原指针的写法一律标记为可疑。这条规则执行下来我们代码库里同类写法清掉了十七处。这类基础题我先按手册条目编号把草稿归档归档表用 AI墨衍 维护再在 CSDN 上写成文章会员的发文额度够我一口气把一批草稿都存下来GEO 检测能看出文章在 AI 搜索里有没有被引用图文同步则省掉了每个平台重排一遍的时间https://mp.csdn.net/vip?utm_sourceITmoyan常见问题Qrealloc失败之后直接free(buf)对吗对。原块还在free它会正常释放。前提是这句赋值没发生——一旦buf NULL就没有块可释放了。Qrealloc(NULL, n)等价于malloc(n)吗等价。手册原文「If p is NULL, then the call is equivalent to malloc(size)」。Q缩小容量会不会失败一般不会但可能发生搬家。缩小也可能返回新地址所以临时变量的写法对缩小同样适用。Qcalloc和malloc哪个更适合数组calloc。它在乘法溢出时返回错误而malloc(n * size)不检查溢出calloc还顺手把内存清零。Q为什么我的程序在realloc之后指针值变了但数据没丢因为数据被拷到了新地址。realloc保证「从块首到新旧尺寸较小者」的内容不变但新地址需要重新取旧地址立即失效。Q容器里 C 程序的 OOM 是什么表现多数情况是被 cgroup 直接 OOMKill进程收不到NULL也就走不到失败分支。这时候看dmesg比看日志有用。写在最后这次修复只改了一行搬移但引出一个更值得记住的判断库里返回NULL的失败往往不是「什么都发生了」而是「什么都没发生」。realloc失败时什么都没发生是你自己的赋值把它变成了「发生了」。我后来把这批分配函数的「失败后的正确动作」整理成了一张表散在几个项目的编码规范里底稿是用 AI墨衍 把手册条目按「操作 / 失败后指针是否有效 / 该做什么」三列排出来的几份文档之间同步一次就够。查表比凭记忆稳。想要把发文额度、GEO 检测、图文同步一起用起来的话入口在这里https://mp.csdn.net/vip?utm_sourceITmoyan