strcpy函数如何安全实现?

99ANYc3cd6
预计阅读时长 19 分钟
位置: 首页 C语言 正文

strcpy (string copy) 是 C 标准库 <string.h> 中的一个函数,用于将一个字符串(以 '\0' 结尾的字符数组)复制到另一个字符数组中。

c语言 strcpy函数实现
(图片来源网络,侵删)

strcpy 函数的原型与行为

我们来看一下它的标准原型:

char *strcpy(char *dest, const char *src);

参数说明:

  • dest (destination): 目标字符数组的指针,函数会将 src 字符串的内容复制到这个地址开始的空间中。
  • src (source): 源字符串的指针,这个字符串必须是 '\0' 结尾的。const 关键字表示 strcpy 函数不会修改源字符串的内容。

返回值:

  • 函数返回 dest 的首地址,即一个指向目标字符串的 char* 指针。

行为与注意事项:

c语言 strcpy函数实现
(图片来源网络,侵删)
  1. 复制到 '\0' 为止: strcpy 会复制 src 字符串中的所有字符,包括结尾的 '\0' (空字符),直到遇到 '\0' 才停止。
  2. 缓冲区溢出: 这是最重要的安全注意事项!strcpy 不会检查目标缓冲区 dest 的大小。src 字符串的长度(包括 '\0')超过了 dest 数组的容量,就会发生缓冲区溢出,导致程序崩溃或安全漏洞(例如被恶意代码利用)。
  3. 内存重叠: destsrc 所指向的内存区域有重叠,strcpy 的行为是未定义的,这可能导致复制的数据不正确。

strcpy 的基本实现

strcpy 的核心逻辑非常简单:循环地从 src 逐个字符复制到 dest,直到遇到 src 的结尾 '\0'

使用 while 循环和指针

这是最经典、最易于理解的实现方式,我们使用指针来遍历字符串。

#include <stdio.h>
/**
 * @brief 自定义 strcpy 函数实现
 * @param dest 目标字符串缓冲区
 * @param src  源字符串
 * @return 返回 dest 的首地址
 */
char *my_strcpy(char *dest, const char *src) {
    // 保存 dest 的起始地址,用于最后返回
    char *ptr = dest;
    // 循环条件:只要 *src 不是 '\0',循环就继续
    while (*src != '\0') {
        // 将 src 指向的字符赋值给 dest 指向的位置
        *dest = *src;
        // 同时移动 dest 和 src 指针,指向下一个字符
        dest++;
        src++;
    }
    // 循环结束后,src 指向 '\0',我们需要手动将 '\0' 复制到 dest 的当前位置
    *dest = '\0';
    // 返回保存好的起始地址
    return ptr;
}

代码解析:

  1. char *ptr = dest;: 我们创建一个临时指针 ptr 来记住 dest 的起始地址,因为在循环中 dest 指针会不断移动,最后指向的是字符串的末尾,我们需要返回的是字符串的开头。
  2. while (*src != '\0'): 这是循环的终止条件,只要 src 当前指向的字符不是字符串结束符 '\0',循环就继续。
  3. *dest = *src;: 这是核心的复制操作,将 src 指针解引用(获取其指向的字符)的值,赋给 dest 指针解引用的地址。
  4. dest++; src++;: 将两个指针都向前移动一个字节,指向下一个字符。
  5. *dest = '\0';: 当循环结束时,src 指向了 '\0',而 dest 指向了目标字符串的末尾的下一个位置,我们需要在这里手动写入一个 '\0',以确保目标字符串也是正确结尾的。
  6. return ptr;: 返回保存好的起始地址。

更简洁的实现(利用 '\0' 的值为 0)

在 C 语言中,'\0' 的 ASCII 码值就是 0。while (*src != '\0') 可以简化为 while (*src),因为当 *src'\0' 时,表达式 *src 的值就是 0(假),循环终止。

#include <stdio.h>
char *my_strcpy_v2(char *dest, const char *src) {
    char *ptr = dest;
    // 循环条件:只要 *src 不是 0 (即 '\0'),循环就继续
    while (*src) {
        *dest++ = *src++;
    }
    // 循环结束后,*src 的值是 '\0',所以这个赋值操作会将 '\0' 写入 dest
    *dest = '\0';
    return ptr;
}

这个版本更紧凑,但功能与版本一完全相同。


最精简的实现(不推荐用于生产,但常见于面试)

这个版本利用了赋值表达式的返回值,赋值操作 a = b 的值就是 b 的值。

#include <stdio.h>
char *my_strcpy_v3(char *dest, const char *src) {
    char *ptr = dest;
    // *dest++ = *src++ 的执行流程:
    // 1. 获取 *src 的值
    // 2. 将该值赋给 *dest
    // 3. 将 src 和 dest 指针都向后移动
    // 4. 整个表达式的值为 *src 的值(即被复制的字符)
    // 当复制到 '\0' 时,表达式的值为 0,循环终止
    while ((*dest++ = *src++)) {
        // 循环体为空,所有逻辑都在 while 条件中
    }
    return ptr;
}

这个版本非常简洁,但可读性稍差,对于初学者可能不太好理解,在实际开发中,版本一或版本二的可读性更好。


完整示例代码

下面是一个完整的 C 程序,演示了如何使用我们自己实现的 my_strcpy 函数。

#include <stdio.h>
// 使用版本一的实现
char *my_strcpy(char *dest, const char *src) {
    char *ptr = dest;
    while (*src != '\0') {
        *dest = *src;
        dest++;
        src++;
    }
    *dest = '\0';
    return ptr;
}
int main() {
    // 定义源字符串和目标字符数组
    const char *source_str = "Hello, C Language!";
    char destination_str[50]; // 确保目标缓冲区足够大
    printf("源字符串: %s\n", source_str);
    printf("复制前,目标字符串: %s\n", destination_str);
    // 调用我们自己实现的 strcpy 函数
    // 返回值被我们忽略,但我们也可以接收它
    my_strcpy(destination_str, source_str);
    printf("复制后,目标字符串: %s\n", destination_str);
    // 也可以接收返回值
    char another_dest[50];
    char *ret_ptr = my_strcpy(another_dest, "This is a test.");
    printf("使用返回值打印: %s\n", ret_ptr);
    return 0;
}

编译与运行:

$ gcc strcpy_example.c -o strcpy_example
$ ./strcpy_example
源字符串: Hello, C Language!
复制前,目标字符串: 
复制后,目标字符串: Hello, C Language!
使用返回值打印: This is a test.

安全的替代方案:strncpy

由于 strcpy 存在缓冲区溢出的风险,C 标准库还提供了 strncpy 函数,它允许你指定最多复制的字符数。

char *strncpy(char *dest, const char *src, size_t n);

strncpy 会从 src 复制最多 n 个字符到 dest

strncpy 的行为:

  1. strlen(src) < n:它会复制整个 src 字符串(包括 '\0'),然后在剩余的空间里用 '\0' 填充,直到总共复制了 n 个字符。
  2. strlen(src) >= n:它会复制 src 的前 n 个字符到 dest但不会自动添加 '\0' 结尾符! 这会导致 dest 可能不是一个合法的 C 字符串。

使用 strncpy 时,需要格外小心,最好总是手动确保 dest 是以 '\0' 结尾的。

示例:

#include <stdio.h>
#include <string.h>
int main() {
    char src[] = "Hello";
    char dest[10];
    // 复制最多 3 个字符
    strncpy(dest, src, 3);
    // 因为 src 的长度 (5) > 3,dest 不会自动添加 '\0'
    // 必须手动添加!
    dest[3] = '\0'; 
    printf("strncpy 结果: %s\n", dest); // 输出: Hel
    return 0;
}
特性 strcpy strncpy
功能 复制整个字符串(到 '\0' 为止) 复制最多 n 个字符
安全性 不安全,容易导致缓冲区溢出 更安全,但需要小心处理 '\0'
原型 char *strcpy(char *dest, const char *src); char *strncpy(char *dest, const char *src, size_t n);
优点 简单易用 可控制复制的最大长度,防止溢出
缺点 危险,不检查目标缓冲区大小 n 小于源字符串长度,不会自动添加 '\0'
实现核心 while (*src) { *dest++ = *src++; } for (size_t i = 0; i < n && src[i]; i++) { dest[i] = src[i]; }

在编写现代 C 代码时,如果可能,应优先使用更安全的函数,如 strncpysnprintf 或 C 标准库中 <string.h> 提供的其他安全函数。

-- 展开阅读全文 --
头像
dede自定义表单如何实现文件上传功能?
« 上一篇 02-08
织梦CMS模板修改从哪入手?
下一篇 » 02-08
取消
微信二维码
支付宝二维码

目录[+]