目录
1.引言
memcpy
在C/C++开发中很常用,面试时候也会经常被问到memcpy
和memmove
的区别,本文先介绍下memcpy
的原理。
可能各个平台的实现原理不同,但都大同小异,本文主要介绍glibc
中的memcpy
。
2.基本定义
void *memcpy(void *dest, const void *src, size_t n);
memcpy
的功能是将src
指向的内存区域的前n个字节复制到dest
指向的内存区域。
使用memcpy
,需要注意以下几点:
-
不处理重叠区域(这是
memmove
的职责) -
返回目标地址dest
-
要求调用者确保:
-
dest
和src
都是有效指针 -
内存区域不重叠
-
有足够的空间
-
3.实现策略
glibc
的memcpy
实现采用了三层复制策略:
-
字节复制(Byte Copy)
-
字复制(Word Copy)
-
页复制(Page Copy)
4.核心实现
整体策略大体如下
-
数据量小于16字节:
-
直接使用字节复制
-
避免对齐开销
-
-
数据量在16字节到16KB之间(不一定是16KB,为了方便起见,统一使用16,你理解原理即可):
-
先对齐
-
使用字复制
-
处理剩余字节
-
-
数据量大于16KB:
-
先对齐
-
尝试页复制
-
字复制处理剩余数据
-
字节复制处理尾部
-
以下是glibc-2.10.1中memcpy函数的源码:
#include <string.h>
#include <memcopy.h>
#include <pagecopy.h>
#undef memcpy
void *
memcpy (dstpp, srcpp, len)
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);
/* 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;
}
libc_hidden_builtin_def (memcpy)
详细分析:
字节复制
最基础的复制方式,一次复制一个字节:
#define BYTE_COPY_FWD(dst_bp, src_bp, nbytes) \
do \
{ \
size_t __nbytes = (nbytes); \
while (__nbytes > 0) \
{ \
byte __x = ((byte *) src_bp)[0]; \
src_bp += 1; \
__nbytes -= 1; \
((byte *) dst_bp)[0] = __x; \
dst_bp += 1; \
} \
} while (0)
使用场景:
-
小数据量复制
-
处理非对齐数据
-
处理剩余字节
字复制
按机器字长进行复制,提高效率:
#define WORD_COPY_FWD(dst_bp, src_bp, nbytes_left, nbytes) \
do \
{ \
if (src_bp % OPSIZ == 0) \
_wordcopy_fwd_aligned (dst_bp, src_bp, (nbytes) / OPSIZ); \
else \
_wordcopy_fwd_dest_aligned (dst_bp, src_bp, (nbytes) / OPSIZ); \
src_bp += (nbytes) & -OPSIZ; \
dst_bp += (nbytes) & -OPSIZ; \
(nbytes_left) = (nbytes) % OPSIZ; \
} while (0)
优化特点:
-
利用CPU字长进行批量复制
-
要求内存对齐
-
比字节复制更高效
页复制
最高效的复制方式,适用于大块数据:
#define PAGE_COPY_FWD_MAYBE(dstp, srcp, nbytes_left, nbytes) \
do { \
if ((nbytes) >= PAGE_COPY_THRESHOLD \
&& PAGE_OFFSET ((dstp) - (srcp)) == 0) \
{ \
/* 处理第一个不完整页 */ \
size_t nbytes_before = PAGE_OFFSET (-(dstp)); \
if (nbytes_before != 0) \
{ \
WORD_COPY_FWD (dstp, srcp, nbytes_left, nbytes_before); \
nbytes -= nbytes_before; \
} \
/* 执行页复制 */ \
PAGE_COPY_FWD (dstp, srcp, nbytes_left, nbytes); \
} \
} while (0)
#define PAGE_COPY_THRESHOLD (16384)
#define PAGE_SIZE __vm_page_size
#define PAGE_COPY_FWD(dstp, srcp, nbytes_left, nbytes) \
((nbytes_left) = ((nbytes) \
- (__vm_copy (__mach_task_self (), \
(vm_address_t) srcp, trunc_page (nbytes), \
(vm_address_t) dstp) == KERN_SUCCESS \
? trunc_page (nbytes) \
: 0)))