Jaka jest różnica między memmovei memcpy? Którego zazwyczaj używasz i jak?
Jaka jest różnica między memmovei memcpy? Którego zazwyczaj używasz i jak?
Odpowiedzi:
W memcpyprzypadku miejsce docelowe nie może w ogóle pokrywać się ze źródłem. Dzięki memmovetemu może. Oznacza to, że memmovemoże to być nieco wolniejsze niż memcpy, ponieważ nie może przyjąć takich samych założeń.
Na przykład memcpymoże zawsze kopiować adresy od niskiego do wysokiego. Jeśli miejsce docelowe nakłada się na źródło, oznacza to, że niektóre adresy zostaną nadpisane przed skopiowaniem. memmovewykryje to i skopiuje w innym kierunku - od wysokiego do niskiego - w tym przypadku. Jednak sprawdzenie tego i przejście na inny (prawdopodobnie mniej wydajny) algorytm zajmuje trochę czasu.
i = i++ + 1niezdefiniowanym; kompilator nie zabrania ci pisania dokładnie tego kodu, ale wynikiem tej instrukcji może być cokolwiek, a różne kompilatory lub procesory pokażą tutaj różne wartości.
memmoveradzi sobie z nakładaniem się pamięci, memcpynie może.
Rozważać
char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up
Oczywiście źródło i miejsce docelowe nakładają się teraz, nadpisujemy „-bar” na „bar”. Jest to niezdefiniowane zachowanie, memcpygdy źródło i miejsce docelowe nakładają się, więc w tym przypadku potrzebujemy memmove.
memmove(&str[3],&str[4],4); //fine
Główną różnicą pomiędzy memmove()i memcpy()jest to, że w bufor - pamięć tymczasowa - jest używany, więc nie ma ryzyka nakładania. Z drugiej strony bezpośrednio kopiuje dane z lokalizacji wskazanej przez źródło do lokalizacji wskazanej przez cel . ( http://www.cplusplus.com/reference/cstring/memcpy/ )memmove()memcpy()
Rozważ następujące przykłady:
#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;
}
Zgodnie z oczekiwaniami wydrukuje się:
stackoverflow
stackstacklow
stackstacklowAle w tym przykładzie wyniki nie będą takie same:
#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;
}
Wynik:
stackoverflow
stackstackovw
stackstackstwDzieje się tak, ponieważ „memcpy ()” wykonuje następujące czynności:
1. stackoverflow
2. stacksverflow
3. stacksterflow
4. stackstarflow
5. stackstacflow
6. stackstacklow
7. stackstacksow
8. stackstackstw
memmove()była wymagana do korzystania z bufora. Jest całkowicie uprawniony do przenoszenia w miejscu (o ile każdy odczyt kończy się przed jakimkolwiek zapisem na ten sam adres).
Zakładając, że musiałbyś zaimplementować oba, implementacja mogłaby wyglądać tak:
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
}
}
I to powinno całkiem dobrze wyjaśniać różnicę. memmovezawsze kopiuje w taki sposób, że nadal jest bezpieczne srci dstzachodzi na siebie, ale memcpypo prostu nie obchodzi, jak mówi dokumentacja podczas używania memcpy, oba obszary pamięci nie mogą się pokrywać.
Np. Jeśli memcpykopiuje "od przodu do tyłu" i bloki pamięci są wyrównane w ten sposób
[---- src ----]
[---- dst ---]
skopiowanie pierwszego bajtu srcdo dstjuż niszczy zawartość ostatnich bajtów srcsprzed ich skopiowania. Tylko kopiowanie „od tyłu do przodu” da poprawne wyniki.
Teraz zamień srci dst:
[---- dst ----]
[---- src ---]
W takim przypadku kopiowanie „od przodu do tyłu” jest bezpieczne, ponieważ kopiowanie „od tyłu do przodu” spowodowałoby zniszczenie w srcpobliżu przodu już podczas kopiowania pierwszego bajtu.
Być może zauważyłeś, że memmovepowyższa implementacja nawet nie testuje, czy faktycznie pokrywają się, po prostu sprawdza ich względne pozycje, ale samo to sprawi, że kopia będzie bezpieczna. Jak memcpyzwykle używa najszybszego możliwego sposobu kopiowania pamięci w dowolnym systemie, memmovejest zwykle raczej implementowany jako:
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);
}
}
Czasami, jeśli memcpyzawsze kopiuje „od przodu do tyłu” lub „od tyłu do przodu”, memmovemoże również użyć memcpyw jednym z nakładających się przypadków, ale memcpymoże nawet skopiować w inny sposób w zależności od tego, jak dane są wyrównane i / lub ile danych ma być skopiowane, więc nawet jeśli przetestowałeś, jak memcpykopiuje się w twoim systemie, nie możesz polegać na tym, że wynik testu będzie zawsze poprawny.
Co to oznacza dla Ciebie, gdy decydujesz, do kogo zadzwonić?
O ile nie masz pewności, że to się nie pokrywa srci dstnie nakładasz się na siebie, zadzwoń, memmoveponieważ zawsze prowadzi to do poprawnych wyników i zwykle jest tak szybkie, jak to możliwe w przypadku kopii, której potrzebujesz.
Jeśli wiesz na pewno srci dstnie nakładasz się na siebie, zadzwoń, memcpyponieważ nie ma znaczenia, który z nich wywołasz, oba będą działać poprawnie w tym przypadku, ale memmovenigdy nie będą szybsze niż, memcpya jeśli masz pecha, może nawet wolniej, więc możesz wygrać tylko dzwonienie memcpy.
po prostu z normy ISO / IEC: 9899 jest dobrze opisany.
7.21.2.1 Funkcja memcpy
[…]
2 Funkcja memcpy kopiuje n znaków z obiektu wskazywanego przez s2 do obiektu wskazywanego przez s1. Jeśli kopiowanie odbywa się między nakładającymi się obiektami, zachowanie jest niezdefiniowane.
I
7.21.2.2 Funkcja memmove
[…]
2 Funkcja memmove kopiuje n znaków z obiektu wskazywanego przez s2 do obiektu wskazywanego przez s1. Kopiowanie odbywa się tak, jakby n znaków z obiektu wskazywanego przez s2 było najpierw kopiowanych do tymczasowej tablicy n znaków, która nie pokrywa się z obiektami wskazywanymi przez s1 i s2, a następnie n znaków z tymczasowej tablicy jest kopiowanych do obiekt wskazywany przez s1.
Który z nich zwykle używam zgodnie z pytaniem, zależy od potrzebnej funkcjonalności.
W zwykłym tekście memcpy()nie pozwala s1i s2nakłada się, podczas gdy memmove()nie.
Istnieją dwa oczywiste sposoby implementacji mempcpy(void *dest, const void *src, size_t n)(ignorowanie zwracanej wartości):
for (char *p=src, *q=dest; n-->0; ++p, ++q)
*q=*p;char *p=src, *q=dest;
while (n-->0)
q[n]=p[n];W pierwszej implementacji kopia przechodzi od niskiego do wysokiego adresu, aw drugiej od wysokiego do niskiego. Jeśli zakres do skopiowania nakłada się (jak ma to miejsce na przykład podczas przewijania bufora ramki), to tylko jeden kierunek operacji jest poprawny, a drugi nadpisuje lokalizacje, z których będą później odczytywane.
memmove()Wdrożenie, w swojej najprostszej, przetestuje dest<src(w jakiś sposób zależne od platformy) i wykonać odpowiedni kierunek memcpy().
Kod użytkownika nie może tego oczywiście zrobić, ponieważ nawet po rzutowaniu srci dstdo konkretnego typu wskaźnika nie wskazują (na ogół) tego samego obiektu, więc nie można ich porównać. Ale standardowa biblioteka może mieć wystarczającą wiedzę o platformie, aby wykonać takie porównanie bez powodowania niezdefiniowanego zachowania.
Należy pamiętać, że w rzeczywistości implementacje są znacznie bardziej złożone, aby uzyskać maksymalną wydajność z większych transferów (jeśli pozwala na to wyrównanie) i / lub dobre wykorzystanie pamięci podręcznej danych. Powyższy kod ma na celu uproszczenie sprawy.
memmove radzi sobie z nakładającymi się regionami źródłowymi i docelowymi, podczas gdy memcpy nie. Spośród tych dwóch memcpy jest znacznie wydajniejszy. Więc lepiej UŻYWAJ memcpy, jeśli możesz.
Źródła : https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr Jerry Cain, (Wykład Stanford Intro Systems - 7) Godz .: 36:00
memcpy()i nie memcopy().