4、字符串处理:strcpy与strncpy的安全使用规范

字符串拷贝,C语言里最基础的操作之一。但说实话,这也是我见过出bug最多的地方。你想想看,一个简单的strcpy,用不好就能让你的程序崩得莫名其妙。我在项目中遇到过好几次线上事故,追查到最后,都是字符串拷贝越界惹的祸。

今天我们就来聊聊strcpy和strncpy这对“兄弟”。怎么用才安全?有哪些坑?我把自己踩过的雷都摊开来讲。

4.1 strcpy:简单但危险

先看个最典型的例子:

char src[] = "Hello, World!";
char dst[10];
strcpy(dst, src);  // 危险!dst只有10字节,src有13字节

这段代码能编译通过,运行也不一定马上报错。但dst数组后面的内存已经被污染了。嗯,这就是传说中的缓冲区溢出

为什么会这样?因为strcpy不检查目标缓冲区的大小。它只管把源字符串一个字节一个字节地复制,直到遇到'\0'为止。至于目标空间够不够?它不管。

警告:strcpy没有长度限制,是C语言中最危险的标准函数之一。在安全要求高的项目中,我建议直接禁用strcpy。

我曾经在一个嵌入式项目中,接手过一段遗留代码。里面用了大量strcpy,结果产品在极端工况下偶尔会死机。查了三天,最后发现是一个字符串长度超过了预期,strcpy把栈上的返回地址给覆盖了。从那以后,我对strcpy就格外警惕。

4.2 strncpy:安全吗?也不尽然

很多人觉得,strncpy比strcpy安全,因为它多了一个长度参数。但说实话,strncpy也有自己的“小脾气”。

char src[] = "Hello";
char dst[10];
strncpy(dst, src, sizeof(dst));  // 这样安全吗?

这段代码看起来没问题。但strncpy有个特点:如果源字符串长度小于n,它会用'\0'填充剩余空间。如果源字符串长度大于等于n,它不会自动添加'\0'!

看这个例子:

char src[] = "Hello, World! This is a long string.";
char dst[10];
strncpy(dst, src, sizeof(dst));
// dst现在没有'\0'结尾!
printf("%s", dst);  // 危险!可能打印出乱码
核心要点:strncpy不会保证目标字符串以'\0'结尾。你必须手动处理。

我个人习惯的做法是:

char dst[10];
strncpy(dst, src, sizeof(dst) - 1);
dst[sizeof(dst) - 1] = '\0';  // 手动添加结束符

这样写,既保证了不会越界,又确保了字符串有正确的结尾。说白了,就是多写一行代码,换来一份安心。

4.3 安全拷贝的最佳实践

在实际项目中,我推荐使用snprintf或者自己封装一个安全的拷贝函数。snprintf的好处是:它一定会以'\0'结尾。

char dst[10];
snprintf(dst, sizeof(dst), "%s", src);  // 安全,自动加'\0'

如果项目不允许用snprintf(比如某些嵌入式环境),那就自己写一个:

void safe_strcpy(char *dst, const char *src, size_t dst_size) {
    if (dst == NULL || src == NULL || dst_size == 0) {
        return;  // 参数检查,不能少
    }
    size_t i;
    for (i = 0; i < dst_size - 1 && src[i] != '\0'; i++) {
        dst[i] = src[i];
    }
    dst[i] = '\0';  // 确保结尾
}
提示:我建议团队统一使用封装后的安全拷贝函数,而不是直接调用strcpy或strncpy。这样即使有人写错了,也容易在封装层统一修复。

4.4 避坑指南

我曾经踩过的坑,列出来给你参考:

  • 不要用strcpy处理用户输入——用户输入的长度不可控,用strcpy就是给自己埋雷。
  • strncpy的第三个参数不要用sizeof(指针)——sizeof(指针)返回的是指针本身的大小,不是数组的大小。我见过有人写strncpy(dst, src, sizeof(dst)),结果dst是char *类型,sizeof(dst)在32位系统上是4,在64位系统上是8。这能不出问题吗?
  • 注意源字符串是NULL的情况——strcpy和strncpy都不检查源指针是否为NULL。如果src是NULL,程序直接崩溃。
  • 不要用strncpy处理二进制数据——strncpy遇到'\0'就停止,但二进制数据中间可能有'\0'。这种情况应该用memcpy。

4.5 知识体系图

下面这张图总结了字符串安全拷贝的核心逻辑,你可以对照着理解:

字符串安全拷贝决策流程 需要字符串拷贝 源字符串长度是否完全可控? 可用strcpy 但建议仍用安全封装 必须用安全拷贝 strncpy / snprintf 手动加'\0',检查返回值

4.6 总结

字符串拷贝这件事,说白了就一句话:永远不要假设目标缓冲区够大。你想想看,程序跑在嵌入式设备上,内存就那么点,一个越界拷贝就能把整个系统搞崩。

我个人的建议是:

  • 新代码一律不用strcpy,用snprintf或自定义安全函数
  • 如果必须用strncpy,记得手动加'\0'
  • 代码审查时,重点检查字符串拷贝相关的代码
  • 单元测试覆盖边界情况:空字符串、满长度、超长字符串

记住,安全拷贝不是多此一举。你多写的那一行代码,可能就避免了一次线上事故。

专注资料整理