memcpy()対memmove()


157

私は違いを理解しようとしている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

1
Microsoft CRTにはかなり前から安全なmemcpy()がありました。
Hans Passant

32
「安全」は正しい言葉だとは思いません。安全はmemcpy希望assertの領域が意図的にあなたのコードのバグをカバーするのではなく、重複しないように。
R .. GitHub ICE HELPING ICEの停止

6
「開発者にとって安全」または「エンドユーザーにとって安全」のどちらを意味するかによって異なります。規格に準拠していない場合でも、言われたとおりに行うほうが、エンドユーザーにとって安全な選択だと私は主張します。
kusma 2012年

glibc 2.19以降-機能しません The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen 2014

回答:


124

あなたの例が奇妙な振る舞いを示さないことに全く驚いていません。代わりににコピーstr1してみてstr1+2、その後どうなるか確認してください。(実際には違いはないかもしれませんが、コンパイラ/ライブラリによって異なります。)

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

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

編集:

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


2
次の実装でも+1、memmove通話memcpyのポインタをテストした後1つのブランチで:student.cs.uwaterloo.ca/~cs350/common/os161-src-html/...
パスカルCuoq

それはいいです。Visual Studioは「安全な」memcpyを実装しているようです(gcc 4.1.1とともに、RHEL 5でもテストしました)。これらの関数のバージョンをclc-wiki.netから作成すると、明確な画像が得られます。ありがとう。
user534785 2010

3
memcpyは重複問題を処理しませんが、memmoveは処理します。次に、libからmemcpyを削除しませんか?
Alcott、2011

37
@アルコット:memcpy高速化できるため。
Billy ONeal、2011年

上記のPascal Cuoqからの固定/ webarchiveリンク:web.archive.org/web/20130722203254/http
//…

94

のメモリ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は重複を許可する必要がありますが。


3
本当に助かりました!+1情報
Muthu Ganapathy Nathan

33

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


10
特に、プラットフォームによってmemcpyは、とまったく同じ方法で実装される可能性がありますmemmove。つまり、コンパイラーを作成した人は誰も、独自のmemcpy関数を作成する必要はありませんでした。
Cam

19

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

私見、str1バッファーが範囲外でアクセスされるため、このサンプルプログラムにはいくつかの欠陥があります(コピーするのに10バイト、バッファーのサイズは7バイトです)。範囲外エラーは、未定義の動作を引き起こします。memcpy()/ memmove()呼び出しの表示結果の違いは実装固有です。また、出力例は上記のプログラムと完全には一致しません...また、strcpy_s()は標準のC AFAIKの一部ではありません(MS固有、以下も参照:stackoverflow.com/questions/36723946/…)-修正する場合は修正してください間違っている。
REL

7

あなたのデモは「悪い」コンパイラーのために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  

これが、memmove2つの重複したメモリブロックをコピーするときに常に選択する理由です。


3

違いmemcpyとは、memmoveということです

  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

6
-1-memmoveが実際にデータを別のバッファーにコピーする必要はありません
jjwchoy 2013年

この例は、概念の理解に役立ちません...ほとんどのコンパイラはmem move出力と同じものを出力するため
Jasdeep Singh Arora '29

1
@jjwchoy概念的にはそうです。バッファは通常最適化されます
MM

Linuxでも同じ結果になります。
CodyChan 2017

2

すでに他の回答で指摘されているように、メモリの重複を考慮にmemmove入れるよりも高度ですmemcpy。memmoveの結果は、srcがバッファにコピーされ、次にバッファがにコピーされたかのように定義されdstます。これは、実際の実装がバッファを使用することを意味するのではなく、おそらくいくつかのポインタ計算を実行します。


1

コンパイラーはmemcpyを最適化できます。例えば:

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

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


3
このような最適化は、非境界整列intアクセスを許可するアーキテクチャーでのみ許可されます。一部のアーキテクチャ(Cortex-M0など)ではint、4の倍数ではないアドレスから32ビットをフェッチしようとすると、クラッシュが発生します(ただしmemcpy機能します)。アライメントされていないアクセスを許可するCPUを使用するか、必要に応じて個別にフェッチされたバイトから整数をアセンブルするようにコンパイラーに指示するキーワードを使用してコンパイラーを使用する場合、次のようなことができ#define UNALIGNED __unaligned、 `x = *(int UNALIGNED * )some_pointer;
スーパーキャット2013

2
一部のプロセッサは、非整列のintアクセスクラッシュを許可しませんがchar x = "12345"; int *i; i = *(int *)(x + 1);、フォールト中にコピーを修正するため、許可するプロセッサもあります。私はこのようなシステムで作業しましたが、なぜパフォーマンスがそれほど低下したのかを理解するのに少し時間がかかりました。
user3431262 14年

*(int *)some_pointerは厳密なエイリアス違反ですが、おそらくコンパイラがintをコピーするアセンブリを出力することを意味します
MM

1

リンク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

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 ただし、すべてが中間バッファーが使用されているかのように行われるため、オーバーラップは明らかに問題ありません。

std::copyただし、C ++ はより寛容であり、オーバーラップを許可します。std:: copyはオーバーラップする範囲を処理しますか?


memmovenの追加の一時配列を使用するので、追加のメモリを使用しますか?しかし、メモリへのアクセスを与えていない場合はどうすればよいでしょうか。(2倍のメモリを使用しています)。
clmno

@clmnoそれは私が期待する他の関数と同様にスタックまたはmallocに割り当てます:-)
Ciro Santilli郝海东冠状病六四事件法轮功

1
私はここで質問をしましたが、良い答えも得ました。ありがとうございました。バイラルになったhackernewsの投稿を見た(x86の1つ):)
clmno

-4

私は日食を使用して、同じプログラムを実行しようとしているとそれが間に明確な違いを示すmemcpyとしますmemmovememcpy()データの破損を引き起こすメモリロケーションのオーバーラップは気にしませんが、memmove()データを一時変数に最初にコピーしてから、実際のメモリロケーションにコピーします。

場所str1からstr1+2にデータをコピーしようとしたときに、の出力memcpyは " aaaaaa"です。問題はどのようになるでしょうか? memcpy()左から右に一度に1バイトをコピーします。プログラム " aabbcc"に示されているように、すべてのコピーは次のように行われます。

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() 最初にデータを一時変数にコピーしてから、実際のメモリ位置にコピーします。

  1. aabbcc(actual) -> aabbcc(temp)

  2. aabbcc(temp) -> aaabcc(act)

  3. aabbcc(temp) -> aaaacc(act)

  4. aabbcc(temp) -> aaaabc(act)

  5. aabbcc(temp) -> aaaabb(act)

出力は

memcpyaaaaaa

memmoveaaaabb


2
Stack Overflowへようこそ。すぐに概要ページをお読みください。対処すべきさまざまな問題があります。何よりもまず、18か月ほど前に複数の回答があった質問に回答を追加しました。追加を保証するには、驚くべき新しい情報を提供する必要があります。次に、Eclipseを指定しますが、EclipseはCコンパイラを使用するIDEですが、コードが実行されているプラ​​ットフォームやEclipseが使用しているCコンパイラを特定していません。memmove()コピーが中間の場所にどのように確認されるかを知りたいです。必要な場合は逆にコピーするだけです。
Jonathan Leffler、2015年

ありがとう。コンパイラについては、Linuxでgccコンパイラを使用しています。Linuxにはメモのmanページがあり、メモがデータの重複を避けるために一時変数にデータをコピーすることを明確に指定しています。これがそのマニュアルページのリンクですlinux.die.net/man/3/memmove
Pratik Panchal

3
実際には「まるで」のように書かれていますが、実際にそうなっているわけではありません。確かにその方法でそれを行うことができたとしても(スペアメモリをどこから取得するかについての質問はありますが)、それ実際にそれを行っているとしたら、私は少し驚いているでしょう。ソースアドレスがターゲットアドレスよりも大きい場合は、最初から最後までコピー(順方向コピー)で十分です。ソースアドレスがターゲットアドレスよりも小さい場合は、末尾から先頭にコピー(後方コピー)するだけで十分です。補助メモリは不要であり、使用されません。
Jonathan Leffler

コードで実際のデータを使用して答えを説明してください。
HaseeB Mir
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.