关于函数strcpy、strncpy和memcpy的细节

参考:
请别再用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;//增加返回值,方便实现链式表达式
}

这个函数看似简单,实际上考虑的东西是真的多。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值