C++中字符串拷贝函数strcpy的实现与优化指南
作者:sanqima
本文介绍了C++中字符串拷贝函数strcpy的基本用法及常见边界条件,分析了不同版本的实现方式及其优缺点,并提供了测试案例和使用场景建议,强调了安全性的重要性,需要的朋友可以参考下
C++中,有个常见的字符串拷贝函数:char* strcpy(char* dst, const char* src), 作用是将1个字符串src,拷贝到另外一个字符串dst。需要考虑的边界条件如下:
- 空指针:dst或src为NULL时会触发非法内存访问。
- 内存重叠:dst和src的地址范围存在重叠,从前向后拷贝会覆盖尚未读取的src内容。
- 空字符串:src仅包含’\0’,需保证拷贝后dst仍然是合法空串。
- 缓冲区溢出:dst空间不足导致拷贝越界,是严重的安全漏洞。
- 结束符丢失:必须保证拷贝后的dst以’\0’结尾,否则后续strlen/printf等字符串操作会越界。
1 基本版本
#include <cassert>
// 对齐标准strcpy签名,src加const保证常量正确性
char* my_strcpy(char* dst, const char* src) {
// 调试期断言快速定位空指针问题,发布模式可关闭断言不影响性能
assert(dst != nullptr && src != nullptr);
// 保存目标地址首指针,后续会移动dst指针
char* ret = dst;
// 拷贝逻辑:赋值表达式返回赋值后的值,遇到'\0'时循环终止,自动包含结束符
while ((*dst++ = *src++) != '\0');
return ret;
}
1.1 优点
实现了核心功能,适合调用方能保证参数合法性的高性能场景。
1.2 缺点
- 若发布模式关闭断言,则传入空指针,会触发未定义行为。
- 没有处理内存重叠场景,若dst落在src的地址范围内且dst > src,会导致拷贝内容错误。
- 没有检查dst缓冲区大小,极易出现溢出。
2 进阶版(支持内存重叠)
参考memmove的语义,处理重叠场景,特别存在地址重叠的情况(如字符串内部移动)
#include <cstddef> // size_t定义
char* my_strcpy(char* dst, const char* src) {
// 发布期也保留空指针检查,避免程序崩溃
if (dst == nullptr || src == nullptr) {
return nullptr;
}
char* ret = dst;
// 第一步:计算src总长度(包括末尾'\0')
size_t len = 0;
while (src[len] != '\0') len++;
len += 1; // 包含结束符的总拷贝字节数
// 第二步:判断内存重叠,选择拷贝方向
if (dst < src || dst >= src + len) {
// 无重叠 / dst在src前面,从前向后拷贝性能更高
for (size_t i = 0; i < len; i++) {
dst[i] = src[i];
}
} else {
// 重叠且dst在src后面,从后向前拷贝避免覆盖未读取的src内容
// 注意:size_t是无符号类型,禁止写i>=0(永远为真),改为从len倒序到1
for (size_t i = len; i > 0; i--) {
dst[i-1] = src[i-1];
}
}
return ret;
}
2.1 特点
- 即使参数为空也返回nullptr,避免程序崩溃。
- 正确处理所有内存重叠场景,例如dst = src + 2这类后向重叠场景,也能得到正确结果。
- 仍然依赖调用方保证src以’\0’结尾、dst缓冲区足够大。
3 高级版(缓存区防护)
在实际开发中,禁止使用无长度限制的strcpy,推荐实现带缓冲区长度的安全版本,彻底避免溢出:
#include <cstddef>
char* my_strcpy_s(char* dst, size_t dst_size, const char* src) {
if (dst == nullptr || src == nullptr || dst_size == 0) {
return nullptr;
}
size_t i = 0;
// 最多拷贝dst_size-1个字符,预留1个位置给'\0'
while (src[i] != '\0' && i < dst_size - 1) {
dst[i] = src[i];
i++;
}
// 强制加结束符,保证dst永远是合法C字符串(解决strncpy不自动加'\0'的缺陷)
dst[i] = '\0';
return dst;
}
3.1 特点
- 完全避免缓冲区溢出,即使src远长于dst的空间,也只会截断到缓冲区可容纳的最大长度。
- 始终保证dst以’\0’结尾,不会出现非法字符串。
4 测试案例
| 测试场景 | 用例代码 | 预期结果 |
|---|---|---|
| 普通字符串拷贝 | char dst[20]; my_strcpy(dst, “hello world”); | dst内容为"hello world" |
| 空字符串拷贝 | char dst[10]; my_strcpy(dst, “”); | dst[0] == ‘\0’ |
| 自拷贝 | char buf[] = “test”; my_strcpy(buf, buf); | buf内容不变,仍为"test" |
| 后向内存重叠 | char buf[20] = “abcdefgh”; my_strcpy(buf+2, buf); | buf内容为"ababcdefgh"(基础版会得到"abababab…"错误结果) |
| 空参数传入 | my_strcpy(nullptr, “test”); | 返回nullptr,程序不崩溃 |
| 缓冲区不足(安全版) | char dst[5]; my_strcpy_s(dst, 5, “hello world”); | dst内容为"hell",自动截断并加结束符 |
5 使用场景
- const 正确性:src参数必须加const修饰,既可以传入const字符串,也能避免函数内部误修改源字符串。
- 指针溢出:嵌入式 / 内核开发中,若src地址接近地址空间上限,src + len可能出现指针溢出,可转为uintptr_t整数类型后再做范围比较。
- 性能取舍:如果业务不存在重叠场景,基础版的性能更高;如果优先保证安全性,直接使用带长度的安全版本。
到此这篇关于C++中字符串拷贝函数strcpy的实现与优化指南的文章就介绍到这了,更多相关C++字符串拷贝函数strcpy内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
