C语言strcpy如何正确实现?

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

下面我将从最基础、最安全到最贴近标准库实现的方式,分步为你讲解如何用 C 语言实现 strcpy

最基础的实现(不安全)

这是最直接、最容易理解的实现,但它存在严重的安全隐患。

代码示例:

#include <stdio.h>
/**
 * @brief 不安全的 strcpy 实现
 * @param dest 目标缓冲区
 * @param src  源字符串
 * @return     指向目标缓冲区的指针
 */
char* my_strcpy_unsafe(char* dest, const char* src) {
    char* original_dest = dest; // 保存起始地址,用于返回
    // 循环直到 src 指向的字符是 '\0' (字符串结束符)
    while (*src != '\0') {
        *dest = *src; // 将 src 的值赋给 dest
        dest++;       // dest 指针后移
        src++;        // src 指针后移
    }
    // 循环结束后,*src 是 '\0',所以这里需要手动在 dest 末尾添加 '\0'
    *dest = '\0';
    return original_dest;
}
int main() {
    char source[] = "Hello, C!";
    char destination[20]; // 确保目标缓冲区足够大
    my_strcpy_unsafe(destination, source);
    printf("Source: %s\n", source);
    printf("Destination: %s\n", destination);
    return 0;
}

代码解析:

  1. 函数签名char* my_strcpy_unsafe(char* dest, const char* src)

    • dest: 目标缓冲区的指针,我们修改这个指针指向的内容。
    • src: 源字符串的指针,我们只读取它指向的内容,所以用 const 修饰,防止意外修改。
    • char*: 返回类型,按照惯例,strcpy 返回指向目标缓冲区起始位置的指针。
  2. while 循环

    • while (*src != '\0'):循环条件是检查 src 当前指向的字符是否是字符串的结束符 \0
    • *dest = *src:核心操作,将 src 指向的字符复制到 dest 指向的位置。
    • dest++src++:指针递增,指向下一个字符。
  3. 添加结束符

    • while 循环结束时,src 指向了 \0dest 指向的是复制的最后一个字符的下一个位置,我们必须在这里手动写入 \0,以确保 dest 也是一个合法的 C 字符串。
  4. 返回值

    • return original_dest:返回指向目标缓冲区起始地址的指针,方便链式调用或后续使用。

为什么不安全?

这个实现最大的问题是缓冲区溢出src 字符串的长度超过了 dest 缓冲区的大小(sizeof(dest)),while 循环会继续写入,覆盖掉 dest 缓冲区之外的内存,导致程序崩溃或安全漏洞。


安全的实现(增加长度检查)

为了避免缓冲区溢出,我们需要在复制前检查目标缓冲区的大小。

代码示例:

#include <stdio.h>
/**
 * @brief 安全的 strcpy 实现
 * @param dest 目标缓冲区
 * @param src  源字符串
 * @param dest_size 目标缓冲区的总大小 (包括 '\0')
 * @return         指向目标缓冲区的指针
 */
char* my_strcpy_safe(char* dest, const char* src, size_t dest_size) {
    if (dest_size == 0) {
        return dest; // 缓冲区为0,无法写入任何内容
    }
    size_t i;
    for (i = 0; i < dest_size - 1 && src[i] != '\0'; i++) {
        dest[i] = src[i];
    }
    // 循环结束后,确保 dest 总是以 '\0' 
    dest[i] = '\0';
    return dest;
}
int main() {
    char source[] = "This is a very long string that might cause overflow.";
    char destination[10]; // 故意设置一个很小的缓冲区
    printf("Attempting to copy a long string into a small buffer...\n");
    // 调用安全版本
    my_strcpy_safe(destination, source, sizeof(destination));
    printf("Source: %s\n", source);
    printf("Destination: %s\n", destination); // 只复制了 "This is"
    return 0;
}

代码解析:

  1. 新增参数size_t dest_size,表示目标缓冲区的总容量(包括用于存放 \0 的一个字节)。
  2. 循环条件for (i = 0; i < dest_size - 1 && src[i] != '\0'; i++)
    • i < dest_size - 1:这是关键!它确保我们最多只写入 dest_size - 1 个字符,为最后的 \0 预留空间。
    • src[i] != '\0':如果源字符串提前结束,循环也会停止。
  3. 添加结束符dest[i] = '\0'; 无论循环是因为哪种情况结束,我们都保证 dest 是一个合法的、以 \0 结尾的字符串。

这个版本可以防止缓冲区溢出,是更健壮的实现。


最优化的实现(指针运算)

标准库中的 strcpy 通常使用指针运算来实现,因为它比数组索引(dest[i])在某些编译器和架构下效率更高。

代码示例:

#include <stdio.h>
/**
 * @brief 优化的 strcpy 实现(使用指针运算)
 * @param dest 目标缓冲区
 * @param src  源字符串
 * @param dest_size 目标缓冲区的总大小 (包括 '\0')
 * @return         指向目标缓冲区的指针
 */
char* my_strcpy_optimized(char* dest, const char* src, size_t dest_size) {
    if (dest_size == 0) {
        return dest;
    }
    char* d = dest;
    const char* s = src;
    // 复制字符,直到遇到 '\0' 或缓冲区满
    while (--dest_size > 0 && (*d++ = *s++) != '\0') {
        // 循环体为空,所有操作都在条件中完成
    }
    // dest_size 变为 0,说明缓冲区已满,需要手动添加 '\0'
    // 如果因为 *s == '\0' 退出,循环已经将 '\0' 复制给了 *d
    if (dest_size == 0) {
        *d = '\0'; // 确保在缓冲区满时也添加结束符
    }
    return dest;
}
int main() {
    char source[] = "Optimized copy";
    char destination[20];
    my_strcpy_optimized(destination, source, sizeof(destination));
    printf("Destination: %s\n", destination);
    return 0;
}

代码解析:

  1. 指针别名:使用 char* d = dest;const char* s = src; 创建指针别名,使代码更清晰,也避免了重复解引用 destsrc
  2. 精妙的循环条件while (--dest_size > 0 && (*d++ = *s++) != '\0')
    • --dest_size > 0:在循环开始时先 dest_size 减 1,然后判断是否还大于 0,这相当于我们最多可以复制 dest_size - 1 个字符。
    • (*d++ = *s++) != '\0':这是一个赋值表达式,它先执行 *d = *sd++s++,整个表达式的值是赋值后的 *d 的值,如果赋值的是 \0,表达式为假,循环结束。
    • 这个循环将复制、指针移动、判断是否结束三个操作巧妙地合并在一行,非常高效。
  3. 处理边界情况:如果循环因为 dest_size 变为 0 而退出(即缓冲区已满,但源字符串还没结束),我们需要手动在 d 的当前位置(即缓冲区的末尾)写入 \0,以保证字符串的正确性。

总结与对比

特性 my_strcpy_unsafe my_strcpy_safe my_strcpy_optimized
安全性 不安全,可能溢出 安全,防止溢出 安全,防止溢出
核心逻辑 简单的 while 循环 for 循环加长度检查 while 循环加指针运算
效率 较低 较低 ,贴近标准库实现
适用场景 学习理解,绝不可用于生产代码 需要基本安全性的场景 推荐在生产代码中使用的模式
标准库对应 strncpy (但 strncpy 行为略有不同) strcpy (需确保缓冲区足够大)

重要提示:在现代 C 编程中,如果你已经知道目标缓冲区足够大,直接使用标准库的 <string.h> 中的 strcpy 是最简单、最高效的选择,如果你不确定缓冲区大小,应该使用 strncpy 或者更安全的 snprintf

#include <string.h>
#include <stdio.h>
int main() {
    char src[] = "Standard Library";
    char dest1[50];
    char dest2[10];
    // 安全使用,因为 dest1 足够大
    strcpy(dest1, src);
    printf("dest1: %s\n", dest1);
    // 危险! dest2 太小,会导致缓冲区溢出
    // strcpy(dest2, src); // 不要这样做!
    // 安全的替代方案
    strncpy(dest2, src, sizeof(dest2) - 1);
    dest2[sizeof(dest2) - 1] = '\0'; // 确保手动添加结束符
    printf("dest2: %s\n", dest2);
    return 0;
}
-- 展开阅读全文 --
头像
织梦无法生成列表页
« 上一篇 今天
dede调用多张图片
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]