C関数memcpyの実現の詳細について考える


この間,C基礎関数memcpyの実装の詳細について友人と議論したところ,多くの収穫があった.この関数はC/C++プログラミングの分野で使用率が比較的高い(おそらく上位10位以内).しかし、その実現原理を研究する人は少ない.その実現を明らかにするために、私は自分に1つのテーマを出して、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
"); // 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("
from 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("
from 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;
}

以上から,前方コピー互換性を行い,後方コピー重複アドレスが上書きされることを防止した.しかし、3つ目のケースでは、前方にコピーされた重複メモリが上書きされています.これは,パラメータに基づいて前コピーか後コピーかを判断することである.次のように変更します.
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関数ライブラリのインプリメンテーションにも近似している.その後、ASMで実装されたC関数ライブラリの実装を追跡しました.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の背後にある論理を明らかにする必要があります.