web-dev-qa-db-ja.com

memcpy()vs memmove()

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
146
user534785

あなたの例が奇妙な振る舞いを示さないことは全く驚きではありません。代わりにstr1str1+2にコピーしてみて、その結果を確認してください。 (実際には違いはありません。コンパイラ/ライブラリに依存します。)

一般に、memcpyは単純な(ただし高速な)方法で実装されます。単純に、データを順番にループし、ある場所から別の場所にコピーします。これにより、読み取り中にソースが上書きされる可能性があります。

Memmoveは、オーバーラップを適切に処理するためにさらに作業を行います。

編集:

(残念ながら、私はまともな例を見つけることができませんが、これらはあります)。ここに示されている memcpy および memmove の実装とは対照的です。 memcpyはループするだけですが、memmoveはテストを実行して、データの破損を防ぐためにループする方向を決定します。これらの実装はかなり単純です。ほとんどの高性能実装はより複雑です(バイト単位ではなく、一度にWordサイズのブロックをコピーする必要があります)。

113

memcpycannotのメモリはオーバーラップするか、未定義の動作のリスクがありますが、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はオーバーラップを許可する必要があります。

86
rxantos

memcpyが重複する領域を処理する必要がないからといって、それらが正しく処理されないわけではありません。重複する領域を持つ呼び出しは、未定義の動作を生成します。未定義の動作は、1つのプラットフォームで期待どおりに完全に機能します。それはそれが正しいまたは有効であることを意味しません。

32
Billy ONeal

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
16
Neilvert Noval

あなたのデモは、コンパイラが「悪い」ために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を選択する理由です。

7
huubby

memcpymemmoveの違いは、

  1. memmoveでは、指定されたサイズのソースメモリがバッファにコピーされ、宛先に移動されます。したがって、メモリが重複している場合、副作用はありません。

  2. 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
3

他の回答ですでに指摘したように、memmovememcpyよりも洗練されているため、メモリのオーバーラップを考慮します。 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 が重複する問題を処理するのかを理解できます。

1
singingsingh

コンパイラはmemcpyを最適化できます。たとえば、次のとおりです。

int x;
memcpy(&x, some_pointer, sizeof(int));

このmemcpyは次のように最適化できます:x = *(int*)some_pointer;

1
rockeet

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は範囲の重複を処理しますか?