memcpy () vs memmove ()


157

Ich versuche, den Unterschied zwischen memcpy()und zu verstehen memmove(), und ich habe den Text gelesen, der memcpy()sich nicht um die überlappende Quelle und das überlappende Ziel memmove()kümmert.

Wenn ich diese beiden Funktionen jedoch auf überlappenden Speicherblöcken ausführe, ergeben beide das gleiche Ergebnis. Nehmen Sie zum Beispiel das folgende MSDN-Beispiel auf der memmove()Hilfeseite: -

Gibt es ein besseres Beispiel, um die Nachteile zu verstehen memcpyund wie es memmovegelöst werden kann?

// 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 );
}

Ausgabe:

The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb

1
Das Microsoft CRT hat seit einiger Zeit ein sicheres memcpy ().
Hans Passant

32
Ich denke nicht, dass "sicher" das richtige Wort dafür ist. Ein Safe memcpywäre, assertdass sich die Regionen nicht überlappen, anstatt absichtlich Fehler in Ihrem Code zu vertuschen.
R .. GitHub STOP HELPING ICE

6
Hängt davon ab, ob Sie "sicher für den Entwickler" oder "sicher für den Endbenutzer" meinen. Ich würde argumentieren, dass das Handeln wie gesagt, auch wenn es nicht standardkonform ist, die sicherere Wahl für den Endbenutzer ist.
Kusma

seit glibc 2.19 - nicht funktionieren The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen

Sie können auch hier sehen .
Ren

Antworten:


124

Ich bin nicht ganz überrascht, dass Ihr Beispiel kein seltsames Verhalten zeigt. Versuchen Sie das Kopieren str1zu str1+2statt und sehen , was dann passiert. (Kann keinen Unterschied machen, hängt vom Compiler / den Bibliotheken ab.)

Im Allgemeinen wird memcpy auf einfache (aber schnelle) Weise implementiert. Vereinfacht gesagt werden nur die Daten (in der angegebenen Reihenfolge) durchlaufen und von einem Ort zum anderen kopiert. Dies kann dazu führen, dass die Quelle beim Lesen überschrieben wird.

Memmove leistet mehr Arbeit, um sicherzustellen, dass die Überlappung korrekt behandelt wird.

BEARBEITEN:

(Leider kann ich keine anständigen Beispiele finden, aber diese reichen aus). Vergleichen Sie die memcpy und memmove Implementierungen hier gezeigt. memcpy wird nur wiederholt, während memmove einen Test durchführt, um festzustellen, in welche Richtung eine Schleife ausgeführt werden soll, um eine Beschädigung der Daten zu vermeiden. Diese Implementierungen sind ziemlich einfach. Die meisten Hochleistungsimplementierungen sind komplizierter (das gleichzeitige Kopieren von Blöcken in Wortgröße anstelle von Bytes).


2
1 Auch in der folgenden Implementierung, memmoveruft memcpyin einem Zweig nach dem Zeiger Prüfung: student.cs.uwaterloo.ca/~cs350/common/os161-src-html/...
Pascal Cuoq

Das klingt gut. Offenbar implementiert Visual Studio ein "sicheres" Memcpy (zusammen mit gcc 4.1.1 habe ich es auch auf RHEL 5 getestet). Das Schreiben der Versionen dieser Funktionen von clc-wiki.net ergibt ein klares Bild. Vielen Dank.
user534785

3
memcpy kümmert sich nicht um das Überlappungsproblem, memmove jedoch. Warum dann nicht memcpy aus der Bibliothek entfernen?
Alcott

37
@Alcott: Weil memcpyschneller sein kann.
Billy ONeal


94

Der Speicher in memcpy kann sich nicht überlappen, oder Sie riskieren undefiniertes Verhalten, während sich der Speicher in memmoveüberlappen kann.

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. 

Einige Implementierungen von memcpy funktionieren möglicherweise immer noch für überlappende Eingaben, aber Sie können dieses Verhalten nicht zählen. Während memmove Überlappungen zulassen muss.


3
es hat mir wirklich geholfen, danke! +1 für Ihre Informationen
Muthu Ganapathy Nathan

33

Nur weil memcpyes sich nicht um überlappende Regionen handeln muss, heißt das nicht, dass es nicht richtig mit ihnen umgeht. Der Aufruf mit überlappenden Regionen erzeugt undefiniertes Verhalten. Undefiniertes Verhalten kann auf einer Plattform wie erwartet funktionieren. das heißt nicht, dass es richtig oder gültig ist.


10
Insbesondere ist es je nach Plattform möglich, dass memcpygenau so implementiert wird wie memmove. Das heißt, wer auch immer den Compiler geschrieben hat, hat sich nicht die Mühe gemacht, eine eindeutige memcpyFunktion zu schreiben .
Cam

19

Sowohl memcpy als auch memove machen ähnliche Dinge.

Aber um einen Unterschied zu erkennen:

#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 );

}

gibt:

The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef

Meiner Meinung nach weist dieses Beispielprogramm einige Mängel auf, da auf den str1-Puffer außerhalb der Grenzen zugegriffen wird (10 Bytes zum Kopieren, Puffer 7 Bytes groß). Der Out-of-Bound-Fehler führt zu undefiniertem Verhalten. Die Unterschiede in den angezeigten Ergebnissen der Aufrufe memcpy () / memmove () sind implementierungsspezifisch. Und die Beispielausgabe stimmt nicht genau mit dem obigen Programm überein ... Außerdem ist strcpy_s () nicht Teil der Standard-C-AFAIK (MS-spezifisch, siehe auch: stackoverflow.com/questions/36723946/… ) - Bitte korrigieren Sie mich, wenn ich 'Ich liege falsch.
Rel

7

Ihre Demo hat keine memcpy-Nachteile aufgrund des "schlechten" Compilers aufgedeckt, es tut Ihnen in der Debug-Version einen Gefallen. Eine Release-Version liefert jedoch die gleiche Ausgabe, jedoch aufgrund von Optimierungen.

    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  

Das Register wird %eaxhier als temporärer Speicher abgespielt, wodurch Überlappungsprobleme "elegant" behoben werden.

Der Nachteil tritt auf, wenn 6 Bytes kopiert werden, zumindest ein Teil davon.

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);
}

Ausgabe:

The string: aabbccdd
New string: aaaabbbb
The string: aabbccdd
New string: aaaabbcc

Sieht komisch aus, es wird auch durch Optimierung verursacht.

    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  

Deshalb wähle ich immer, memmovewenn ich versuche, 2 überlappende Speicherblöcke zu kopieren.


3

Der Unterschied zwischen memcpyund memmoveist das

  1. In memmovewird der Quellspeicher der angegebenen Größe in den Puffer kopiert und dann zum Ziel verschoben. Wenn sich der Speicher also überlappt, treten keine Nebenwirkungen auf.

  2. Im Falle von memcpy()wird kein zusätzlicher Puffer für den Quellspeicher benötigt. Das Kopieren erfolgt direkt im Speicher, sodass bei Überlappung des Speichers unerwartete Ergebnisse erzielt werden.

Diese können durch den folgenden Code beobachtet werden:

//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);
}

Ausgabe ist:

hare hare rama hare rama
hare hare hare hare hare hare rama hare rama

6
-1 - Es ist nicht erforderlich, dass memmove Daten tatsächlich in einen separaten Puffer
kopiert

Dieses Beispiel hilft nicht beim Verständnis des Konzepts ... da die meisten Compiler dasselbe ausgeben wie die Ausgabe von mem move
Jasdeep Singh Arora

1
@jjwchoy Konzeptionell tut es. Der Puffer wird normalerweise optimiert
MM

Das gleiche Ergebnis unter Linux.
CodyChan

2

Wie bereits in anderen Antworten erwähnt, memmoveist es komplexer als memcpysolche, dass es Speicherüberschneidungen berücksichtigt. Das Ergebnis von memmove ist so definiert, als ob das srcin einen Puffer und dann in den Puffer kopiert wurde dst. Dies bedeutet NICHT, dass die eigentliche Implementierung einen Puffer verwendet, sondern wahrscheinlich eine Zeigerarithmetik.


1

Der Compiler könnte memcpy optimieren, zum Beispiel:

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

Dieser Speicher kann wie folgt optimiert werden: x = *(int*)some_pointer;


3
Eine solche Optimierung ist nur für Architekturen zulässig, die nicht ausgerichtete intZugriffe ermöglichen. Bei einigen Architekturen (z. B. Cortex-M0) führt der Versuch, ein 32-Bit intvon einer Adresse abzurufen, die kein Vielfaches von vier ist, zu einem Absturz ( memcpywürde aber funktionieren). Wenn man entweder eine CPU verwendet, die einen nicht ausgerichteten Zugriff ermöglicht, oder einen Compiler mit einem Schlüsselwort verwendet, das den Compiler #define UNALIGNED __unalignedanweist, bei Bedarf Ganzzahlen aus separat abgerufenen Bytes zusammenzusetzen, könnte man so etwas tun und dann `x = * (int UNALIGNED * ) some_pointer;
Supercat

2
Einige Prozessoren erlauben keinen nicht ausgerichteten Absturz des int-Zugriffs, char x = "12345"; int *i; i = *(int *)(x + 1);andere jedoch, weil sie die Kopie während des Fehlers reparieren. Ich habe an einem solchen System gearbeitet, und es hat einige Zeit gedauert, um zu verstehen, warum die Leistung so schlecht war.
user3431262

*(int *)some_pointerist eine strikte Aliasing-Verletzung, aber Sie meinen wahrscheinlich, dass der Compiler eine Assembly ausgeben würde, die eine int
MM

1

Der Code in den Links http://clc-wiki.net/wiki/memcpy für memcpy scheint mich ein wenig zu verwirren, da er nicht die gleiche Ausgabe liefert, wenn ich ihn anhand des folgenden Beispiels implementiert habe.

#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();
}

Ausgabe :

The string: abcdefghij
Actual memcpy output: aabcdefghi
Implemented memcpy output: aaaaaaaaaa
Implemented memmove output: aabcdefghi

Aber Sie können jetzt verstehen, warum memmove sich um überlappende Probleme kümmert.


1

C11 Standardentwurf

Der Standardentwurf C11 N1570 lautet:

7.24.2.1 "Die memcpy-Funktion":

2 Die memcpy-Funktion kopiert n Zeichen von dem Objekt, auf das s2 zeigt, in das Objekt, auf das s1 zeigt. Wenn zwischen überlappenden Objekten kopiert wird, ist das Verhalten undefiniert.

7.24.2.2 "Die memmove-Funktion":

2 Die Funktion memmove kopiert n Zeichen von dem Objekt, auf das s2 zeigt, in das Objekt, auf das s1 zeigt. Das Kopieren erfolgt so, als würden die n Zeichen des Objekts, auf das s2 zeigt, zuerst in ein temporäres Array von n Zeichen kopiert, das die Objekte, auf die s1 und s2 zeigen, nicht überlappt. Anschließend werden die n Zeichen des temporären Arrays kopiert das Objekt, auf das s1 zeigt

Daher führt jede Überlappung memcpyzu undefiniertem Verhalten, und alles kann passieren: schlecht, nichts oder sogar gut. Gut ist allerdings selten :-)

memmove Es wird jedoch klar gesagt, dass alles so geschieht, als ob ein Zwischenpuffer verwendet wird, sodass Überlappungen eindeutig in Ordnung sind.

C ++ std::copyist jedoch verzeihender und erlaubt Überlappungen: Behandelt std :: copy überlappende Bereiche?


memmoveVerwenden Sie ein zusätzliches temporäres Array von n. Verwendet es also zusätzlichen Speicher? Aber wie kann es sein, wenn wir ihm keinen Zugriff auf einen Speicher gewährt haben? (Es verwendet 2x den Speicher).
Clmno

@clmno es wird wie jede andere Funktion, die ich erwarten würde, auf Stack oder Malloc zugewiesen :-)
Ciro Santilli 27 冠状 病 六四 事件 27

1
Ich hatte hier eine Frage gestellt und auch eine gute Antwort bekommen. Danke dir. Hab deinen Hackernews- Beitrag gesehen , der viral wurde (der x86-
Beitrag

-4

Ich habe versucht, dasselbe Programm mit Eclipse auszuführen, und es zeigt einen deutlichen Unterschied zwischen memcpyund memmove. memcpy()Es ist nicht wichtig, dass sich der Speicherort überlappt, was zu einer Beschädigung der Daten führt, während zuerst memmove()Daten in eine temporäre Variable und dann in den tatsächlichen Speicherort kopiert werden.

Beim Versuch, Daten vom Speicherort str1nach zu kopieren str1+2, ist die Ausgabe von memcpy" aaaaaa". Die Frage wäre wie? memcpy()kopiert jeweils ein Byte von links nach rechts. Wie in Ihrem Programm " aabbcc" gezeigt, erfolgt der gesamte Kopiervorgang wie folgt:

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() kopiert zuerst Daten in eine temporäre Variable und dann in den tatsächlichen Speicherort.

  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)

Ausgabe ist

memcpy :: aaaaaa

memmove :: aaaabb


2
Willkommen bei Stack Overflow. Bitte lesen Sie bald die Info- Seite. Es gibt verschiedene Probleme zu lösen. In erster Linie haben Sie eine Antwort mit mehreren Antworten von vor ungefähr 18 Monaten hinzugefügt. Um die Hinzufügung zu rechtfertigen, müssten Sie verblüffende neue Informationen bereitstellen. Zweitens geben Sie Eclipse an, aber Eclipse ist eine IDE, die einen C-Compiler verwendet, aber Sie identifizieren nicht die Plattform, auf der Ihr Code ausgeführt wird oder die der C-Compiler Eclipse verwendet. Mich würde interessieren, wie Sie feststellen, dass memmove()Kopien an einen Zwischenort kopiert werden. Es sollte bei Bedarf nur in umgekehrter Reihenfolge kopiert werden.
Jonathan Leffler

Vielen Dank. Über den Compiler, also benutze ich gcc Compiler unter Linux. Unter Linux gibt es eine Manpage für den Memove, die eindeutig angibt, dass der Memove Daten in temporäre Variablen kopiert, um Datenüberschneidungen zu vermeiden. Hier ist der Link dieser Manpage linux.die.net/man/3/memmove
Pratik Panchal

3
Es heißt tatsächlich "als ob", was nicht bedeutet, dass es das ist, was tatsächlich passiert. Zugegeben, es könnte tatsächlich so gemacht werden (obwohl es Fragen darüber gibt, woher es den freien Speicher bezieht), aber ich wäre mehr als ein wenig überrascht, wenn es das wäre, was es tatsächlich macht. Wenn die Quelladresse größer als die Zieladresse ist, reicht es aus, von Anfang bis Ende zu kopieren (Vorwärtskopie). Wenn die Quelladresse kleiner als die Zieladresse ist, reicht es aus, vom Ende zum Anfang zu kopieren (Rückwärtskopie). Es wird kein Hilfsspeicher benötigt oder verwendet.
Jonathan Leffler

Versuchen Sie, Ihre Antwort mit tatsächlichen Daten im Code zu erklären, das wäre hilfreicher.
HaseeB Mir
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.