Was ist der Unterschied zwischen memmoveund memcpy? Welches verwenden Sie normalerweise und wie?
Was ist der Unterschied zwischen memmoveund memcpy? Welches verwenden Sie normalerweise und wie?
Antworten:
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.
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.
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
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
stackstacklowIn 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
stackstackstwDies 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. 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?
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.
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.
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.
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<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.
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().