原型: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++;
- }
- void Test()
- {
- char p1[256] = ”hello,world!”;
- char p2[256] = {0};
- MyMemMove(p2,p1,strlen(p1));
- printf(“%s”,p2);
- }
首先我们看看函数声明是否合理,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;
- }
- }
还有几个细节需要注意,为了实现链式表达式,我们应该将返回值也改为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;
- }
程序清单 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;
- }
程序清单 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;
- }
程序清单 8 重叠的内存测试(面试中最容易考的问题)
- void Test()
- {
- char p [256]= "hello,world!";
- MyMemMove(p+1,p,strlen(p)+1);
- printf("%s\n",p);
- }
MyMemMove( p, p+1, strlen(p)+1); 所以最完美的解决方案还是判断源地址和目的地址的大小,才决定到底是从高地址开始拷贝还是低地址开始拷贝,所以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);
- }
程序清单 10 相对全面的测试用例
- 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);
- }