詳解memcpy的實(shí)現(xiàn)原理,可不只是逐個(gè)字節(jié)復(fù)制那么簡單
掃描二維碼
隨時(shí)隨地手機(jī)看文章
memcpy在C/C++開發(fā)中很常用,面試時(shí)候也會(huì)經(jīng)常被問到memcpy和memmove的區(qū)別,本文先介紹下memcpy的原理。
可能各個(gè)平臺(tái)的實(shí)現(xiàn)原理不同,但都大同小異,本文主要介紹glibc中的memcpy。
基本定義
/* 函數(shù)原型 */ void *memcpy(void *dest, const void *src, size_t n);
memcpy的功能是將src指向的內(nèi)存區(qū)域的前n個(gè)字節(jié)復(fù)制到dest指向的內(nèi)存區(qū)域。
使用memcpy,需要注意以下幾點(diǎn):
- 不處理重疊區(qū)域(這是memmove的職責(zé))
- 返回目標(biāo)地址dest
- 要求調(diào)用者確保:
- dest和src都是有效指針
- 內(nèi)存區(qū)域不重疊
- 有足夠的空間
實(shí)現(xiàn)策略
glibc的memcpy實(shí)現(xiàn)采用了三層復(fù)制策略:
- 字節(jié)復(fù)制(Byte Copy)
- 字復(fù)制(Word Copy)
- 頁復(fù)制(Page Copy)
面試時(shí)候回答出三層復(fù)制策略這個(gè)關(guān)鍵點(diǎn)就很加分了。
核心實(shí)現(xiàn)
整體策略大體如下
- 數(shù)據(jù)量小于16字節(jié):
- 直接使用字節(jié)復(fù)制
- 避免對齊開銷
- 數(shù)據(jù)量在16字節(jié)到16KB之間(不一定是16KB,為了方便起見,統(tǒng)一使用16,你理解原理即可):
- 先對齊
- 使用字復(fù)制
- 處理剩余字節(jié)
- 數(shù)據(jù)量大于16KB:
- 先對齊
- 嘗試頁復(fù)制
- 字復(fù)制處理剩余數(shù)據(jù)
- 字節(jié)復(fù)制處理尾部
代碼詳見:
void * MEMCPY (void *dstpp, const void *srcpp, size_t len) { unsigned long int dstp = (long int) dstpp; unsigned long int srcp = (long int) srcpp; /* 保存原始目標(biāo)地址用于返回 */ void *orig_dstpp = dstpp; /* 對于足夠大的數(shù)據(jù)塊,使用優(yōu)化策略 */ if (len >= OP_T_THRES) { /* 1. 首先對齊目標(biāo)地址 */ len -= (-dstp) % OPSIZ; BYTE_COPY_FWD (dstp, srcp, (-dstp) % OPSIZ); /* 2. 嘗試頁復(fù)制 */ PAGE_COPY_FWD_MAYBE (dstp, srcp, len, len); /* 3. 使用字復(fù)制處理剩余數(shù)據(jù) */ WORD_COPY_FWD (dstp, srcp, len, len); } /* 4. 處理剩余字節(jié) */ BYTE_COPY_FWD (dstp, srcp, len); return orig_dstpp; }
詳細(xì)分析
字節(jié)復(fù)制
最基礎(chǔ)的復(fù)制方式,一次復(fù)制一個(gè)字節(jié):
#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)
使用場景:
- 小數(shù)據(jù)量復(fù)制
- 處理非對齊數(shù)據(jù)
- 處理剩余字節(jié)
字復(fù)制
按機(jī)器字長進(jìn)行復(fù)制,提高效率:
#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)
優(yōu)化特點(diǎn):
- 利用CPU字長進(jìn)行批量復(fù)制
- 要求內(nèi)存對齊
- 比字節(jié)復(fù)制更高效
頁復(fù)制
最高效的復(fù)制方式,適用于大塊數(shù)據(jù):
#define PAGE_COPY_FWD_MAYBE(dstp, srcp, nbytes_left, nbytes) \ do { \ if ((nbytes) >= PAGE_COPY_THRESHOLD \ && PAGE_OFFSET ((dstp) - (srcp)) == 0) \ { \ /* 處理第一個(gè)不完整頁 */ \ size_t nbytes_before = PAGE_OFFSET (-(dstp)); \ if (nbytes_before != 0) \ { \ WORD_COPY_FWD (dstp, srcp, nbytes_left, nbytes_before); \ nbytes -= nbytes_before; \ } \ /* 執(zhí)行頁復(fù)制 */ \ 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)))





