Jaka jest różnica między memmove
i memcpy
? Którego zazwyczaj używasz i jak?
Jaka jest różnica między memmove
i memcpy
? Którego zazwyczaj używasz i jak?
Odpowiedzi:
W memcpy
przypadku miejsce docelowe nie może w ogóle pokrywać się ze źródłem. Dzięki memmove
temu może. Oznacza to, że memmove
może to być nieco wolniejsze niż memcpy
, ponieważ nie może przyjąć takich samych założeń.
Na przykład memcpy
moż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. memmove
wykryje 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++ + 1
niezdefiniowanym; 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.
memmove
radzi sobie z nakładaniem się pamięci, memcpy
nie 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, memcpy
gdy ź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
stackstacklow
Ale 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
stackstackstw
Dzieje 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ę. memmove
zawsze kopiuje w taki sposób, że nadal jest bezpieczne src
i dst
zachodzi na siebie, ale memcpy
po prostu nie obchodzi, jak mówi dokumentacja podczas używania memcpy
, oba obszary pamięci nie mogą się pokrywać.
Np. Jeśli memcpy
kopiuje "od przodu do tyłu" i bloki pamięci są wyrównane w ten sposób
[---- src ----]
[---- dst ---]
skopiowanie pierwszego bajtu src
do dst
już niszczy zawartość ostatnich bajtów src
sprzed ich skopiowania. Tylko kopiowanie „od tyłu do przodu” da poprawne wyniki.
Teraz zamień src
i 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 src
pobliżu przodu już podczas kopiowania pierwszego bajtu.
Być może zauważyłeś, że memmove
powyż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 memcpy
zwykle używa najszybszego możliwego sposobu kopiowania pamięci w dowolnym systemie, memmove
jest 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 memcpy
zawsze kopiuje „od przodu do tyłu” lub „od tyłu do przodu”, memmove
może również użyć memcpy
w jednym z nakładających się przypadków, ale memcpy
moż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 memcpy
kopiuje 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 src
i dst
nie nakładasz się na siebie, zadzwoń, memmove
ponieważ 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 src
i dst
nie nakładasz się na siebie, zadzwoń, memcpy
ponieważ nie ma znaczenia, który z nich wywołasz, oba będą działać poprawnie w tym przypadku, ale memmove
nigdy nie będą szybsze niż, memcpy
a 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 s1
i s2
nakł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 src
i dst
do 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()
.