Was ist der Unterschied zwischen memmove
und memcpy
? Welches verwenden Sie normalerweise und wie?
Was ist der Unterschied zwischen memmove
und memcpy
? Welches verwenden Sie normalerweise und wie?
Antworten:
Mit memcpy
kann das Ziel die Quelle überhaupt nicht überlappen. Damit memmove
kann. Dies bedeutet, dass dies memmove
möglicherweise etwas langsamer ist als memcpy
, da nicht dieselben Annahmen getroffen werden können.
Beispielsweise können memcpy
Adressen 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. memmove
wü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.
i = i++ + 1
undefiniert 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.
memmove
kann mit überlappendem Speicher umgehen, memcpy
kann 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, memcpy
wenn sich Quelle und Ziel überschneiden. In diesem Fall benötigen wir also memmove
.
memmove(&str[3],&str[4],4); //fine
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:
#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
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
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).
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. memmove
kopiert immer so, dass es immer noch sicher ist, wenn src
und dst
überlappen, während es memcpy
einfach egal ist, wie die Dokumentation bei der Verwendung sagt memcpy
, dürfen sich die beiden Speicherbereiche nicht überlappen.
ZB wenn memcpy
Kopien "von vorne nach hinten" und die Speicherblöcke so ausgerichtet sind
[---- src ----]
[---- dst ---]
Durch das Kopieren des ersten Bytes von src
bis wird dst
bereits der Inhalt der letzten Bytes von zerstört, src
bevor diese kopiert wurden. Nur das Kopieren von "von hinten nach vorne" führt zu korrekten Ergebnissen.
Jetzt tauschen src
und dst
:
[---- dst ----]
[---- src ---]
In diesem Fall ist es nur sicher, "von vorne nach hinten" zu kopieren, da das Kopieren von "von hinten nach vorne" src
bereits beim Kopieren des ersten Bytes in der Nähe seiner Vorderseite zerstört wird.
Möglicherweise haben Sie bemerkt, dass die memmove
obige 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 memcpy
normalerweise der schnellstmögliche Weg zum Kopieren von Speicher auf einem beliebigen System verwendet wird, memmove
wird 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 memcpy
immer "von vorne nach hinten" oder "von hinten nach vorne" kopiert wird, memmove
kann dies auch memcpy
in einem der überlappenden Fälle verwendet werden, es memcpy
kann 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 memcpy
Kopien 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?
Wenn Sie das nicht genau wissen src
und dst
sich nicht überschneiden, rufen memmove
Sie 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.
Wenn Sie das sicher wissen src
und dst
sich nicht überschneiden, rufen memcpy
Sie an , da es keine Rolle spielt, welches Sie für das Ergebnis aufrufen. Beide funktionieren in diesem Fall korrekt, sind jedoch memmove
niemals schneller als memcpy
und wenn Sie Pech haben, kann es sogar sein Seien Sie langsamer, damit Sie nur Anrufe gewinnen können memcpy
.
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 s1
und s2
überlappt nicht, während memmove()
tut.
Es gibt zwei offensichtliche Möglichkeiten zur Implementierung mempcpy(void *dest, const void *src, size_t n)
(Ignorieren des Rückgabewerts):
for (char *p=src, *q=dest; n-->0; ++p, ++q)
*q=*p;
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<src
Fall (auf plattformabhängige Weise) und führt die entsprechende Richtung aus memcpy()
.
Benutzercode kann das natürlich nicht, da sie selbst nach dem Umwandeln src
und dst
auf 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.
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
memcpy()
und nicht memcopy()
.