memcpy 内存覆盖问题

[以下内容转自:http://my.oschina.net/renhc/blog/36345,作者:任洪彩,2011-12-02],这篇解释的相对比较透彻些!

面试中如问到memcpy的实现,那就要小心了,这里有陷阱。

先看下标准memcpy()的解释:注意下面的注释,对于地址重叠的情况,该函数的行为是未定义的

事实上所说的陷阱也在于此,自己动手实现memcpy()时就需要考虑地址重叠的情况。

另外,标准库也提供了地址重叠时的内存拷贝函数:memmove(),那么为什么还要考虑重写memcpy()函数呢?因为memmove()函数的实现效率问题,该函数把源字符串拷贝到临时buf里,然后再从临时buf里写到目的地址,增加了一次不必要的开

下面给出memcpy()的实现,为了与标准库函数区分,我们实现其包裹函数:

 

[cpp]  view plain  copy
  1. <span style="font-size:14px;" deep="5">void*Memcpy(void *dst, const void *src, size_t size);  
  2.    
  3. int main(intargc, char *argv[])  
  4. {  
  5.     char buf[100] = "abcdefghijk";  
  6.     memcpy(buf+2, buf, 5);  
  7.     //Memcpy(buf+2, buf, 5);  
  8.     printf("%s\n", buf+2);  
  9.    
  10.          return 0;  
  11. }  
  12.    
  13. void*Memcpy(void *dst, const void *src, size_t size)  
  14. {  
  15.     char *psrc;  
  16.     char *pdst;  
  17.           
  18.     if(NULL == dst || NULL == src)  
  19.     {  
  20.         return NULL;  
  21.     }  
  22.           
  23.     if((src < dst) && (char *)src +size > (char *)dst) // 自后向前拷贝  
  24.     {  
  25.         psrc = (char *)src + size - 1;  
  26.         pdst = (char *)dst + size - 1;  
  27.         while(size--)  
  28.         {  
  29.             *pdst-- = *psrc--;  
  30.         }  
  31.     }  
  32.     else  
  33.     {  
  34.         psrc = (char *)src;  
  35.         pdst = (char *)dst;  
  36.         while(size--)  
  37.         {  
  38.             *pdst++ = *psrc++;  
  39.         }  
  40.     }  
  41.           
  42.     return dst;  
  43. }</span>  

使用Memcpy()的结果: abcdehijk

使用memcpy()的结果: abcdehijk,//在VC6.0中仍然输出abcdehijk,可能的原因就是:有的函数库实现的memcpy函数考虑到了地址重叠,所以不会有错,而另外一些函数库并没有考虑到。

可以看到标准库函数的源字符串在拷贝的过程中被污染了。



前段时间和朋友讨论关于C基础函数memcpy的实现细节时,收获颇多。这个函数在C / C++编程领域中使用率是比较高的(可能排在前10左右)。但鲜有人去研究其实现原理。为了弄清楚其实现,我给自己出了一道题目,就是用C实现一个memcpy的函数。先看标准memcpy函数的参数和返回值:

[cpp]  view plain  copy
  1. void* memcpy(void* dst, void*  src, size_t size);  
dst  - 目的内存地址,src - 源内出地址,size- 需要复制的长度。返回值为dst。


初想一下,这个函数很简单,就随手写了一个实现。如下:

[cpp]  view plain  copy
  1. void* my_memcpy(void *dst, const void* src, size_t size)  
  2. {  
  3.     if(dst == NULL || src == NULL || size <= 0)  
  4.         return dst;  
  5.     char* dst_pos = (char *)dst;  
  6.     char* src_pos = (char *)src;  
  7.   
  8.     while(size > 0){  
  9.         *dst_pos++ = *src_pos++;  
  10.         size --;  
  11.     }  
  12.   
  13.     return dst;  
  14. }  
咋一看,这个函数是正确的。我使用了基本的测试代码如下:

[cpp]  view plain  copy
  1. int main(int argc, char* argv[])  
  2. {  
  3.     unsigned char buf[15] = {0};  
  4.     unsigned char dst_buf[15] = {0};  
  5.   
  6.     for(unsigned char i = 3; i < 13; i ++)  
  7.         buf[i] = i;  
  8.   
  9.     printf("src buf = ");  
  10.         for(int i = 0; i < 15; i ++)  
  11.         printf("%d ", buf[i]);  
  12.   
  13.     printf("\r\n");  
  14.     //不同BUFF之间拷贝  
  15.     my_memcpy(dst_buf, buf, 15);  
  16.   
  17.     printf("from buf copy to dst buf, dst_buf = ");  
  18.     for(int i = 0; i < 15; i ++)  
  19.         printf("%d ", dst_buf[i]);  
  20.     //同一个BUFF向后拷贝  
  21.     my_memcpy(buf + 4, buf + 3, 10);  
  22.   
  23.     printf("\nfrom buf + 3 copy to buf + 5, buf =");  
  24.     for(int i = 0; i < 15; i ++){  
  25.         printf("%d ", buf[i]);  
  26.         buf[i] = 0;  
  27.     }  
  28.     //同一个BUFF向前拷贝  
  29.     for(unsigned char i = 3; i < 13; i ++)  
  30.         buf[i] = i;  
  31.     my_memcpy(buf, buf + 3, 10);  
  32.     printf("\nfrom buf + 5 coy to buf, buf = ");  
  33.     for(int i = 0; i < 15; i ++)  
  34.         printf("%d ", buf[i]);  
  35.   
  36.     return 0;  
  37. }  


如果用上测试用例来测试,上面写的my_memcpy是只能过不同buf之间的拷贝和同一BUF向前拷贝,同一buf向后拷贝的情况重叠内存是被覆盖了的。于是我进行了修改:

[cpp]  view plain  copy
  1. void* my_memcpy(void *dst, const void* src, size_t size)  
  2. {  
  3.     if(dst == NULL || src == NULL || size <= 0)  
  4.         return dst;  
  5.     char* dst_pos = (char *)dst + size;  
  6.     char* src_pos = (char *)src + size;  
  7.   
  8.     while(size > 0){  
  9.         *dst_pos-- = *src_pos--;  
  10.         size --;  
  11.     }  
  12.   
  13.     return dst;  
  14. }  

从上可以看出,我做了向前拷贝兼容,防止向后拷贝重叠地址被覆盖的情况。但第三种情况向前拷贝的重叠内存还是被覆盖了。这就要思考根据参数判断是从前拷贝还是从后拷贝。于是修改为:

[cpp]  view plain  copy
  1. void* my_memcpy(void *dst, const void* src, size_t size)  
  2. {  
  3.     if(dst == NULL || src == NULL || size <= 0)  
  4.         return dst;  
  5.   
  6.     char* dst_pos = (char *)dst;  
  7.     char* src_pos = (char *)src;  
  8.     if(dst_pos < src_pos + size && dst > src){ //DOWN COPY,向前拷贝  
  9.         dst_pos = dst_pos + size;  
  10.         src_pos = src_pos + size;  
  11.   
  12.         while(size > 0){  
  13.             *dst_pos-- = *src_pos--;  
  14.             size --;  
  15.         }  
  16.     }  
  17.     else { //UP COPY,向后拷贝  
  18.         while(size > 0){  
  19.             *dst_pos++ = *src_pos++;  
  20.             size --;  
  21.         }  
  22.     }  
  23.   
  24.     return dst;  
  25. }  
这个实现是兼容向前拷贝和向后拷贝、不同BUF拷贝的情况,也近似C函数库的实现。后来我跟踪了下C函数库的实现,它是用ASM进行实现的。我贴出VC++中的实现:

[plain]  view plain  copy
  1. ifdef MEM_MOVE  
  2.         _MEM_     equ <memmove>  
  3. else  ; MEM_MOVE  
  4.         _MEM_     equ <memcpy>  
  5. endif  ; MEM_MOVE  
  6.   
  7. %       public  _MEM_  
  8. _MEM_   proc \  
  9.         dst:ptr byte, \  
  10.         src:ptr byte, \  
  11.         count:IWORD  
  12.   
  13.               ; destination pointer  
  14.               ; source pointer  
  15.               ; number of bytes to copy  
  16.   
  17. ;       push    ebp             ;U - save old frame pointer  
  18. ;       mov     ebp, esp        ;V - set new frame pointer  
  19.   
  20.         push    edi             ;U - save edi  
  21.         push    esi             ;V - save esi  
  22.   
  23.         mov     esi,[src]       ;U - esi = source  
  24.         mov     ecx,[count]     ;V - ecx = number of bytes to move  
  25.   
  26.         mov     edi,[dst]       ;U - edi = dest  
  27.   
  28. ;  
  29. ; Check for overlapping buffers:  
  30. ;       If (dst <= src) Or (dst >= src + Count) Then  
  31. ;               Do normal (Upwards) Copy  
  32. ;       Else  
  33. ;               Do Downwards Copy to avoid propagation  
  34. ;  
[plain]  view plain  copy
  1. ..........  
后面的ASM代码太长,我没有全部给出。它也是做了情况判断,只是整个实现考虑的情况很多,细节考虑的很多。所以, 通过对memcpy函数的分析,我们可以很好的了解内存操作需要注意的地方,尤其是同一buf向前拷贝和向后拷贝的问题,我们不仅仅要会使用API,更需要弄清楚API背后的逻辑


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值