memmove、memcpy的实现

来源:互联网 发布:windows优化软件哪个好 编辑:程序博客网 时间:2024/05/06 09:42

http://blog.csdn.net/noworries/article/details/8875955

 

我们平时经常用memcpy以及strcpy等等库函数,都知道用法,但是知道他们的区别吗?因此,从本文开始,将会探究一下他们的内部实现以及之间的区别。

google一下memcpy我们可以在http://www.cplusplus.com/reference/cstring/memcpy/找到关于memcpy的介绍,如下所示

memcpy

<cstring>

void * memcpy ( void* destination, const void * source, size_t num );

Copy block of memory

Copies the values of num bytesfrom the location pointed by source directly to the memoryblock pointed bydestination.

The underlying type of the objects pointed by both the source and destination pointersare irrelevant for this function; The result is a binary copy of the data.

The function does not check for any terminating nullcharacter in source - it always copies exactly num bytes.

To avoid overflows, the size of the arrays pointed by both the destination and source parameters,shall be at leastnum bytes, and should not overlap (for overlapping memory blocks, memmove isa safer approach).

从上面关于memcpy的介绍中我们可以得到以下三点信息

1、从memcpy的函数原型可以发现,其可以操作任意类型的数据

2、memcpy不会检测NULL字符,它会准确的拷贝num个字节的数据

3、使用该函数需要避免数据溢出,以及不要覆盖

我么了解了memcpy的以上特点以后,下面看看memcpy是如何实现的,如下面代码所示

[cpp] view plaincopy
  1. void *memcpy(void *dest, void *src, unsigned long len)  
  2. {  
  3.         unsigned long i;  
  4.   
  5.         if ((unsigned long)dest%sizeof(long) == 0 && \  
  6.                 (unsigned long)dest%sizeof(long) == 0 && \  
  7.                 (len%sizeof(long) == 0)) {  
  8.   
  9.                 long *d = dest;  
  10.                 const long *s = src;  
  11.   
  12.                 for (i=0; i<len/sizeof(long); i++) {  
  13.                         d[i] = s[i];  
  14.                 }  
  15.         }  
  16.         else {  
  17.                 char *d = dest;  
  18.                 const char *s = src;  
  19.   
  20.                 for (i=0; i<len; i++) {  
  21.                         d[i] = s[i];  
  22.                 }  
  23.         }  
  24.   
  25.         return dest;  
  26. }  

以上代码是memcpy的一个实现过程,其中最大的一个特点是,当目的地址(dest)、源地址(src)是四字节对齐,以及拷贝的个数是4的整数个的时候,可以采用每次拷贝4个字节的方式,这样即可以加速拷贝,节省了memcpy执行时间,提高效率

测试以上代码如下所示:

[cpp] view plaincopy
  1. void test_memcpy(void)  
  2. {  
  3.         struct timeval start_time;  
  4.         struct timeval end_time;  
  5.         long time_spent;  
  6.   
  7.         long i;  
  8.         long dest[30000];  
  9.         long src[30000];  
  10.   
  11.         for (i=0; i<20000; i++) {  
  12.                 src[i] = i;  
  13.         }  
  14.   
  15.         gettimeofday (&start_time, NULL);  
  16.   
  17.         memcpy(dest, src, 20001);<span style="white-space:pre"> </span>/* <span style="color:#ff0000;">此处设置为20001是为了采用1字节拷贝的,测试结果只是比4字节拷贝多拷贝了1个字节,没多大影响</span>*/  
  18.   
  19.         gettimeofday (&end_time, NULL);  
  20.   
  21.         time_spent = (end_time.tv_sec - start_time.tv_sec)*1000000 + \  
  22.                         (end_time.tv_usec - start_time.tv_usec); /* us */  
  23.   
  24.         printf("1 byte per time copy,  time_spent is %dus \n", time_spent);  
  25.   
  26.         gettimeofday (&start_time, NULL);  
  27.   
  28.         memcpy(dest, src, 20000);  
  29.   
  30.         gettimeofday (&end_time, NULL);  
  31.   
  32.         time_spent = (end_time.tv_sec - start_time.tv_sec)*1000000 + \  
  33.                         (end_time.tv_usec - start_time.tv_usec); /* us */  
  34.   
  35.         printf("4 byte per time copy, time_spent is %dus \n", time_spent);  
  36. }  
采用以上代码,既可以测试出两种不同拷贝方式所花费的时间,从测试结果来看采用4字节拷贝方式效果是相当明显的,基本上是采用1个字节拷贝方式所花费时间的四分之一,测试结果如下所示:

以上4字节拷贝的方式中可以发现,4字节拷贝的确是有很大的优势? 但是以上设计中,要使用4字节拷贝却需要满足三个条件:第一、目的地址(dest)需要4字节对齐;第二、源地址(src)也需要四字节对齐;第三、拷贝个数需要是4的整数倍;如果不满足以上三个条件那么将会采用1字节方式进行拷贝。

因此,我们设想当不满足以上三个条件而拷贝字节数又是大于4个字节的内存块的时候,怎样采取4字节拷贝方式进行拷贝?

意外测试中发现,当采用linux内部的memcpy函数时,拷贝速度之快,如下图所示


因此,后面还需要详细分析linux里面memcpy函数的实现还是有相当必要的,此工作留到后面继续分析

查看linux内部memcpy的C语言实现,如下所示


memcpy缺陷:

前一篇文章描述了memcpy的实现方法,本文将探讨下memcpy运用中存在的缺陷,即memcpy不能拷贝目的地址(dest)和源地址(src)内存空间有重合的部分,更为确切的说应该是当目的地址大于源地址的时候,不能够有重合部分,否则源地址重合部分数据会发生错误

以下分析只考虑目的地址和源地址有数据重合情况,在没有数据重合情况的时候,memcpy是能够正确的使用,不会出现错误

1、当目的地址(dest)小于源地址(src)且有数据重合的时候,如下图所示

黄色部分则是dest和src数据重合部分,为什么说当目的地址小于源地址且有重合部分的时候memcpy还能够正确的拷贝呢?我们知道memcpy都是从目的地址和源地址开始进行拷贝的,也就是说当拷贝目的地址增长到了源地址开始处的时候,源地址以前的数据已经拷贝完成了,因此,能够正确的进行数据拷贝。如下图所示

dest增长到前一张图片的src开始位置的时候,此处的源地址的数据已经进行了拷贝,因此说当目的地址小于源地址且有数据重合的时候memcpy能够进行数据拷贝

下面我们来探讨另一种情况,当源地址小于目的地址且有数据重合的时候,如下图所示

当我们把源数据(src)拷贝到目的地址(dest)的时候,比如我们在src处拷贝一个字节到dest处的时候,我们发现目的地址开始处,即源地址和目的地址重合部分数据被破坏,如下图所示

我们拷贝数据1、2、3到目的地址的开始三个地方,即存了4、5、6的时候,发现源地址的最后结束的4、5、6三个数据被破坏掉了,因此,下次拷贝该三个地址的数据时候,则会拷贝坏掉的数据1、2、3

总结以上两种情况可以发现,memcpy能够实现前向拷贝(即从高地址拷贝数据到低地址),不能够实现后向拷贝(即从低地址拷贝数据到高地址),那么,我们怎么实现后向拷贝呢? 即memmove函数的实现,下篇文章进行探讨。


上篇文章中,我们探讨了memcpy的缺陷,发现memcpy不能够实现后向拷贝,最后我们提出了其解决方案,本篇文章我们就来实现该方法,即memmove的实现

第一眼看到memmove的时候,第一映像是,memmove是将源数据剪切到目的地址,然后将源数据进行清除了,其实memmove就是拷贝的意思,只是在memcpy的基础上,增加了一个后向拷贝数据,实现原理如下所示

 

[cpp] view plaincopy
  1. void *memmove(void *dest, void *src, unsigned long count)  
  2. {  
  3.     char *tmp;    
  4.     const char *s;  
  5.   
  6.     if (dest <= src) {  
  7. /* 
  8.         tmp = dest;  
  9.         s = src; 
  10.         while (count--) { 
  11.             *tmp++ = *src++; 
  12.         } 
  13. */  
  14.         memcpy(dest, src, count);  
  15.     }  
  16.     else {  
  17.         tmp = dest;  
  18.         tmp += count;  
  19.         s = src;  
  20.         s += count;  
  21.         while (count--) {  
  22.             *--tmp = *--s;  
  23.         }  
  24.     }  
  25. }  


 

以上实现中,我们可以发现,当拷贝是前向拷贝的时候,我们可以memmove可以调用memcpy进行,当后向拷贝的时候,我们通过先拷贝重叠部分数据即可。

因此,memmove不论前向拷贝,还是后向拷贝总体思想就是优先拷贝重叠数据


 

 

0 0
原创粉丝点击