關於C函數memcpy的實現細節思考

前段時間和朋友討論關於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背後的邏輯












發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章