参考:
请别再用strcpy, 而用strncpy-CSDN博客
C/C++笔记–strcpy和strncpy函数的安全性分析-CSDN博客
文章目录
strcpy和strncpy
strcpy有什么危险?
函数原型:
char* strcpy(char* strDest, const char* strSrc)
char* strncpy(char* strDest, const char* strSrc, size_t n)
strcpy: 用于拷贝字符串,会拷贝 '\0'
,当源串出现 '\0'
时拷贝才结束。如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出的错误,可能造成程序崩溃。
int main(){
char str[4] = {0};
char *p = "123456";
strcpy(str, p);
cout << str << endl;
return 0;
}
strncpy:用于拷贝字符,并不保证 '\0'
结束,不一定会拷贝 '\0'
。用来复制源字符串的前n个字符,src 和 dest 所指的内存区域不能重叠,且 dest 必须有足够的空间放置n个字符。
可以这么使用:
int main(){
char str[4] = {0};
char *p = "1234567890";
cout << sizeof(str) << endl;
strncpy(str, p, sizeof(str));
cout << str << endl;
return 0;
}
那其实也看到了strncpy也是有很多限制的,比如src 和 dest 所指的内存区域不能重叠,且 dest 必须有足够的空间放置n个字符。所以一旦不小心忽视这些限制,也可能出错。并且,该函数只是拷贝相应的字符,并不会在意\0
怎么样怎么样:
如果目标长>指定长>源长,则将源长全部拷贝到目标长,自动加上\0
如果指定长<源长,则将源长中按指定长度拷贝到目标字符串,不包括\0
如果指定长>目标长,运行时错误
上面三条并没有测试,来自开头的参考链接。
所以更好的方式其实是使用:memcpy函数,该函数拷贝是按字节拷贝的,可以拷贝任意类型的数据,而strcpy只能用来拷贝字符串。并且,memcpy函数更为安全。
最后如果需要自己实现一个strcpy函数的话,可以一步一步来。
开始可以这么写:
char* my_strcpy(char *dst, const char *src){
assert((dst != NULL) && (src != NULL));
char *p = dst;//保存地址,方便返回
while(*src != '\0'){
*dst++ = *src++;
}
*dst = '\0';//手动加上\0
return p;
}
说一下几个需要注意的点:
- 返回目的地址是为了方便链式操作
- 源地址的参数带const为了防止修改
- 开头的assert进行参数合法性检查
更为优雅的函数原型:
char* my_strcpy(char *dst, const char *src){
assert((dst != NULL) && (src != NULL));
char *p = dst;//保存地址,方便返回
while((*dst++ = *src++) != '\0'){
}
return p;
}
这种简单的函数反而有很多容易忽视的地方。
strncpy:
char* my_strncpy(char *dst, const char *src, size_t n){
assert((dst != NULL) && (src != NULL));
char *p = dst;//保存地址,方便返回
while(n){
if(*src != '\0') *dst++ = *src++;
--n;
}
*dst = '\0';//手动加上\0
return p;
}
同理strlen函数(不考虑\0
,而sizeof函数会考虑)也可以比着写:
int my_strlen(const char *str){
assert(str != NULL);
int len;
while(*str++ != '\0'){
++len;
}
return len;
}
memcpy
void * memcpy(void *dst, const void *src, size_t n);
函数功能是从源地址src拷贝n个字节到目的地址dst。
然后还有一个注意的点是,传入参数是void*指针,需要进行类型转换,而且返回值也需要考虑,然后我查阅了一下资料,发现我还是想的太简单了,这个函数还需要进行参数检查、地址比较等好多细节。
参考:c++中内存拷贝函数(C++ memcpy)详解_Guo_guo-CSDN博客
void* Mymemcpy(void *dst, const void *src, size_t n){
void *ret = dst;
assert(dst);
assert(src);
while(n--){
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
return ret;//增加返回值,方便实现链式表达式
}
但是还会出现个问题就是:源地址和目的地址重叠的时候,输出会出错。比如一下的测试:
#include <bits/stdc++.h>
using namespace std;
void* Mymemcpy(void *dst, const void *src, size_t n){
void *ret = dst;
assert(dst);
assert(src);
while(n--){
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
return ret;//增加返回值,方便实现链式表达式
}
int main(){
char s[100] = "hello world!";
char d[15];
Mymemcpy(s+1, s, strlen(s));
cout << string(s, strlen(s)) << endl;
return 0;
}
输出结果是 : “hhhhhhhhh…”,原因是源地址区间和目的地址区间有重叠的地方,代码无意之中将源地址区间的内容修改了。
所以需要对地址覆盖进行判断,分别采取正向、反向拷贝
void* Mymemcpy(void *dst, const void *src, size_t n){
void *ret = dst;
assert(dst);
assert(src);
if(dst <= src || (char*)dst >= (char*)src + n){
while(n--){//从低地址往高地址拷贝
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
}
else{//从高地址往低地址拷贝
dst = (char *)dst + n - 1;
src = (char *)src + n - 1;
while (n--) {
*(char *)dst = *(char *)src;
dst = (char *)dst - 1;
src = (char *)src - 1;
}
}
return ret;//增加返回值,方便实现链式表达式
}
这个函数看似简单,实际上考虑的东西是真的多。