strcpy&strncpy
好的,我们来详细讲解C语言中这两个非常重要且容易混淆的字符串复制函数。
1. strcpy - 字符串复制函数
基本定义
#include <string.h>
char *strcpy(char *dest, const char *src);
功能说明
- 将
src指向的字符串(包括结束符\0)完全复制到dest指向的内存空间 src必须是有效的以\0结尾的字符串- 返回指向
dest的指针
使用示例
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!";
char dest[20]; // 确保目标缓冲区足够大
strcpy(dest, src);
printf("源字符串: %s\n", src);
printf("目标字符串: %s\n", dest);
return 0;
}
危险之处:缓冲区溢出
#include <stdio.h>
#include <string.h>
void dangerous_example() {
char small_buffer[5]; // 只能容纳4个字符 + \0
char long_string[] = "This is a very long string";
// 危险!会发生缓冲区溢出
strcpy(small_buffer, long_string);
printf("%s\n", small_buffer); // 未定义行为,可能崩溃
}
2. strncpy - 安全字符串复制函数
基本定义
#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
功能说明
- 从
src复制最多n个字符到dest - 如果
src的长度小于n,则用\0填充剩余空间 - 如果
src的长度大于等于n,则不会自动添加\0 - 返回指向
dest的指针
使用示例
#include <stdio.h>
#include <string.h>
int main() {
char src[] = "Hello, World!";
char dest[10];
// 安全复制,限制复制的字符数
strncpy(dest, src, sizeof(dest) - 1); // 留一个位置给\0
dest[sizeof(dest) - 1] = '\0'; // 手动添加结束符
printf("安全复制: %s\n", dest); // 输出: Hello, Wo
return 0;
}
3. 两个函数的详细对比
对比表格
| 特性 | strcpy | strncpy |
|---|---|---|
| 函数原型 | char *strcpy(dest, src) |
char *strncpy(dest, src, n) |
| 安全性 | 不安全,可能缓冲区溢出 | 相对安全,可限制复制长度 |
| 自动添加\0 | 总是添加 | 仅在src长度<n时添加 |
| 性能 | 较快 | 稍慢(需要检查长度) |
| 填充行为 | 无 | 用\0填充剩余空间(当src较短时) |
| 适用场景 | 确定src不会超过dest大小时 | 需要防止缓冲区溢出时 |
内存布局对比示例
#include <stdio.h>
#include <string.h>
#include <string.h>
void compare_functions() {
char src[] = "Hello";
char dest1[10];
char dest2[10];
// 使用strcpy
strcpy(dest1, src);
// dest1内存: ['H','e','l','l','o','\0','?','?','?','?']
// 使用strncpy
strncpy(dest2, src, sizeof(dest2));
// dest2内存: ['H','e','l','l','o','\0','\0','\0','\0','\0']
printf("strcpy结果: %s\n", dest1);
printf("strncpy结果: %s\n", dest2);
}
4. strncpy的重要特性和陷阱
陷阱1:不自动添加结束符
#include <stdio.h>
#include <string.h>
void trap_example() {
char src[] = "This is a long string";
char dest[5];
strncpy(dest, src, 5); // 只复制5个字符
// 危险!dest可能没有\0结束符
printf("可能出错: %s\n", dest); // 未定义行为
// 正确做法:手动添加结束符
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // 确保有结束符
printf("安全做法: %s\n", dest); // 正常输出
}
陷阱2:性能问题(用\0填充)
#include <stdio.h>
#include <string.h>
#include <time.h>
#define SIZE 10000
void performance_issue() {
char small_src[] = "hi";
char large_dest[SIZE];
clock_t start = clock();
strncpy(large_dest, small_src, SIZE);
clock_t end = clock();
printf("strncpy填充耗时: %f秒\n",
(double)(end - start) / CLOCKS_PER_SEC);
// 虽然只复制2个字符,但需要填充9998个\0
}
5. 安全使用的最佳实践
方案1:标准的安全包装函数
#include <stdio.h>
#include <string.h>
// 安全的strncpy包装函数
char* safe_strncpy(char *dest, const char *src, size_t n) {
if (n == 0) return dest;
strncpy(dest, src, n - 1); // 留一个位置给\0
dest[n - 1] = '\0'; // 确保有结束符
return dest;
}
// 更安全的版本,返回实际需要的长度
size_t safe_copy(char *dest, const char *src, size_t dest_size) {
if (dest_size == 0) return 0;
size_t src_len = strlen(src);
size_t copy_len = (src_len < dest_size) ? src_len : dest_size - 1;
memcpy(dest, src, copy_len);
dest[copy_len] = '\0';
return copy_len;
}
int main() {
char buffer[10];
const char *text = "Hello, World!";
safe_strncpy(buffer, text, sizeof(buffer));
printf("安全复制: %s\n", buffer); // 输出: Hello, Wo
size_t copied = safe_copy(buffer, text, sizeof(buffer));
printf("复制了%zu个字符: %s\n", copied, buffer);
return 0;
}
方案2:现代C的替代方案
#include <stdio.h>
#include <string.h>
void modern_alternatives() {
char dest[20];
const char *src = "Hello, World!";
// 方法1: 使用snprintf (C99标准,最推荐)
snprintf(dest, sizeof(dest), "%s", src);
printf("snprintf: %s\n", dest);
// 方法2: 使用strlcpy (非标准但广泛支持)
#ifdef __linux__
// 在Linux上可能需要定义特性测试宏
#define _GNU_SOURCE
#include <bsd/string.h>
strlcpy(dest, src, sizeof(dest));
#endif
// 方法3: 使用memcpy + 手动添加\0
size_t copy_len = strlen(src);
if (copy_len >= sizeof(dest)) {
copy_len = sizeof(dest) - 1;
}
memcpy(dest, src, copy_len);
dest[copy_len] = '\0';
printf("memcpy方法: %s\n", dest);
}
6. 实际应用场景示例
场景1:配置文件读取
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_LINE_LENGTH 256
#define MAX_KEY_LENGTH 50
#define MAX_VALUE_LENGTH 100
typedef struct {
char key[MAX_KEY_LENGTH];
char value[MAX_VALUE_LENGTH];
} ConfigEntry;
int parse_config_line(const char *line, ConfigEntry *entry) {
char buffer[MAX_LINE_LENGTH];
// 安全地复制一行
strncpy(buffer, line, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
// 查找等号分隔符
char *equal_sign = strchr(buffer, '=');
if (!equal_sign) return -1;
// 分割键和值
*equal_sign = '\0'; // 在等号处断开
// 安全地复制键
strncpy(entry->key, buffer, sizeof(entry->key) - 1);
entry->key[sizeof(entry->key) - 1] = '\0';
// 安全地复制值(跳过等号)
strncpy(entry->value, equal_sign + 1, sizeof(entry->value) - 1);
entry->value[sizeof(entry->value) - 1] = '\0';
return 0;
}
场景2:网络数据处理
#include <stdio.h>
#include <string.h>
#define MAX_PACKET_SIZE 1024
void process_network_data(const char *data, size_t data_len) {
char safe_buffer[MAX_PACKET_SIZE + 1]; // +1 for \0
// 安全地复制网络数据
size_t copy_len = data_len;
if (copy_len > MAX_PACKET_SIZE) {
copy_len = MAX_PACKET_SIZE;
printf("警告:数据包被截断\n");
}
strncpy(safe_buffer, data, copy_len);
safe_buffer[copy_len] = '\0'; // 确保有结束符
// 现在可以安全地处理数据
printf("处理数据: %s\n", safe_buffer);
}
7. 总结:选择指南
什么时候用strcpy?
- 当你100%确定源字符串不会超过目标缓冲区大小时
- 性能要求极高的场景
- 代码简单明了,不需要长度检查
什么时候用strncpy?
- 处理用户输入、文件数据、网络数据等不可信源时
- 需要防止缓冲区溢出的安全关键代码
- 记得总是手动添加
\0结束符
现代最佳实践
// 推荐使用snprintf代替strncpy
char dest[100];
const char *src = "some string";
snprintf(dest, sizeof(dest), "%s", src); // 最安全的方式
// 或者使用平台特定的安全函数
#ifdef _WIN32
strcpy_s(dest, sizeof(dest), src);
#elif defined(__linux__)
strlcpy(dest, src, sizeof(dest)); // 如果可用
#endif
记住:安全永远是第一位的。在大多数现代应用中,推荐使用snprintf或其他更安全的替代方案。

浙公网安备 33010602011771号