从memcpy源码谈内存操作

本文详细解析了memmove与memcpy函数的实现原理,对比了两种函数在内存操作中的优势与不足,尤其关注内存对齐、效率及内存重叠处理等问题。

去年在做Linux项目过程中阅读别人的代码,发现项目中的内存操作都封装为一个安全拷贝的接口函数。这个所谓的安全拷贝其实就是判断目的地址的内存能否容纳源地址的内容。以前对内存操作处理,只是停留在memmove或memcpy接口的使用上,没有仔细想过自己去实现memcpy该如何做。因此整理了有关内存操作的知识。

先看一段网上别人贴的源代码:



void *memcpy(void *dest, const
void *source, size_t count)

{

    assert((NULL != dest) &&
(NULL != source));

char *tmp_dest = (char *)dest;

char *tmp_source = (char *)source;

while(count --)//不对是否存在重叠区域进行判断

*tmp_dest ++ = *tmp_source ++;

return dest;

}

这段代码看上去没有什么问题。基本功能实现,形参dest,source指针加入了断言判断,源地址使用const修饰,防止内容被篡改。函数内部也定义了两个临时指针变量tmp_dest、temp_source分别指向了dest,source,保证了形参的完整性,这样return dest就是返回首地址。那么这段代码如果作为接口,供第三方使用,会产生什么问题?

第一,
one byte one 复制,效率极低,无法发挥处理器的性能。因为每次处理器对内存存取都有一定的开销。

第二,
没有考虑内存对齐的情况。小部分处理器不支持内存不对齐的情况,所以访问不对齐的地址,会出错。但是现在大部分的处理器支持内存不对齐,对内存进行存取时候,效率会降低。关于内存对齐,后面后讲解。

第三,
没有考虑内存重叠的情况。即在同一片内存缓存,dest,source有交叉的情况。

针对内存重叠的情况,网上给出的源代码如下面:



void *memmove(void *dest, const void *source, size_t count)

{

    assert((NULL != dest) &&
(NULL != source));

    char *tmp_source, *tmp_dest;

    tmp_source = (char *)source;

    tmp_dest = (char *)dest;



   if((dest + count<source) || (source
+ count) <dest))

   {// 如果没有重叠区域

       while(count--)

      *tmp_dest++ = *tmp_source++;

   }

  else

  { //如果有重叠(反向拷贝)

     tmp_source += count - 1;

     tmp_dest += count - 1;

     while(count--)

        *tmp_dest-- = *tmp--; 

  }

  return dest;

}


这里有一个不是很重要的错误如下:

if((dest + count<source) || (source + count) <dest))

根据自己在Linux做实验的结果:

指针变量dest、source 分别指向同一个地址,然后执行<、<=、>和>=关系运算。结果为0.

针对上面的缺点,在glibc-2.28版本中,看看memcopy和memmove如何实现的

由于glibc的实现比较复杂,下面列出函数的实现代码。

Memcpy源码:



void *memcpy (void *dstpp, const void *srcpp, size_t len)

{

  unsigned long int dstp = (long
int) dstpp;

  unsigned long int srcp = (long
int) srcpp;

 

  /* Copy from the beginning to the
end.  */

 

  /* If there not too few bytes to
copy, use word copy.  */

  if (len >= OP_T_THRES)

    {

      /* Copy just a few bytes to
make DSTP aligned.  */

      len -=
(-dstp) % OPSIZ;//根据目的地址,计算对齐后的长度

      BYTE_COPY_FWD (dstp, srcp,
(-dstp) % OPSIZ);//复制前几个字节,达到按OPSIZ大小字节对齐的目的

 

      /* Copy whole pages from SRCP
to DSTP by virtual address manipulation,

     as much as possible.  */

 

      PAGE_COPY_FWD_MAYBE (dstp,
srcp, len, len);//根据虚拟内存的特性,按页拷贝

 

      /* Copy from SRCP to DSTP
taking advantage of the known alignment of

     DSTP. 
Number of bytes remaining is put in the third argument,

     i.e. in LEN. 
This number may vary from machine to machine.  */

 

      WORD_COPY_FWD (dstp, srcp,
len, len);//按一个字复制

 

      /* Fall out and copy the
tail.  */

    }

 

  /* There are just a few bytes to
copy.  Use byte memory operations.  */

  BYTE_COPY_FWD (dstp, srcp, len); //还有剩余,则单个字节复制

 

  return dstpp;

}


memmove源码:



#include <string.h>

#include <memcopy.h>

 

/* All this is so that bcopy.c can #include

  
this file after defining some things. 
*/

#ifndef     a1

#define    a1    dest /*
First arg is DEST.  */

#define    a1const

#define    a2    src    /*
Second arg is SRC.  */

#define    a2const   const

#undef memmove

#endif

#if    !defined(RETURN)
|| !defined(rettype)

#define    RETURN(s)       return (s) /* Return DEST.  */

#define    rettype             void *

#endif

 

#ifndef MEMMOVE

#define MEMMOVE memmove

#endif

 

rettype

inhibit_loop_to_libcall

MEMMOVE (a1const void *a1, a2const void
*a2, size_t len)

{

 
unsigned long int dstp = (long int) dest;

 
unsigned long int srcp = (long int) src;

 

  /*
This test makes the forward copying code be used whenever possible.

    
Reduces the working set.  */

  if (dstp - srcp >=
len)   /* *Unsigned* compare!  *///两个指针即使指向地址相同可以相减

    {

     
/* Copy from the beginning to the end. 
*/

 

#if MEMCPY_OK_FOR_FWD_MEMMOVE

     
dest = memcpy (dest, src, len);

#else

     
/* If there not too few bytes to copy, use word copy.  */

     
if (len >= OP_T_THRES)

        {

          /* Copy just a few bytes to make DSTP
aligned.  */

          len -= (-dstp) % OPSIZ;

          BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ);

 

          /* Copy whole pages from SRCP to DSTP by
virtual address

             manipulation, as much as possible.  */

 

          PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len);

 

          /* Copy from SRCP to DSTP taking advantage of
the known

             alignment of DSTP.  Number of bytes remaining is put

             in the third argument, i.e. in LEN.  This number may

             vary from machine to machine.  */

 

          WORD_COPY_FWD (dstp, srcp, len, len);

 

          /* Fall out and copy the tail.  */

        }

 

     
/* There are just a few bytes to copy. 
Use byte memory operations.  */

     
BYTE_COPY_FWD (dstp, srcp, len);

#endif /* MEMCPY_OK_FOR_FWD_MEMMOVE */

    }

  Else//有重叠,则反向拷贝。之后跟正向拷贝处理一样

    {

     
/* Copy from the end to the beginning. 
*/

     
srcp += len;

     
dstp += len;

 

     
/* If there not too few bytes to copy, use word copy.  */

     
if (len >= OP_T_THRES)

        {

          /* Copy just a few bytes to make DSTP
aligned.  */

          len -= dstp % OPSIZ;

          BYTE_COPY_BWD (dstp, srcp, dstp % OPSIZ);

 

          /* Copy from SRCP to DSTP taking advantage of
the known

             alignment of DSTP.  Number of bytes remaining is put

             in the third argument, i.e. in LEN.  This number may

             vary from machine to machine.  */

 

          WORD_COPY_BWD (dstp, srcp, len, len);

 

          /* Fall out and copy the tail.  */

        }

 

     
/* There are just a few bytes to copy. 
Use byte memory operations.  */

     
BYTE_COPY_BWD (dstp, srcp, len);

    }

 

 
RETURN (dest);

}


这段代码形参,函数名,return 都用宏定义,影响阅读。因此在项目中不建议乱使用宏定义。

以上的总结完毕,下一篇会总结内存对齐和效率知识。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值