前段時間和朋友討論關於C基礎函數memcpy的實現細節時,收穫頗多。這個函數在C / C++編程領域中使用率是比較高的(可能排在前10左右)。但鮮有人去研究其實現原理。爲了弄清楚其實現,我給自己出了一道題目,就是用C實現一個memcpy的函數。先看標準memcpy函數的參數和返回值:
void* memcpy(void* dst, void* src, size_t size);
dst - 目的內存地址,src - 源內出地址,size- 需要複製的長度。返回值爲dst。
初想一下,這個函數很簡單,就隨手寫了一個實現。如下:
void* my_memcpy(void *dst, const void* src, size_t size)
{
if(dst == NULL || src == NULL || size <= 0)
return dst;
char* dst_pos = (char *)dst;
char* src_pos = (char *)src;
while(size > 0){
*dst_pos++ = *src_pos++;
size --;
}
return dst;
}
咋一看,這個函數是正確的。我使用了基本的測試代碼如下:
int main(int argc, char* argv[])
{
unsigned char buf[15] = {0};
unsigned char dst_buf[15] = {0};
for(unsigned char i = 3; i < 13; i ++)
buf[i] = i;
printf("src buf = ");
for(int i = 0; i < 15; i ++)
printf("%d ", buf[i]);
printf("\r\n");
//不同BUFF之間拷貝
my_memcpy(dst_buf, buf, 15);
printf("from buf copy to dst buf, dst_buf = ");
for(int i = 0; i < 15; i ++)
printf("%d ", dst_buf[i]);
//同一個BUFF向後拷貝
my_memcpy(buf + 4, buf + 3, 10);
printf("\nfrom buf + 3 copy to buf + 5, buf =");
for(int i = 0; i < 15; i ++){
printf("%d ", buf[i]);
buf[i] = 0;
}
//同一個BUFF向前拷貝
for(unsigned char i = 3; i < 13; i ++)
buf[i] = i;
my_memcpy(buf, buf + 3, 10);
printf("\nfrom buf + 5 coy to buf, buf = ");
for(int i = 0; i < 15; i ++)
printf("%d ", buf[i]);
return 0;
}
如果用上測試用例來測試,上面寫的my_memcpy是隻能過不同buf之間的拷貝和同一BUF向前拷貝,同一buf向後拷貝的情況重疊內存是被覆蓋了的。於是我進行了修改:
void* my_memcpy(void *dst, const void* src, size_t size)
{
if(dst == NULL || src == NULL || size <= 0)
return dst;
char* dst_pos = (char *)dst + size;
char* src_pos = (char *)src + size;
while(size > 0){
*dst_pos-- = *src_pos--;
size --;
}
return dst;
}
從上可以看出,我做了向前拷貝兼容,防止向後拷貝重疊地址被覆蓋的情況。但第三種情況向前拷貝的重疊內存還是被覆蓋了。這就要思考根據參數判斷是從前拷貝還是從後拷貝。於是修改爲:
void* my_memcpy(void *dst, const void* src, size_t size)
{
if(dst == NULL || src == NULL || size <= 0)
return dst;
char* dst_pos = (char *)dst;
char* src_pos = (char *)src;
if(dst_pos < src_pos + size && dst > src){ //DOWN COPY,向前拷貝
dst_pos = dst_pos + size;
src_pos = src_pos + size;
while(size > 0){
*dst_pos-- = *src_pos--;
size --;
}
}
else { //UP COPY,向後拷貝
while(size > 0){
*dst_pos++ = *src_pos++;
size --;
}
}
return dst;
}
這個實現是兼容向前拷貝和向後拷貝、不同BUF拷貝的情況,也近似C函數庫的實現。後來我跟蹤了下C函數庫的實現,它是用ASM進行實現的。我貼出VC++中的實現:
ifdef MEM_MOVE
_MEM_ equ <memmove>
else ; MEM_MOVE
_MEM_ equ <memcpy>
endif ; MEM_MOVE
% public _MEM_
_MEM_ proc \
dst:ptr byte, \
src:ptr byte, \
count:IWORD
; destination pointer
; source pointer
; number of bytes to copy
; push ebp ;U - save old frame pointer
; mov ebp, esp ;V - set new frame pointer
push edi ;U - save edi
push esi ;V - save esi
mov esi,[src] ;U - esi = source
mov ecx,[count] ;V - ecx = number of bytes to move
mov edi,[dst] ;U - edi = dest
;
; Check for overlapping buffers:
; If (dst <= src) Or (dst >= src + Count) Then
; Do normal (Upwards) Copy
; Else
; Do Downwards Copy to avoid propagation
;
..........
後面的ASM代碼太長,我沒有全部給出。它也是做了情況判斷,只是整個實現考慮的情況很多,細節考慮的很多。所以,通過對memcpy函數的分析,我們可以很好的瞭解內存操作需要注意的地方,尤其是同一buf向前拷貝和向後拷貝的問題,我們不僅僅要會使用API,更需要弄清楚API背後的邏輯。