strcpy和memcpy函数的深入剖析
上面的文章已经为讲解了strcpy和memcpy函数的区别,但是后来通过验证,发现两个函数实现的代码写的有各种漏洞,所以在此继续深入的理解去解析这两个函数的实现。
首先strcpy函数的实现,上篇文章中的代码实现如下:
char *strcpy(char *dest, const char *src) // 实现src到dest的复制{ if ((src == NULL) || (dest == NULL)) //判断参数src和dest的有效性 { return NULL; } char *strdest = dest; //保存目标字符串的首地址 while ((*strDest++ = *strSrc++)!='\0'); //把src字符串的内容复制到dest下 return strdest;}
但是呢,这个函数没有考虑拷贝时内存重叠的情况,用下面的测试用例就能使调用strcpy函数的程序崩溃:
char str[10]="hello"; strcpy(str+1,str);
打印的结果就是hhello,这就是出现了内存重叠的现象,所以这个函数的实现考虑的不全面!
合理的实现方法,我觉得应该结合memcpy内存拷贝函数来实现,因为memcpy函数实现的时候考虑到了内存的重叠问题(ps:在上篇文章中的内存拷贝函数的实现没有考虑内存的重叠问题,等下下面会有进一步的讲解),可以完成指定大小的内存拷贝,经过修改之后我对strcpy函数的实现采用如下的方式:
char *strcpy(char *dest,const char *src) { assert(dst != NULL); assert(src != NULL); char *strdest = dest; memcpy(dest,src,strlen(src)+1); return strdest; }
上面之所以不用
if ((src == NULL) || (dest == NULL)) //判断参数src和dest的有效性 { return NULL; }
这个判断函数,首先对参数进行合法性检查,如果不合法就直接返回,这样虽然程序down掉的可能性降低了,但是性能去大打折扣了,因为每次调用都会进行一次判断,特别是频繁的调用和性能要求比较高的场合,它在性能上的损失就不可小觑了。
下面就是详解memcpy函数的实现:
上篇文章中的实现方法如下:
void *memcpy(void *memTo, const void *memFrom, size_t size){ if((memTo == NULL) || (memFrom == NULL)) //memTo和memFrom必须有效 return NULL; char *tempFrom = (char *)memFrom; //保存memFrom首地址 char *tempTo = (char *)memTo; //保存memTo首地址 while(size -- > 0) //循环size次,复制memFrom的值到memTo中 *tempTo++ = *tempFrom++ ; return memTo;}
那么这样的实现方法有什么不对的地方呢?这就要求作为程序员的我们从逻辑上进行考虑了,下面我们将经过一个例子进行测试下你就知道了。
测试程序如下:
void Test() { char p [256]= "hello,world!"; memcpy(p+1,p,strlen(p)+1); printf("%s\n",p); }
测试结果并不是我们所期待的“hhello,world!”(在hello,world!前面加个h),而是“hhhhhhhhhhhhh”,这是什么原因呢?
原因在于源地址和目的地址之间有重叠的地方,程序无意之中将源地址区间的内容修改了!
或许有的人已经有答案了,从高地址开始拷贝,虽然区间重叠了,但在程序修改以前已经拷贝了,所以并不影响结果。这样似乎是正确的,但是请看下面的的一句调用,你就会明白这依然犯了思维不严谨的错误:
memcpy( p, p+1, strlen(p)+1);
所以,我们应该首先判断源地址和目的地址的大小再决定是从高地址开始拷贝还是从低地址开始拷贝,合理的程序如下:
void *memcpy(void *memTo,const void *memFrom,size_t size) { assert(memTo); assert(memFrom); void *ret = memTo; if (( memTo <= memFrom ) || ((char *)memTo >= ((char *)memFrom + size)))//源地址和目的地址不重叠,低字节向高字节拷贝 { while(size--) { *(char *)memTo = *(char *)memFrom; memTo = (char *)memTo + 1; memFrom = (char *)memFrom + 1; } } else //源地址和目的地址重叠,高字节向低字节拷贝 { memTo = (char *)memTo + size - 1; memFrom = (char *)memFrom + size - 1; while(size--) { *(char *)memTo = *(char *)memFrom; memTo = (char *)memTo - 1; memFrom = (char *)memFrom - 1; } } return ret; }