菜单

我真的气笑了——17.c - 午休的时候 - 连老用户都容易中招!!别问我怎么知道的

我真的气笑了——17.c · 午休的时候 · 连老用户都容易中招!!别问我怎么知道的

我真的气笑了——17.c - 午休的时候 - 连老用户都容易中招!!别问我怎么知道的

午休本该是短暂的放空,结果我在睡眼朦胧的时候被一个“午休专属”的 bug 拿下了。事情很简单:把老项目里的 17.c 拉出来改了两行,编译没报错,测试也过了几个用例,心想可以去吃饭了。结果同事午休回来一运行,程序在某个随机输入下炸了——还只在特定长度的字符串出现。调试来调试去,最后发现居然是 strncpy 在捣鬼。连我们这些写了十多年 C 的老用户都被这玩意儿绊过。

先讲结果:问题是 strncpy 并不会保证目标字符串以 '\0' 结尾,当源字符串长度恰好等于要拷贝的长度时,会出现没有终止符的情况。很多人误以为 strncpy 是“安全版 strcpy”,但它只是不同的行为而已。这个细节只在特定输入长度(恰好触发边界)时暴露出来,所以非常容易在日常测试中漏掉,午休回来就会被抓包。

下面把过程和解决方案说清楚,方便以后别再被同样的事情整懵。

症状回顾

  • 程序在处理某些输入时随机崩溃或输出乱码,似乎和字符串长度有关。
  • 崩溃点通常发生在后续对该字符串调用 strlen、strcmp、printf("%s") 等函数时。
  • 把出问题的输入和正常输入对比,长度差别很微妙,恰好是 buffer 大小的边界。

核心罪魁:strncpy 的陷阱

  • strncpy(dest, src, n) 的行为:
  • 如果 strlen(src) < n,strncpy 会把 src 拷贝过去并在剩下的位置填 '\0',看似安全。
  • 如果 strlen(src) >= n,strncpy 只会拷 n 个字节,不会自动添加 '\0',结果目标不是以 '\0' 结尾。
  • 许多人把 strncpy 当成“安全 strcpy”,其实两者在边界行为上完全不同,容易误用。

示例(错误用法) char buf[17]; strncpy(buf, user_input, sizeof(buf)); // assume buf is a C-string now — WRONG

当 user_input 的长度恰好 >= 17 时,buf 就不是以 '\0' 结尾了。后续任何字符串操作都会踩雷。

正确做法(几种可选) 1) 明确手动保证以 '\0' 结尾 char buf[17]; strncpy(buf, user_input, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0';

2) 用更直观的替代(如果平台支持)

  • 使用 strlcpy(更安全,返回拷贝长度) strlcpy(buf, user_input, sizeof(buf));
  • 或者用 snprintf(格式化并自动以 '\0' 结尾) snprintf(buf, sizeof(buf), "%s", user_input);

3) 在能用更高级类型的地方,直接用动态字符串或 C++ std::string,避免裸指针和固定数组。

调试小技巧(在午休后也能快速搞定)

  • 用 printf/日志把拷贝前后的字节逐个打印(%02x)看有没有 '\0'。
  • valgrind 能帮你发现未初始化读取和越界。
  • 把崩溃输入保存成测试用例,写个小脚本批量跑边界长度(len = N-1, N, N+1)来复现。
  • 在代码里统一封装字符串拷贝函数,避免项目里每个地方都各自写一套。

为什么连老用户都会中招

  • strncpy 出现得早,很多旧代码用它是习惯问题。
  • 文档里没有直观的“不会自动加终止符”警告,或者被简化成“比 strcpy 安全”,导致误解。
  • 平时测试的数据没覆盖到边界长度,问题只在实际使用中偶发——这个恰好就是午休后同事跑了不同数据才发现的场景。

结尾 别问我怎么知道的——午休回来看到程序崩溃那一刻,我是又气又笑。把这篇贴出来,提醒大家:字符串边界永远值得三思。欢迎把你们遇到的“午休惊喜”也丢上来,大家互相长长见识。

有用吗?

技术支持 在线客服
返回顶部