c++实现内存拷贝的函数_第1页
c++实现内存拷贝的函数_第2页
c++实现内存拷贝的函数_第3页
c++实现内存拷贝的函数_第4页
c++实现内存拷贝的函数_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、写一个函数,完成内存移动,并为其写一个简单的测试用例来进行测试。够简单的吧?有的同学很快就写出了答案,详见程序清单1与程序清单2。程序清单 1 V0.1版程序void MyMemMove(char *dst,char *src,int count)while(count-)*dst+ = *src+;程序清单 2 测试用例void Test()char p1256 = ”hello,world!”;char p2256 = 0;MyMemMove(p2,p1,strlen(p1);printf(“%s”,p2);客观地讲,相比那些交白卷或者函数声明都不会写的同学来说,能够写出这段代码的同学已经

2、非常不错了,至少在C语言这门课程上已经达到了现行高校的教育目标,但是离企业的用人要求还有一定的距离。我们不妨将上面的程序称为V0.1版本,看看还有没有什么地方可以改进。首先我们看看函数声明是否合理,V0.1版的程序将源地址和目的地址都用char *来表示,这样当然也没有什么问题,但是让其他人使用起来却很不方便,假如现在要将count个连续的结构体对象移动到另外一个地方去,如果要使用v0.1的程序的话,正确的写法如下:MyMemMove(char *)dst,(char *)src,sizeof(TheStruct)*count)也就是说我们需要将结构体指针强制转换成char * 才能够正常工作

3、,这样除了字符串以外其它的类型都不可避免地要进行指针强制转换,否则编译器就会呱呱叫,比如在VC+2008下就会出现这样的错误:error C2664: 'MyMemMove' : cannot convert parameter 1 from 'TheStruct *' to 'char *'那么如何解决这个问题呢?其实很简单,我们知道有一种特别的指针,任何类型的指针都可以对它赋值,那就是void *,所以应该将源地址和目的地址都用void*来表示。当然函数体的内容也要作相应的改变,这样我们就得到了V0.2版的程序。程序清单 3 V0.2版程序v

4、oid 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是一劳永逸!还有几个细节需要注意,为了实现链式表达式,我们应该将返回值也改为voi

5、d *。此外,如果我们不小心将“*(char *)dst = *(char *)src;”写反了,写成“*(char *)src = *(char *)dst;”编译照样通过,而为了找出这个错误又得花费不少时间。注意到src所指向的内容在这个函数内不应该被改变,所有对src所指的内容赋值都应该被禁止,所以这个参数应该用const修饰,如果有类似的错误在编译时就能够被发现:error C3892: 'src' : you cannot assign to a variable that is const作为程序员犯错误在所难免,但是我们可以利用相对难犯错误的机器,也就是编译器来降

6、低犯错误的概率,这样我们就得到了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),这是完全可能的,因为一般来说这些地址都是程序计算出来的,那就难免会算错,出现零地址或者其它的非

7、法地址也不足为奇。可以预料的是,如果出现这种情况的话,则程序马上就会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 =

8、 (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版的代码首先对参数进行合法性检查,如

9、果不合法就直接返回,这样虽然程序dwon掉的可能性降低了,但是性能却大打折扣了,因为每次调用都会进行一次判断,特别是频繁的调用和性能要求比较高的场合,它在性能上的损失就不可小觑。如果通过长期的严格测试,能够保证使用者不会使用零地址作为参数调用MyMemMove函数,则希望有简单的方法关掉参数合法性检查。我们知道宏就有这种开关的作用,所以V0.5版程序也就出来了。程序清单 6 V0.5版程序void * MyMemMove(void *dst,const void *src,int count)void *ret=dst;#ifdef DEBUGif (NULL=dst|NULL =src)re

10、turn dst;#endifwhile (count-)*(char *)dst = *(char *)src;dst = (char *)dst + 1;src = (char *)src + 1;return ret;如果在调试时我们加入“#define DEBUG”语句,增强程序的健壮性,那么在调试通过后我们再改为“#undef DEBUG”语句,提高程序的性能。事实上在标准库里已经存在类似功能的宏:assert,而且更加好用,它还可以在定义DEBUG时指出代码在那一行检查失败,而在没有定义DEBUG时完全可以把它当作不存在。assert(_Expression)的使用非常简单,当_E

11、xpression为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; 到目前为止,在语言层面上,我们的程序基本上没有什么问题了,那么是否真的就没有问题了呢?这就要求程序

12、员从逻辑上考虑了,这也是优秀程序员必须具备的素质,那就是思维的严谨性,否则程序就会有非常隐藏的bug,就这个例子来说,如果用户用下面的代码来调用你的程序。程序清单 8 重叠的内存测试void Test()char p 256= "hello,world!"MyMemMove(p+1,p,strlen(p)+1);printf("%sn",p);如果你身边有电脑,你可以试一下,你会发现输出并不是我们期待的“hhello,world!”(在“hello world!”前加个h),而是“hhhhhhhhhhhhhh”,这是什么原因呢?原因出在源地址区间和目的地

13、址区间有重叠的地方,V0.6版的程序无意之中将源地址区间的内容修改了!有些反映快的同学马上会说我从高地址开始拷贝。粗略地看,似乎能解决这个问题,虽然区间是重叠了,但是在修改以前已经拷贝了,所以不影响结果。但是仔细一想,这其实是犯了和上面一样的思维不严谨的错误,因为用户这样调用还是会出错:MyMemMove( p, p+1, strlen(p)+1);所以最完美的解决方案还是判断源地址和目的地址的大小,才决定到底是从高地址开始拷贝还是低地址开始拷贝,所以V0.7顺利成章地出来了。程序清单 9 V0.7版程序void * MyMemMove(void *dst,const void *src,in

14、t 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 相对全面的测试用例void Test()char p1256 = "hello,world!"char p2256 = 0;MyMe

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论