【c++中内存拷贝函数(C++ memcpy)详解】

这篇具有很好参考价值的文章主要介绍了【c++中内存拷贝函数(C++ memcpy)详解】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


原型:void*memcpy(void*dest, const void*src,unsigned int count); 

功能:由src所指内存区域复制count个字节到dest所指内存区域。  

说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。    

举例: 

//   memcpy.c                         
#include   <stdlib.h>             
#include   <string.h>             
main()             
{                 
    char *s= "Golden  Global   View ";  
    char d[20];  
    clrscr();  
    memcpy(d,s,strlen(s));  
    d[strlen(s)]=0;  
    printf( "%s ",d);  
    getchar();  
    return   0;  
}  


下面自行实现这个函数

程序清单 1 V0.1版程序 

void MyMemMove(char *dst,char *src,int count)   
{   
    while(count--)   
        *dst++ = *src++;   
}  


程序清单 2 测试V0.1用例 

void Test()   
{   
    char p1[256] = ”hello,world!”;   
    char p2[256] = {0};   
    MyMemMove(p2,p1,strlen(p1));   
    printf(“%s”,p2);   
}   

        客观地讲,相比那些交白卷或者函数声明都不会写的同学来说,能够写出这段代码的同学已经非常不错了,至少在C语言这门课程上已经达到了现行高校的教育目标,但是离企业的用人要求还有一定的距离。我们不妨将上面的程序称为V0.1版本,看看还有没有什么地方可以改进。 
        首先我们看看函数声明是否合理,V0.1版的程序将源地址和目的地址都用char *来表示,这样当然也没有什么问题,但是让其他人使用起来却很不方便,假如现在要将count个连续的结构体对象移动到另外一个地方去,如果要使用v0.1的程序的话,正确的写法如下: 
       

 MyMemMove((char *)dst,(char *)src,sizeof(TheStruct)*count); 

        也就是说我们需要将结构体指针强制转换成char * 才能够正常工作,这样除了字符串以外其它的类型都不可避免地要进行指针强制转换,否则编译器就会呱呱叫,比如在VC++2008下就会出现这样的错误: 

error C2664: 'MyMemMove' : cannot convert parameter 1 from 'TheStruct *'to 'char *' ;

那么如何解决这个问题呢?其实很简单,我们知道有一种特别的指针,任何类型的指针都可以对它赋值,那就是void *,所以应该将源地址目的地址都用void*来表示。当然函数体的内容也要作相应的改变,这样我们就得到了V0.2版的程序。 
程序清单 3 V0.2版程序 

void MyMemMove(void *dst,void *src,int count)   
{   
    while (count--)   
    {   
        *(char *)dst = *(char *)src;   
        dst = (char *)dst + 1;   
        src = (char *)src + 1;   
    }   
}   

        有的同学可能会问,这里面不是还有指针强制转换吗?只不过是换了地方。没错,强制指针转换确实是从使用者的代码转移到了库的代码里,但我们可以将 MyMemMove理解为库,而将Test理解为使用者,事实上通过调整之后的效果却有天壤之别,V0.1是一逸永劳,而V0.2是一劳永逸! 
        还有几个细节需要注意,为了实现链式表达式,我们应该将返回值也改为void *。此外,如果我们不小心将“*(char *)dst = *(char *)src;”写反了,写成“*(char *)src =*(char *)dst;”编译照样通过,而为了找出这个错误又得花费不少时间。注意到src所指向的内容在这个函数内不应该被改变,所有对src所指的内容赋值都应该被禁止,所以这个参数应该用const修饰,如果有类似的错误在编译时就能够被发现: 

error C3892: 'src' : you cannot assign to a variable that is const ;

        作为程序员犯错误在所难免,但是我们可以利用相对难犯错误的机器,也就是编译器来降低犯错误的概率,这样我们就得到了V0.3版的程序。 
程序清单 4 V0.3版程序 

void * MyMemMove(void *dst,const void *src,int count)   
{   
    void *ret=dst;   
    while (count--)   
    {   
        *(char *)dst = *(char *)src;   
        dst = (char *)dst + 1;   
        src = (char *)src + 1;   
    }   
    return ret;  
}   

        现在再来考虑这样一种情况,有使用者这样调用库:MyMemMove(NULL,src, count),这是完全可能的,因为一般来说这些地址都是程序计算出来的,那就难免会算错,出现零地址或者其它的非法地址也不足为奇。可以预料的是,如果出现这种情况的话,则程序马上就会down掉,更糟糕的是你不知道错误出在哪里,于是不得不投入大量的精力在浩瀚的代码中寻找bug。解决这类问题的通用办法是对输入参数作合法性检查,也就是V0.4版程序。 
程序清单 5 V0.4版程序

void * MyMemMove(void *dst,const void *src,int count)   
{   
    void *ret=dst;   
    if (NULL==dst||NULL ==src)   
    {   
        return dst;   
    }   
    while (count--)   
    {   
        *(char *)dst = *(char *)src;   
        dst = (char *)dst + 1;   
        src = (char *)src + 1;   
    }   
    return ret;   
} 

        上面之所以写成“if(NULL==dst||NULL ==src)”而不是写成“if (dst == NULL || src == NULL)”,也是为了降低犯错误的概率。我们知道,在C语言里面“==”和“=”都是合法的运算符,如果我们不小心写成了“if (dst = NULL || src = NULL)”还是可以编译通过,而意思却完全不一样了,但是如果写成“if (NULL=dst||NULL =src)”,则编译的时候就通不过了,所以我们要养成良好的程序设计习惯:常量与变量作条件判断时应该把常量写在前面V0.4版的代码首先对参数进行合法性检查,如果不合法就直接返回,这样虽然程序down掉的可能性降低了,但是性能却大打折扣了,因为每次调用都会进行一次判断,特别是频繁的调用和性能要求比较高的场合,它在性能上的损失就不可小觑。如果通过长期的严格测试,能够保证使用者不会使用零地址作为参数调用MyMemMove函数,则希望有简单的方法关掉参数合法性检查。我们知道宏就有这种开关的作用,所以V0.5版程序也就出来了。 

程序清单 6 V0.5版程序

void * MyMemMove(void *dst,const void *src,int count)   
{   
    void *ret=dst;   
#ifdef DEBUG   
    if (NULL==dst||NULL ==src)   
    {   
        return dst;   
    }   
#endif   
    while (count--)   
    {   
        *(char *)dst = *(char *)src;   
        dst = (char *)dst + 1;   
        src = (char *)src + 1;   
    }   
    return ret;   
}   

        如果在调试时我们加入“#defineDEBUG”语句,增强程序的健壮性,那么在调试通过后我们再改为“#undef DEBUG”语句,提高程序的性能。事实上在标准库里已经存在类似功能的宏:assert,而且更加好用,它还可以在定义DEBUG时指出代码在那一行检查失败,而在没有定义DEBUG时完全可以把它当作不存在。assert(_Expression)的使用非常简单,当_Expression为0时,调试器就可以出现一个调试错误,有了这个好东西代码就容易多了。 
程序清单 7 V0.6版程序

void * MyMemMove(void *dst,const void *src,int count)   
{   
    assert(dst);   
    assert(src);   
    void *ret=dst;   
    while (count--)   
    {   
        *(char *)dst = *(char *)src;   
        dst = (char *)dst + 1;   
        src = (char *)src + 1;   
    }   
    return ret;   
}   

        到目前为止,在语言层面上,我们的程序基本上没有什么问题了,那么是否真的就没有问题了呢?这就要求程序员从逻辑上考虑了,这也是优秀程序员必须具备的素质,那就是思维的严谨性,否则程序就会有非常隐藏的bug,就这个例子来说,如果用户用下面的代码来调用你的程序。 
程序清单 8 重叠的内存测试。

void Test()   
{   
    char p [256]= "hello,world!";   
    MyMemMove(p+1,p,strlen(p)+1);   
    printf("%s\n",p);   
}   

        如果你身边有电脑,你可以试一下,你会发现输出并不是我们期待的“hello,world!”(在“hello world!”前加个h),而是“hhhhhhhhhhhhhh”,这是什么原因呢?原因出在源地址区间和目的地址区间有重叠的地方,V0.6版的程序无意之中将源地址区间的内容修改了!有些反映快的同学马上会说我从高地址开始拷贝。粗略地看,似乎能解决这个问题,虽然区间是重叠了,但是在修改以前已经拷贝了,所以不影响结果。但是仔细一想,这其实是犯了和上面一样的思维不严谨的错误,因为用户这样调用还是会出错: 

MyMemMove( p, p+1, strlen(p)+1); 

【c++中内存拷贝函数(C++ memcpy)详解】       

        所以最完美的解决方案还是判断源地址和目的地址的大小,才决定到底是从高地址开始拷贝还是低地址开始拷贝,所以V0.7顺利成章地出来了。 
程序清单 9 V0.7版程序 

void * MyMemMove(void *dst,const void *src,int count)   
{   
    assert(dst);   
    assert(src);   
    void * ret = dst;   
    if (dst <= src || (char *)dst >= ((char *)src + count)) {   
        while (count--) {   
            *(char *)dst = *(char *)src;   
            dst = (char *)dst + 1;   
            src = (char *)src + 1;   
        }   
    }   
    else {   
        dst = (char *)dst + count - 1;   
        src = (char *)src + count - 1;   
        while (count--) {   
            *(char *)dst = *(char *)src;   
            dst = (char *)dst - 1;   
        src = (char *)src - 1;   
        }   
    }   
    return(ret);   
}  

        经过以上7个版本的修改,我们的程序终于可以算是“工业级”了。回头再来看看前面的测试用例,就会发现那根本就算不上是测试用例,因为它只调用了最正常的一种情况,根本达不到测试的目的。有了上面的经历,测试用例也就相应地出现了,我们不妨用字符数组来模拟内存。 
程序清单 10 相对全面的测试用例 。文章来源地址https://www.toymoban.com/news/detail-418903.html

void Test()   
{   
    char p1[256] = "hello,world!";   
    char p2[256] = {0};   
    MyMemMove(p2,p1,strlen(p1)+1);   
    printf("%s\n",p2);   
    MyMemMove(NULL,p1,strlen(p1)+1);   
    MyMemMove(p2,NULL,strlen(p1)+1);   
    MyMemMove(p1+1,p1,strlen(p1)+1);   
    printf("%s\n",p1);   
    MyMemMove(p1,p1+1,strlen(p1)+1);   
    printf("%s\n",p1);   
} 

void * memcpy ( void * dst,const void * src,size_t count)  
{  
    void * ret = dst;  
    while (count--) {  
        *(char *)dst = *(char *)src;  
        dst = (char *)dst + 1;  
        src = (char *)src + 1;  
    }  
    return(ret);  
}  
char *strcpy(char *des, const char *src){  
    assert((des != NULL) && (src != NULL));  
    char *ret = des; // 防止改变des的地址  
    while ((*des++ = *src++) !='\0') ;  
    return ret;  
}  

到了这里,关于【c++中内存拷贝函数(C++ memcpy)详解】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C语言】memcpy memmove memset memcmp 四大内存操作函数(详解+用法+模拟实现)

    头文件string.h中常用内存操作函数共有四大,学习完本篇文章,各种类型数组的常见处理轻松拿下。 对字符串(字符数组)的操作函数有很多,但是我们想要操作整型数组等呢: 这就需要内存操作函数了, memory在计算机科学中是内存的意思 ,这也是四大内存操作函数都有mem头

    2024年02月10日
    浏览(47)
  • 【C语言】memmove()函数(拷贝重叠内存块函数详解)

    🦄 个人主页 :修修修也 🎏 所属专栏 :C语言 ⚙️ 操作环境 : Visual Studio 2022 目录  一.memmove()函数简介 1.函数功能 2.函数参数 1.void * destination 2.onst void * source 3.size_t num 3.函数返回值 4.函数头文件 二.memmove()函数的具体使用 1.使用memmove()函数完成拷贝整型数组数据(目的地与源重

    2024年02月06日
    浏览(37)
  • 【C++】vector问题解决(非法的间接寻址,迭代器失效 , memcpy拷贝问题)

    送给大家一句话: 世界在旋转,我们跌跌撞撞前进,这就够了 —— 阿贝尔 加缪 我们之前实现了手搓vector,但是当时依然有些问题没有解决: 迭代器区间拷贝(非法的间接寻址问题) 迭代器失效问题 使用memcpy拷贝问题 接下来,我们一点一点来解决这些问题!!! 来看这个

    2024年04月09日
    浏览(54)
  • 【C++学习手札】-引用与内联函数以及C++中对const拓展详解(超详细!)

                                           食用指南:本文在有C基础的情况下食用更佳                                        🔥 这就不得不推荐此专栏了:C语言                                        🍀 内联函数前置知识 :宏函数

    2024年02月14日
    浏览(49)
  • C语言——内存操作函数(memcpy、memmove、memcmp、memset)

    本文章会详解C语言进阶内容,有关内存操作函数( memcpy,memmove,memcmp,memset )的使用说明、API文档该类函数原型以及模拟实现内存函数 首先我们从API文档中搜索memcpy查看一下该函数的原型 可以看出该函数有三个参数,那么这三个参数的作用分别是什么呢? 该函数的实现思

    2024年02月06日
    浏览(40)
  • 【C语言】内存函数memcpy和memmove的功能与模拟实现

    1.memcpy 功能:把source指向的前num个字节内容拷贝到destination指向的位置去,可以拷贝任意类型的数据。 注:1.memcpy并不关心\\0,毕竟传的也不一定是字符串,因此拷贝过程中遇到\\0也不会停下来。 2.num的单位是字节,并不是要拷贝的元素个数 3.如果source和destination有任何的重叠

    2024年02月19日
    浏览(39)
  • 【C语言内存函数精选】memcpy、memset、memmove及仿真实现!掌握内存操作的艺术!

    ❤️ 博客主页: 小镇敲码人 🍏 欢迎关注:👍点赞 👂🏽留言 😍收藏 🌞 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎 ❤️ 我的努力求学没有得到别的好处,只

    2024年02月15日
    浏览(37)
  • C++实现memcpy函数

    功能:memcpy为按字节拷贝内存函数,从源src所指的内存地址开始拷贝n个字节到目标dest为起始地址的内存中。 返回值:函数返回指向目标内存区dest的指针。 注意事项: memcpy应该实现按字节拷贝指定长度的内存内容,但若传入函数的实参dest和src指针的类型不同,直接自增++可

    2024年02月16日
    浏览(38)
  • 来不及哀悼了,接下来上场的是C语言内存函数memcpy,memmove,memset,memcmp

    今天又来写一篇C的文章,这里要讲的是C语言中的几个内存函数,主要是讲解功能和用法,望能耐心观看哦。望官方也多多曝光。 目录 memcpy  memmove memset  memcmp  memcpy  是 C 语言标准库中的一个函数,用于复制内存块的内容。它的主要作用是将一个源内存区域的内容复制到

    2024年02月22日
    浏览(39)
  • 【C语言】memcpy,memmove,memcmp,memset函数详解

    💐 🌸 🌷 🍀 🌹 🌻 🌺 🍁 🍃 🍂 🌿 🍄🍝 🍛 🍤 📃 个人主页 :阿然成长日记 👈点击可跳转 📆 个人专栏: 🔹数据结构与算法🔹C语言进阶 🚩 不能则学,不知则问,耻于问人,决无长进 🍭 🍯 🍎 🍏 🍊 🍋 🍒 🍇 🍉 🍓 🍑 🍈 🍌 🍐 🍍 #includestring.h 与strn

    2024年02月17日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包