Was ist der Unterschied zwischen memmove und memcpy?

120

Was ist der Unterschied zwischen memmoveund memcpy? Welches verwenden Sie normalerweise und wie?

Gemeinschaft
quelle
Beachten Sie die Probleme, die auftreten können: lwn.net/Articles/414467
Zan Lynx

Antworten:

162

Mit memcpykann das Ziel die Quelle überhaupt nicht überlappen. Damit memmovekann. Dies bedeutet, dass dies memmovemöglicherweise etwas langsamer ist als memcpy, da nicht dieselben Annahmen getroffen werden können.

Beispielsweise können memcpyAdressen immer von niedrig nach hoch kopiert werden. Wenn sich das Ziel nach der Quelle überschneidet, bedeutet dies, dass einige Adressen vor dem Kopieren überschrieben werden. memmovewürde dies erkennen und in diesem Fall in die andere Richtung - von hoch nach niedrig - kopieren. Das Überprüfen und Umschalten auf einen anderen (möglicherweise weniger effizienten) Algorithmus nimmt jedoch Zeit in Anspruch.

bdonlan
quelle
1
Wie kann ich bei Verwendung von memcpy sicherstellen, dass sich die Adressen src und dest nicht überschneiden? Sollte ich persönlich sicherstellen, dass sich src und dest nicht überschneiden?
Alcott
6
@Alcott, benutze memcpy nicht, wenn du nicht weißt, dass sie sich nicht überlappen - benutze stattdessen memmove. Wenn es keine Überlappung gibt, sind memmove und memcpy gleichwertig (obwohl memcpy möglicherweise sehr, sehr, sehr etwas schneller ist).
Bdonlan
Sie können das Schlüsselwort "einschränken" verwenden, wenn Sie mit langen Arrays arbeiten und Ihren Kopiervorgang schützen möchten. Wenn Ihre Methode beispielsweise Eingabe- und Ausgabearrays als Parameter verwendet und Sie sicherstellen müssen, dass der Benutzer nicht dieselbe Adresse wie Eingabe und Ausgabe übergibt. Lesen Sie hier mehr stackoverflow.com/questions/776283/…
DanielHsH
10
@DanielHsH 'einschränken' ist ein Versprechen, das Sie dem Compiler machen; Es wird vom Compiler nicht erzwungen . Wenn Sie Ihre Argumente einschränken und tatsächlich überlappen (oder allgemeiner auf die eingeschränkten Daten von einem Zeiger zugreifen, der von mehreren Stellen abgeleitet wurde), ist das Verhalten des Programms undefiniert, es treten seltsame Fehler auf, und der Compiler wird Sie normalerweise nicht davor warnen.
Bdonlan
@bdonlan Es ist nicht nur ein Versprechen an den Compiler, es ist eine Voraussetzung für Ihren Anrufer. Diese Anforderung wird nicht durchgesetzt. Wenn Sie jedoch gegen eine Anforderung verstoßen, können Sie sich nicht beschweren, wenn Sie unerwartete Ergebnisse erhalten. Eine Verletzung zu verletzen ist undefiniertes Verhalten, genau wie i = i++ + 1undefiniert ist. Der Compiler verbietet Ihnen nicht, genau diesen Code zu schreiben, aber das Ergebnis dieser Anweisung kann alles sein, und verschiedene Compiler oder CPUs zeigen hier unterschiedliche Werte an.
Mecki
33

memmovekann mit überlappendem Speicher umgehen, memcpykann nicht.

Erwägen

char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up

Offensichtlich überschneiden sich Quelle und Ziel jetzt, wir überschreiben "-bar" mit "bar". Es ist ein undefiniertes Verhalten, memcpywenn sich Quelle und Ziel überschneiden. In diesem Fall benötigen wir also memmove.

memmove(&str[3],&str[4],4); //fine
nr
quelle
5
warum sollte zuerst explodieren?
4
@ultraman: Weil es möglicherweise mit einer Low-Level-Baugruppe implementiert wurde, die erfordert, dass sich der Speicher nicht überlappt. In diesem Fall können Sie beispielsweise eine Signal- oder Hardware-Ausnahme für den Prozessor generieren, die die Anwendung abbricht. In der Dokumentation wird angegeben, dass die Bedingung nicht behandelt wird, der Standard gibt jedoch nicht an, was passieren wird, wenn diese Bedingungen vialoiert werden (dies wird als undefiniertes Verhalten bezeichnet). Undefiniertes Verhalten kann alles.
Martin York
Mit gcc 4.8.2 akzeptiert Even memcpy auch überlappende Quell- und Zielzeiger und funktioniert einwandfrei.
GeekyJ
4
@ jagsgediya Sicher könnte es. Da jedoch dokumentiert ist, dass memcpy dies nicht unterstützt, sollten Sie sich nicht auf dieses implementierungsspezifische Verhalten verlassen. Deshalb gibt es memmove (). In einer anderen Version von gcc kann dies anders sein. Es kann anders sein, wenn gcc das memcpy einbindet, anstatt memcpy () in glibc aufzurufen, es kann anders sein bei einer älteren oder neueren Version von glibc und so weiter.
Nr.
Aus der Praxis scheinen memcpy und memmove dasselbe getan zu haben. So ein tiefes undefiniertes Verhalten.
Leben
22

Von der memcpy- Manpage.

Die Funktion memcpy () kopiert n Bytes vom Speicherbereich src in den Speicherbereich dest. Die Speicherbereiche sollten sich nicht überlappen. Verwenden Sie memmove (3), wenn sich die Speicherbereiche überlappen.

John Carter
quelle
12

Der Hauptunterschied zwischen memmove()und memcpy()besteht darin, dass in memmove()einem Puffer ein temporärer Speicher verwendet wird, sodass keine Überlappungsgefahr besteht. Kopiert andererseits memcpy()die Daten direkt von dem Ort, auf den die Quelle zeigt, zu dem Ort, auf den das Ziel zeigt . ( http://www.cplusplus.com/reference/cstring/memcpy/ )

Betrachten Sie die folgenden Beispiele:

  1. #include <stdio.h>
    #include <string.h>
    
    int main (void)
    {
        char string [] = "stackoverflow";
        char *first, *second;
        first = string;
        second = string;
    
        puts(string);
        memcpy(first+5, first, 5);
        puts(first);
        memmove(second+5, second, 5);
        puts(second);
        return 0;
    }

    Wie erwartet wird dies ausgedruckt:

    stackoverflow
    stackstacklow
    stackstacklow
  2. In diesem Beispiel sind die Ergebnisse jedoch nicht dieselben:

    #include <stdio.h>
    #include <string.h>
    
    int main (void)
    {
        char string [] = "stackoverflow";
        char *third, *fourth;
        third = string;
        fourth = string;
    
        puts(string);
        memcpy(third+5, third, 7);
        puts(third);
        memmove(fourth+5, fourth, 7);
        puts(fourth);
        return 0;
    }

    Ausgabe:

    stackoverflow
    stackstackovw
    stackstackstw

Dies liegt daran, dass "memcpy ()" Folgendes tut:

1.  stackoverflow
2.  stacksverflow
3.  stacksterflow
4.  stackstarflow
5.  stackstacflow
6.  stackstacklow
7.  stackstacksow
8.  stackstackstw
Mirac Suzgun
quelle
2
Aber es scheint, dass die von Ihnen erwähnte Ausgabe umgekehrt ist !!
Kumar
1
Wenn ich dasselbe Programm ausführe, erhalte ich das folgende Ergebnis: stackoverflow stackstackstw stackstackstw // bedeutet, dass es keinen Unterschied in der Ausgabe zwischen memcpy und memmove gibt
kumar
4
"Ist das, dass in" memmove () "ein Puffer - ein temporärer Speicher - verwendet wird?" Ist nicht wahr. es sagt "als ob", also muss es sich nur so verhalten, nicht dass es so sein muss. Das ist in der Tat relevant, da die meisten Memmove-Implementierungen nur einen XOR-Swap durchführen.
Dhein
2
Ich denke nicht, dass die Implementierung von memmove()erforderlich ist, um einen Puffer zu verwenden. Es ist vollkommen berechtigt, sich an Ort und Stelle zu bewegen (solange jeder Lesevorgang abgeschlossen ist, bevor an dieselbe Adresse geschrieben wird).
Toby Speight
12

Angenommen, Sie müssten beide implementieren, könnte die Implementierung folgendermaßen aussehen:

void memmove ( void * dst, const void * src, size_t count ) {
    if ((uintptr_t)src < (uintptr_t)dst) {
        // Copy from back to front

    } else if ((uintptr_t)dst < (uintptr_t)src) {
        // Copy from front to back
    }
}

void mempy ( void * dst, const void * src, size_t count ) {
    if ((uintptr_t)src != (uintptr_t)dst) {
        // Copy in any way you want
    }
}

Und das sollte den Unterschied ziemlich gut erklären. memmovekopiert immer so, dass es immer noch sicher ist, wenn srcund dstüberlappen, während es memcpyeinfach egal ist, wie die Dokumentation bei der Verwendung sagt memcpy, dürfen sich die beiden Speicherbereiche nicht überlappen.

ZB wenn memcpyKopien "von vorne nach hinten" und die Speicherblöcke so ausgerichtet sind

[---- src ----]
            [---- dst ---]

Durch das Kopieren des ersten Bytes von srcbis wird dstbereits der Inhalt der letzten Bytes von zerstört, srcbevor diese kopiert wurden. Nur das Kopieren von "von hinten nach vorne" führt zu korrekten Ergebnissen.

Jetzt tauschen srcund dst:

[---- dst ----]
            [---- src ---]

In diesem Fall ist es nur sicher, "von vorne nach hinten" zu kopieren, da das Kopieren von "von hinten nach vorne" srcbereits beim Kopieren des ersten Bytes in der Nähe seiner Vorderseite zerstört wird.

Möglicherweise haben Sie bemerkt, dass die memmoveobige Implementierung nicht einmal testet, ob sie sich tatsächlich überschneiden, sondern nur ihre relativen Positionen überprüft. Dies allein macht die Kopie jedoch sicher. Da memcpynormalerweise der schnellstmögliche Weg zum Kopieren von Speicher auf einem beliebigen System verwendet wird, memmovewird dies normalerweise eher implementiert als:

void memmove ( void * dst, const void * src, size_t count ) {
    if ((uintptr_t)src < (uintptr_t)dst
        && (uintptr_t)src + count > (uintptr_t)dst
    ) {
        // Copy from back to front

    } else if ((uintptr_t)dst < (uintptr_t)src
        && (uintptr_t)dst + count > (uintptr_t)src
    ) {
        // Copy from front to back

    } else {
        // They don't overlap for sure
        memcpy(dst, src, count);
    }
}

Manchmal, wenn memcpyimmer "von vorne nach hinten" oder "von hinten nach vorne" kopiert wird, memmovekann dies auch memcpyin einem der überlappenden Fälle verwendet werden, es memcpykann jedoch sogar auf andere Weise kopiert werden, je nachdem, wie die Daten ausgerichtet sind und / oder wie viele Daten sein sollen kopiert. Selbst wenn Sie getestet haben, wie memcpyKopien auf Ihrem System erstellt wurden, können Sie sich nicht darauf verlassen, dass dieses Testergebnis immer korrekt ist.

Was bedeutet das für Sie, wenn Sie sich für einen Anruf entscheiden?

  1. Wenn Sie das nicht genau wissen srcund dstsich nicht überschneiden, rufen memmoveSie an , da dies immer zu korrekten Ergebnissen führt und normalerweise so schnell ist, wie dies für den gewünschten Kopierfall möglich ist.

  2. Wenn Sie das sicher wissen srcund dstsich nicht überschneiden, rufen memcpySie an , da es keine Rolle spielt, welches Sie für das Ergebnis aufrufen. Beide funktionieren in diesem Fall korrekt, sind jedoch memmoveniemals schneller als memcpyund wenn Sie Pech haben, kann es sogar sein Seien Sie langsamer, damit Sie nur Anrufe gewinnen können memcpy.

Mecki
quelle
+1, weil Ihre "ASCII-Zeichnungen" nützlich waren, um zu verstehen, warum es möglicherweise keine Überlappungen gibt, ohne die Daten zu
beschädigen
10

Einer behandelt überlappende Ziele, der andere nicht.

KPexEA
quelle
6

einfach aus der Norm ISO / IEC: 9899 ist es gut beschrieben.

7.21.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.

Und

7.21.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.

Welches ich normalerweise gemäß der Frage verwende, hängt davon ab, welche Funktionalität ich benötige.

Im Klartext memcpy()erlaubt s1und s2überlappt nicht, während memmove()tut.

Dhein
quelle
0

Es gibt zwei offensichtliche Möglichkeiten zur Implementierung mempcpy(void *dest, const void *src, size_t n)(Ignorieren des Rückgabewerts):

  1. for (char *p=src, *q=dest;  n-->0;  ++p, ++q)
        *q=*p;
  2. char *p=src, *q=dest;
    while (n-->0)
        q[n]=p[n];

In der ersten Implementierung geht die Kopie von niedrigen zu hohen Adressen und in der zweiten von hohen zu niedrigen Adressen über. Wenn sich der zu kopierende Bereich überlappt (wie dies beispielsweise beim Scrollen eines Framebuffers der Fall ist), ist nur eine Betriebsrichtung korrekt, und die andere überschreibt Positionen, aus denen anschließend gelesen wird.

Eine memmove()Implementierung testet im einfachsten dest<srcFall (auf plattformabhängige Weise) und führt die entsprechende Richtung aus memcpy().

Benutzercode kann das natürlich nicht, da sie selbst nach dem Umwandeln srcund dstauf einen konkreten Zeigertyp (im Allgemeinen) nicht auf dasselbe Objekt zeigen und daher nicht verglichen werden können. Die Standardbibliothek kann jedoch über genügend Plattformkenntnisse verfügen, um einen solchen Vergleich durchzuführen, ohne undefiniertes Verhalten zu verursachen.


Beachten Sie, dass Implementierungen im wirklichen Leben tendenziell wesentlich komplexer sind, um die maximale Leistung durch größere Übertragungen (sofern die Ausrichtung dies zulässt) und / oder eine gute Auslastung des Datencaches zu erzielen. Der obige Code dient nur dazu, den Punkt so einfach wie möglich zu machen.

Toby Speight
quelle
0

memmove kann sich mit überlappenden Quell- und Zielregionen befassen, memcpy nicht. Unter den beiden ist memcpy viel effizienter. Verwenden Sie memcpy also besser, wenn Sie können.

Referenz: https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr. Jerry Cain, (Stanford Intro Systems Lecture - 7) Zeit: 36:00

Ehsan
quelle
Diese Antwort sagt "möglicherweise ein bisschen schneller" und liefert quantitative Daten, die nur einen geringen Unterschied anzeigen. Diese Antwort besagt, dass man "viel effizienter" ist. Wie viel effizienter haben Sie den schnelleren gefunden? Übrigens: Ich nehme an, Sie meinen memcpy()und nicht memcopy().
chux
Der Kommentar basiert auf dem Vortrag von Dr. Jerry Cain. Ich würde Sie bitten, seinen Vortrag um 36:00 Uhr anzuhören, nur 2-3 Minuten werden ausreichen. Und danke für den Fang. : D
Ehsan