C関数memcpyの実現の詳細について考える
4037 ワード
この間,C基礎関数memcpyの実装の詳細について友人と議論したところ,多くの収穫があった.この関数はC/C++プログラミングの分野で使用率が比較的高い(おそらく上位10位以内).しかし、その実現原理を研究する人は少ない.その実現を明らかにするために、私は自分に1つのテーマを出して、Cでmemcpyの関数を実現します.まず、標準memcpy関数のパラメータと戻り値を見てみましょう.
dst-宛先メモリアドレス、src-ソース内出アドレス、size-コピーが必要な長さ.戻り値はdstです.
最初に考えてみると、この関数は簡単で、手当たり次第に実現を書きました.次のようになります.
テスト例を使ってテストすると、my_と書いてあります.memcpyは、異なるbuf間のコピーと同じBUFを越えて前方にコピーするしかなく、同じbufを後方にコピーする場合は重複メモリが上書きされます.そこで私は修正しました.
以上から,前方コピー互換性を行い,後方コピー重複アドレスが上書きされることを防止した.しかし、3つ目のケースでは、前方にコピーされた重複メモリが上書きされています.これは,パラメータに基づいて前コピーか後コピーかを判断することである.次のように変更します.
memcpy関数の解析により、メモリ操作に注意すべき点、特に同じbufの前方コピーと後方コピーの問題をよく理解することができ、APIだけでなく、APIの背後にある論理を明らかにする必要があります.
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の背後にある論理を明らかにする必要があります.