180

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
4

11 に答える 11

141

あなたの例が奇妙な動作を示さないことにまったく驚かない. 代わりにコピーstr1してみて、何が起こるか見てみましょう。str1+2(コンパイラ/ライブラリによっては、実際には違いがない場合があります。)

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

Memmove は、オーバーラップを正しく処理するために、より多くの作業を行います。

編集:

(残念ながら、適切な例を見つけることができませんが、これで十分です)。ここに示すmemcpymemmoveの実装を比較してください。memcpy はループするだけですが、memmove はテストを実行して、データの破損を避けるためにループする方向を決定します。これらの実装はかなり単純です。ほとんどの高パフォーマンスの実装はより複雑です (バイト単位ではなくワード サイズのブロックを一度にコピーする必要があります)。

于 2010-12-11T08:39:38.713 に答える
112

のメモリはオーバーラップmemcpy できません。そうしないと、未定義の動作が発生するリスクがありますが、メモリ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 はオーバーラップを許可する必要があります。

于 2011-07-25T22:41:43.767 に答える
36

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

于 2010-12-11T08:38:32.653 に答える
20

memcpy と memove はどちらも同様のことを行います。

しかし、1 つの違いを確認するには:

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[7] = "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("\nstr1: %s\n", str1);
   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
于 2010-12-11T09:18:03.920 に答える
9

あなたのデモは、「悪い」コンパイラのためにmemcpyの欠点を明らかにしませんでした.Debugバージョンであなたに有利に働きます. ただし、リリース バージョンでも同じ出力が得られますが、これは最適化のためです。

    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  

memmoveこれが、 2 つのオーバーラップしたメモリ ブロックをコピーしようとするときに常に選択する理由です。

于 2013-11-07T03:13:39.687 に答える
2

他の回答ですでに指摘されているように、メモリの重複を説明するmemmoveよりも洗練されています。memcpymemmove の結果は、srcがバッファにコピーされ、次にバッファが にコピーされたかのように定義されdstます。これは、実際の実装がバッファを使用することを意味するわけではありませんが、おそらくいくつかのポインタ演算を行います。

于 2016-04-19T23:41:21.817 に答える
1

リンクhttp://clc-wiki.net/wiki/memcpy for 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が処理する理由が理解できるようになりました。

于 2014-03-30T15:55:41.700 に答える