strcpy 是 C 标准库 <string.h> 中的一个函数,全称是 "string copy"(字符串复制),它的主要作用是将一个源字符串(source)复制到目标字符串(destination)中。
函数原型
我们来看一下 strcpy 在头文件 <string.h> 中声明的原型:
char *strcpy(char *dest, const char *src);
参数解释:
dest(destination): 目标字符数组(或字符指针),复制后的字符串将存放在这里。src(source): 源字符数组(或字符指针),这是你要被复制的字符串。const: 关键字const修饰src,表示strcpy函数承诺不会修改源字符串src的内容,这是一个很好的编程实践,可以防止意外修改原始数据。char *: 函数返回一个char *类型的指针,这个指针指向目标字符串dest的起始地址。
返回值:
函数返回指向 dest 的指针,这个返回值很有用,它可以方便地进行链式操作,或者用来检查调用是否成功(虽然 strcpy 本身不提供错误检查)。
基本用法示例
下面是一个最简单的 strcpy 使用例子。
#include <stdio.h>
#include <string.h> // 必须包含这个头文件才能使用 strcpy
int main() {
// 定义一个足够大的目标数组来存放复制的字符串
char dest[20];
const char src[] = "Hello, World!"; // 源字符串
// 使用 strcpy 进行复制
strcpy(dest, src);
// 打印结果
printf("源字符串 (src): %s\n", src);
printf("目标字符串 (dest): %s\n", dest);
// 打印返回值
printf("strcpy 返回的指针指向: %s\n", strcpy(dest, src)); // 链式调用
return 0;
}
输出:
源字符串 (src): Hello, World!
目标字符串 (dest): Hello, World!
strcpy 返回的指针指向: Hello, World!
工作原理
strcpy 的工作方式非常简单:它从 src 指向的内存地址开始,逐个字节地复制字符,直到遇到空字符 \0为止,它会连同 \0 一起复制到 dest 中。
src 的内容在内存中可能是:'H', 'e', 'l', 'l', 'o', '\0'
strcpy 会把这些内容原封不动地复制到 dest 中。
重要注意事项(非常重要!)
strcpy 虽然方便,但它也是一个非常危险的函数,如果使用不当,极易导致缓冲区溢出(Buffer Overflow)程序崩溃或安全漏洞。
警告 1:目标缓冲区必须足够大!
这是 strcpy 最常见也最危险的陷阱,你必须确保 dest 数组的大小大于等于 src 字符串的长度(包括末尾的 \0)。
错误示例:
#include <stdio.h>
#include <string.h>
int main() {
// dest 数组只有 5 个字节的空间
char dest[5];
const char src[] = "This is a very long string!"; // 这个字符串很长
// 错误!src 字符串的长度远大于 dest 的大小
// 这会导致缓冲区溢出,dest 数组后面的内存会被意外覆盖
strcpy(dest, src); // 未定义行为!
printf("dest: %s\n", dest); // 输出是不可预测的,并且可能破坏程序的栈
return 0;
}
为什么这很危险?
当 src 比 dest 大时,strcpy 会继续复制,直到遇到 \0,这会导致它写入 dest 数组边界之外的内存区域,从而可能:
- 覆盖其他重要变量的值。
- 导致程序崩溃(段错误)。
- 为黑客攻击创造条件(覆盖函数返回地址)。
警告 2:目标缓冲区必须是可写的!
dest 必须是一个字符数组或者指向动态分配内存的指针,不能是一个字符串字面量(string literal),因为字符串字面量通常存储在只读的代码段(.rodata)中。
错误示例:
#include <stdio.h>
#include <string.h>
int main() {
// 错误!str 是一个字符串字面量,存储在只读内存区
char *str = "Hello";
// 尝试向只读内存写入,会导致程序崩溃(段错误)
strcpy(str, "New"); // 未定义行为!
return 0;
}
警告 3:strcpy 不会检查目标缓冲区大小!
C 标准库的设计哲学之一是“信任程序员”,strcpy 不会自动检查 dest 是否有足够的空间,它只是盲目地执行复制操作,安全必须由程序员自己保证。
更安全的替代方案
由于 strcpy 的危险性,现代 C 编程推荐使用更安全的替代函数,它们在 <string.h> 中都有定义。
strncpy
strncpy 可以让你指定最多复制的字符数,从而防止溢出。
原型:
char *strncpy(char *dest, const char *src, size_t n);
参数 n: 表示最多复制的字符数。
用法和陷阱:
#include <stdio.h>
#include <string.h>
int main() {
char dest[10];
const char src[] = "A very long string";
// 最多复制 9 个字符到 dest (因为要留一个位置给 '\0')
strncpy(dest, src, 9);
// 重要:strncpy 不会自动在 dest 末尾添加 '\0'!
// src 的长度 >= n,dest 就不会以 '\0'
// 这会导致后续使用 dest 的函数(如 printf)可能会越界读取。
// 手动添加 '\0' 是一个好习惯。
dest[9] = '\0';
printf("dest: %s\n", dest); // 输出 "A very lon"
return 0;
}
strncpy 的优点:可以防止缓冲区溢出。
strncpy 的缺点:
src比n长,dest不会以\0需要手动处理。src比n短,strncpy会用\0填充dest剩余的空间,这可能造成不必要的性能开销。
snprintf (最推荐)
snprintf 是一个非常灵活且安全的函数,它不仅能防止缓冲区溢出,还能格式化字符串。
原型:
int snprintf(char *str, size_t size, const char *format, ...);
参数 size: 是 str 缓冲区的总大小。snprintf 保证最多写入 size - 1 个字符,并在最后自动添加 \0。
优点:
- 绝对安全:不会发生缓冲区溢出。
- 自动处理
\0:总是确保字符串正确终止。 - 功能强大:不仅可以复制字符串,还可以进行格式化输出。
#include <stdio.h>
#include <string.h>
int main() {
char dest[10];
const char src[] = "Another long string";
// 安全地复制,最多向 dest 写入 9 个字符,第 10 个留给 '\0'
snprintf(dest, sizeof(dest), "%s", src);
printf("dest: %s\n", dest); // 输出 "Another l"
return 0;
}
sizeof(dest) 会自动计算出数组的大小(这里是 10),非常方便。
| 函数 | 原型 | 安全性 | 描述 |
|---|---|---|---|
strcpy |
char *strcpy(char *dest, const char *src); |
危险 | 简单复制,但不检查目标缓冲区大小,极易导致溢出。 |
strncpy |
char *strncpy(char *dest, const char *src, size_t n); |
较安全 | 可限制复制长度,但可能不会自动添加 \0,需要手动处理。 |
snprintf |
int snprintf(char *str, size_t size, ...); |
最安全 | 推荐使用,可限制写入总长度,并保证字符串以 \0
|
核心建议:
- 优先使用
snprintf:在现代 C 编程中,snprintf是处理字符串复制和格式化输出的首选,因为它最安全、最灵活。 - 如果必须用
strcpy,请务必确保目标缓冲区足够大:在调用前,最好用sizeof运算符检查目标数组的大小。 - 了解
strcpy的工作原理和风险:在阅读遗留代码或性能极端敏感的场景下,你仍然会遇到它,理解其风险至关重要。
