memcpy()
と memmove()
の違いを理解しようとしていますが、memcpy()
はmemmove()
が重複するソースと宛先を処理しないというテキストを読みました。
ただし、重複するメモリブロックでこれら2つの関数を実行すると、どちらも同じ結果になります。たとえば、memmove()
ヘルプページで次のMSDNの例をご覧ください。
memcpy
の欠点とmemmove
がそれをどのように解決するかを理解するためのより良い例はありますか?
// crt_memcpy.c
// Illustrate overlapping copy: memmove always handles it correctly; memcpy may handle
// it correctly.
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[7] = "aabbcc";
int main( void )
{
printf( "The string: %s\n", str1 );
memcpy( str1 + 2, str1, 4 );
printf( "New string: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "aabbcc" ); // reset string
printf( "The string: %s\n", str1 );
memmove( str1 + 2, str1, 4 );
printf( "New string: %s\n", str1 );
}
The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb
あなたの例が奇妙な振る舞いを示さないことは全く驚きではありません。代わりにstr1
をstr1+2
にコピーしてみて、その結果を確認してください。 (実際には違いはありません。コンパイラ/ライブラリに依存します。)
一般に、memcpyは単純な(ただし高速な)方法で実装されます。単純に、データを順番にループし、ある場所から別の場所にコピーします。これにより、読み取り中にソースが上書きされる可能性があります。
Memmoveは、オーバーラップを適切に処理するためにさらに作業を行います。
編集:
(残念ながら、私はまともな例を見つけることができませんが、これらはあります)。ここに示されている memcpy および memmove の実装とは対照的です。 memcpyはループするだけですが、memmoveはテストを実行して、データの破損を防ぐためにループする方向を決定します。これらの実装はかなり単純です。ほとんどの高性能実装はより複雑です(バイト単位ではなく、一度にWordサイズのブロックをコピーする必要があります)。
memcpy
cannotのメモリはオーバーラップするか、未定義の動作のリスクがありますが、memmove
のメモリはオーバーラップできます。
char a[16];
char b[16];
memcpy(a,b,16); // valid
memmove(a,b,16); // Also valid, but slower than memcpy.
memcpy(&a[0], &a[1],10); // Not valid since it overlaps.
memmove(&a[0], &a[1],10); // valid.
Memcpyの一部の実装は、重複する入力に対して引き続き機能する可能性がありますが、その動作をカウントすることはできません。 memmoveはオーバーラップを許可する必要があります。
memcpy
が重複する領域を処理する必要がないからといって、それらが正しく処理されないわけではありません。重複する領域を持つ呼び出しは、未定義の動作を生成します。未定義の動作は、1つのプラットフォームで期待どおりに完全に機能します。それはそれが正しいまたは有効であることを意味しません。
Memcpyとmemoveはどちらも同様のことを行います。
しかし、1つの違いを確認するには:
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[17] = "abcdef";
int main()
{
printf( "The string: %s\n", str1 );
memcpy( (str1+6), str1, 10 );
printf( "New string: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "aabbcc" ); // reset string
printf( "The string: %s\n", str1 );
memmove( (str1+6), str1, 10 );
printf( "New string: %s\n", str1 );
}
与える:
The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef
あなたのデモは、コンパイラが「悪い」ためにmemcpyの欠点を明らかにしませんでした。それは、デバッグ版で有利です。ただし、リリースバージョンでは、最適化のために同じ出力が得られます。
memcpy(str1 + 2, str1, 4);
00241013 mov eax,dword ptr [str1 (243018h)] // load 4 bytes from source string
printf("New string: %s\n", str1);
00241018 Push offset str1 (243018h)
0024101D Push offset string "New string: %s\n" (242104h)
00241022 mov dword ptr [str1+2 (24301Ah)],eax // put 4 bytes to destination
00241027 call esi
ここのレジスタ%eax
は、一時的なストレージとして再生され、「エレガントに」重複の問題を修正します。
6バイトをコピーするとき、少なくともその一部がコピーされると、欠点が生じます。
char str1[9] = "aabbccdd";
int main( void )
{
printf("The string: %s\n", str1);
memcpy(str1 + 2, str1, 6);
printf("New string: %s\n", str1);
strcpy_s(str1, sizeof(str1), "aabbccdd"); // reset string
printf("The string: %s\n", str1);
memmove(str1 + 2, str1, 6);
printf("New string: %s\n", str1);
}
出力:
The string: aabbccdd
New string: aaaabbbb
The string: aabbccdd
New string: aaaabbcc
奇妙に見えますが、それも最適化が原因です。
memcpy(str1 + 2, str1, 6);
00341013 mov eax,dword ptr [str1 (343018h)]
00341018 mov dword ptr [str1+2 (34301Ah)],eax // put 4 bytes to destination, earlier than the above example
0034101D mov cx,Word ptr [str1+4 (34301Ch)] // HA, new register! Holding a Word, which is exactly the left 2 bytes (after 4 bytes loaded to %eax)
printf("New string: %s\n", str1);
00341024 Push offset str1 (343018h)
00341029 Push offset string "New string: %s\n" (342104h)
0034102E mov Word ptr [str1+6 (34301Eh)],cx // Again, pulling the stored Word back from the new register
00341035 call esi
これが、重複する2つのメモリブロックをコピーするときに常にmemmove
を選択する理由です。
memcpy
とmemmove
の違いは、
memmove
では、指定されたサイズのソースメモリがバッファにコピーされ、宛先に移動されます。したがって、メモリが重複している場合、副作用はありません。
memcpy()
の場合、ソースメモリ用に余分なバッファは取得されません。コピーはメモリ上で直接行われるため、メモリのオーバーラップがある場合、予期しない結果が生じます。
これらは、次のコードで確認できます。
//include string.h, stdio.h, stdlib.h
int main(){
char a[]="hare rama hare rama";
char b[]="hare rama hare rama";
memmove(a+5,a,20);
puts(a);
memcpy(b+5,b,20);
puts(b);
}
出力は次のとおりです。
hare hare rama hare rama
hare hare hare hare hare hare rama hare rama
他の回答ですでに指摘したように、memmove
はmemcpy
よりも洗練されているため、メモリのオーバーラップを考慮します。 memmoveの結果は、src
がバッファーにコピーされ、次にバッファーがdst
にコピーされたかのように定義されます。これは、実際の実装がバッファを使用することを意味するものではありませんが、おそらくいくつかのポインタ演算を行います。
リンクで指定されたコード http://clc-wiki.net/wiki/memcpy memcpyを使用すると、以下を使用して実装したときに同じ出力が得られないため、少し混乱するようです例。
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[11] = "abcdefghij";
void *memcpyCustom(void *dest, const void *src, size_t n)
{
char *dp = (char *)dest;
const char *sp = (char *)src;
while (n--)
*dp++ = *sp++;
return dest;
}
void *memmoveCustom(void *dest, const void *src, size_t n)
{
unsigned char *pd = (unsigned char *)dest;
const unsigned char *ps = (unsigned char *)src;
if ( ps < pd )
for (pd += n, ps += n; n--;)
*--pd = *--ps;
else
while(n--)
*pd++ = *ps++;
return dest;
}
int main( void )
{
printf( "The string: %s\n", str1 );
memcpy( str1 + 1, str1, 9 );
printf( "Actual memcpy output: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "abcdefghij" ); // reset string
memcpyCustom( str1 + 1, str1, 9 );
printf( "Implemented memcpy output: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "abcdefghij" ); // reset string
memmoveCustom( str1 + 1, str1, 9 );
printf( "Implemented memmove output: %s\n", str1 );
getchar();
}
出力:
The string: abcdefghij
Actual memcpy output: aabcdefghi
Implemented memcpy output: aaaaaaaaaa
Implemented memmove output: aabcdefghi
しかし、なぜ memmove が重複する問題を処理するのかを理解できます。
コンパイラはmemcpyを最適化できます。たとえば、次のとおりです。
int x;
memcpy(&x, some_pointer, sizeof(int));
このmemcpyは次のように最適化できます:x = *(int*)some_pointer;
C11標準ドラフト
C11 N1570標準ドラフト の意味:
7.24.2.1「memcpy関数」:
2 memcpy関数は、s2が指すオブジェクトからn文字をs1が指すオブジェクトにコピーします。重複するオブジェクト間でコピーが行われる場合、動作は未定義です。
7.24.2.2「memmove機能」:
2 memmove関数は、s2が指すオブジェクトからn文字をs1が指すオブジェクトにコピーします。コピーは、s2が指すオブジェクトのn個の文字が、s1とs2が指すオブジェクトと重複しないn個の文字の一時配列に最初にコピーされ、次に一時配列のn個の文字がs1が指すオブジェクト
したがって、memcpy
でのオーバーラップは未定義の動作につながり、悪いこと、何もない、または良いことさえ起こります。良いことはまれですが:-)
ただし、memmove
は、すべてが中間バッファが使用されているかのように発生することを明確に示しているため、重複は明らかに問題ありません。
ただし、C++のstd::copy
はより寛容であり、重複を許可します。 std :: copyは範囲の重複を処理しますか?