去年在做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 都用宏定义,影响阅读。因此在项目中不建议乱使用宏定义。
以上的总结完毕,下一篇会总结内存对齐和效率知识。
本文详细解析了memmove与memcpy函数的实现原理,对比了两种函数在内存操作中的优势与不足,尤其关注内存对齐、效率及内存重叠处理等问题。
1996





