strcpy,memcpy,memmove和内存重叠分析

时间:2022-05-23 16:55:18

strcpy,memcpy,memmove和内存重叠分析



一:strcpy函数用法和实现:

[cpp] view plaincopyprint?
  1.  /*  
  2.  
  3. GNU-C中的实现(节选):  
  4.  
  5. */  
  6.   
  7. char* strcpy(char *d, const char *s)   
  8.   
  9. {   
  10.   
  11.   char *r=d;   
  12.   
  13.   while((*d++=*s++));   
  14.   
  15.   return r;   
  16.   
  17. }   


有没有发现这个实现并不是很好。。至少没有判断是不是为NULL。一个更好的版本:

[cpp] view plaincopyprint?
  1. char *strcpy(char *strDest, const char *strSrc)  
  2. {  
  3.     assert((strDest!=NULL) && (strSrc !=NULL));  
  4.     char *address = strDest;   
  5.     while( (*strDest++ = * strSrc++) != '\0')   
  6.            
  7.     return address ;   
  8. }  


好好研究这段代码其实写的挺妙的。。(当然不是我写的)。。

 

二:memcpy函数用法和实现:

从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

微软实现:

[cpp] view plaincopyprint?
  1.  void * __cdecl memcpy (void * dst,const void * src, size_t count )   
  2.  {   
  3.      void * ret = dst;   
  4. #if defined (_M_MRX000) || defined (_M_ALPHA) || defined (_M_PPC)           
  5.      {           
  6.          extern void RtlMoveMemory( void *, const void *, size_t count );  
  7.          RtlMoveMemory( dst, src, count );  
  8.      }   
  9. #else  /* defined (_M_MRX000) || defined (_M_ALPHA) || defined (_M_PPC) */           
  10.      /*          * copy from lower addresses to higher addresses          */          
  11.      while (count--) {   
  12.          *(char *)dst = *(char *)src;  
  13.          dst = (char *)dst + 1;   
  14.          src = (char *)src + 1;  
  15.      }   
  16. #endif  /* defined (_M_MRX000) || defined (_M_ALPHA) || defined (_M_PPC) */          
  17.      return(ret);  
  18.  }   


写的是不是也很好。。仿佛天衣无缝啊!。。但是你有没有注意到一个问题:那就是内存重叠。。

什么叫内存重叠呢?就是两块内存区域有一个共同的部分。。是不是很SB的解释。。当存在这个问题的时候,那还和我们预期的那样拷贝吗?注意:理解了这个才是关键。。

例子:(引用网上一个例子)

[cpp] view plaincopyprint?
  1. int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};  
  2.  memcpy(a+4, a, sizeof(int)*6)   

如果照微软实现的话,那么输出应该是0123012301(理解这个再忘下看吧。。)

而我们想要达到的目的是输出:0123012345。。如果你实验一下,确实也是输出0123012345。。(对于这个问题,我也解释不了)。为了避免这样的情况发生,我们找到了一个更好的函数:memmove。。

 

三:memmove函数用法和实现

用法和memcpy函数一样。。

实现:

[cpp] view plaincopyprint?
  1. void *memmove(void *dest, const void *source, size_t count)  
  2. {  
  3.  assert((NULL != dest) && (NULL != source));  
  4.  char *tmp_source, *tmp_dest;  
  5.  tmp_source = (char *)source;  
  6.  tmp_dest = (char *)dest;  
  7.  if((dest + count<source) || (source + count) <dest))  
  8.  {// 如果没有重叠区域  
  9.      while(count--)  
  10.          *tmp_dest++ = *tmp_source++;  
  11.  }  
  12.  else  
  13.  { //如果有重叠(反向拷贝)  
  14.      tmp_source += count - 1;  
  15.      tmp_dest += count - 1;  
  16.      while(count--)  
  17.          *--tmp_dest = *--tmp;  
  18.  }  
  19.  return dest;  
  20. }  


一开始看到这段代码的时候,觉得好神奇啊。后来发现,其实有个很大的漏洞(先不说那些小问题),那就是内存重叠有两种情况。其实我觉得该作者也想到了,而且给出了解决办法。,估计是粗心大意吧。
:src<dest&&src+count>dest;  dest<src&&dest+count>src。。但是网上那位作者没有写清楚,下面我就重写一下上面的else部分:

[cpp] view plaincopyprint?
  1. else  
  2.  {   
  3.      //重叠1:(反向拷贝)  
  4.      if(tmp_source<=tmp_dest&&tmp_source+count>=tmp_dest)  
  5.      {  
  6.          tmp_source += count - 1;  
  7.          tmp_dest += count - 1;  
  8.          while(count--)  
  9.              *tmp_dest-- = *tmp_source--;  
  10.      }  
  11.       //重叠2:(正向拷贝)  
  12.      else  
  13.      {  
  14.          while (count--)  
  15.          {  
  16.              *tmp_dest++=*tmp_source++;  
  17.          }  
  18.      }  
  19.  }  


但写完之后发现傻逼了。。后面的那个else不可以和前面的那个if写在一起么?所以优化后的完整代码:

[cpp] view plaincopyprint?
  1. void *memmove(void *dest, const void *source, size_t count)  
  2. {  
  3.  assert((NULL != dest) && (NULL != source));  
  4.  char *tmp_source, *tmp_dest;  
  5.  tmp_source = (char *)source;  
  6.  tmp_dest = (char *)dest;  
  7.   
  8.  if(tmp_source<=tmp_dest&&tmp_source+count>=tmp_dest)  
  9.  {  
  10.      tmp_source += count - 1;  
  11.      tmp_dest += count - 1;  
  12.      while(count--)  
  13.          *tmp_dest-- = *tmp_source--;  
  14.  }  
  15.  else  
  16.  {  
  17.      while (count--)  
  18.      {  
  19.          *tmp_dest++=*tmp_source++;  
  20.      }  
  21.  }  
  22.   
  23.  return tmp_dest;  
  24. }  

 

此时就加上了内存重叠的判断。如果你理解了上面的那个(输出应该是0123012301),一切都明白了。。还有:别忘了那个问题哟,运行的时候还是输出:0123012345。。和我们的理论值有冲突。。希望大神发现问题能告知,一起学习,交流。。

 

此时才发现,平时写的那些代码,我们能懂他们多少呢?