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.
podczas korzystania z memcpy, jak mogę zagwarantować, że adresy src i dest nie pokrywają się? Czy powinienem osobiście upewnić się, że src i dest nie nakładają się na siebie?
Alcott,
6
@Alcott, nie używaj memcpy, jeśli nie wiesz, że się nie nakładają - zamiast tego użyj memmove. Gdy nie ma nakładania się, memmove i memcpy są równoważne (chociaż memcpy może być bardzo, bardzo, bardzo nieznacznie szybsze).
bdonlan,
Możesz użyć słowa kluczowego „ogranicz”, jeśli pracujesz z długimi tablicami i chcesz zabezpieczyć proces kopiowania. Na przykład, jeśli metoda przyjmuje jako parametry tablice wejściowe i wyjściowe i musisz sprawdzić, czy użytkownik nie przekazuje tego samego adresu co wejście i wyjście. Przeczytaj więcej tutaj stackoverflow.com/questions/776283/…
DanielHsH
10
@DanielHsH 'ogranicz' to obietnica, którą składasz kompilatorowi; nie jest wymuszane przez kompilator. Jeśli ustawisz `` ogranicz '' na swoich argumentach i faktycznie pokrywają się one (lub, bardziej ogólnie, uzyskasz dostęp do zastrzeżonych danych ze wskaźnika pochodzącego z wielu miejsc), zachowanie programu jest niezdefiniowane, pojawią się dziwne błędy, a kompilator zazwyczaj Cię o tym nie ostrzeże.
bdonlan
@bdonlan To nie tylko obietnica dla kompilatora, to wymóg dla twojego rozmówcy. Jest to wymóg, który nie jest egzekwowany, ale jeśli go naruszysz, nie możesz narzekać, jeśli uzyskasz nieoczekiwane wyniki. Naruszenie wymagania jest niezdefiniowanym zachowaniem, tak samo jak 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.
Mecki,
33
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.
@ultraman: Ponieważ MOŻE zostać zaimplementowane przy użyciu kompilacji niskiego poziomu, która wymaga, aby pamięć się nie nakładała. Jeśli tak, możesz na przykład wygenerować sygnał lub wyjątek sprzętowy dla procesora, który przerywa aplikację. Dokumentacja określa, że nie obsługuje warunku, ale standard nie określa, co się stanie, gdy te warunki zostaną uwzględnione (jest to znane jako niezdefiniowane zachowanie). Niezdefiniowane zachowanie może zrobić wszystko.
Martin York
z gcc 4.8.2, nawet memcpy akceptuje nakładające się wskaźniki źródłowe i docelowe i działa dobrze.
GeekyJ
4
@jagsgediya Jasne, że tak. Ale ponieważ memcpy jest udokumentowane, że nie obsługuje tego, nie powinieneś polegać na tym specyficznym zachowaniu implementacyjnym, dlatego istnieje memmove (). W innej wersji gcc może być inaczej. Może być inaczej, jeśli gcc wstawia memcpy zamiast wywoływać memcpy () w glibc, może być inaczej w starszej lub nowszej wersji glibc i tak dalej.
nr
Z praktyki wynika, że memcpy i memmove zrobiły to samo. Takie głębokie, niezdefiniowane zachowanie.
Funkcja memcpy () kopiuje n bajtów z obszaru pamięci src do obszaru pamięci dest. Obszary pamięci nie powinny się pokrywać. Użyj memmove (3), jeśli obszary pamięci się pokrywają.
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);return0;}
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);return0;}
Wynik:
stackoverflow
stackstackovw
stackstackstw
Dzieje się tak, ponieważ „memcpy ()” wykonuje następujące czynności:
Ale wygląda na to, że dane wyjściowe, o których wspomniałeś, są odwrócone!
kumar
1
Kiedy uruchamiam ten sam program, otrzymuję następujący wynik: stackoverflow stackstackstw stackstackstw // oznacza, że nie ma różnicy w danych wyjściowych między memcpy i memmove
kumar
4
"to że w" memmove () "używany jest bufor - pamięć tymczasowa;" To nie jest prawda. mówi „jak gdyby”, więc po prostu musi się tak zachowywać, a nie, żeby tak było. Jest to rzeczywiście istotne, ponieważ większość implementacji memmove wykonuje po prostu zamianę XOR.
dhein
2
Nie sądzę, aby implementacja 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).
Toby Speight
12
Zakładając, że musiałbyś zaimplementować oba, implementacja mogłaby wyglądać tak:
void memmove (void* dst,constvoid* src,size_t count ){if((uintptr_t)src <(uintptr_t)dst){// Copy from back to front}elseif((uintptr_t)dst <(uintptr_t)src){// Copy from front to back}}void mempy (void* dst,constvoid* 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,constvoid* src,size_t count ){if((uintptr_t)src <(uintptr_t)dst
&&(uintptr_t)src + count >(uintptr_t)dst
){// Copy from back to front}elseif((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.
Ta odpowiedź mówi „prawdopodobnie odrobinę szybciej” i dostarcza danych ilościowych wskazujących tylko na niewielką różnicę. Ta odpowiedź stwierdza, że jest się „znacznie bardziej wydajnym”. O ile wydajniejszy okazał się być szybszy? BTW: Zakładam, że masz na myśli memcpy()i nie memcopy().
chux - Przywróć Monikę
Komentarz jest oparty na wykładzie dr Jerry'ego Caina. Proszę o wysłuchanie jego wykładu o godzinie 36:00, wystarczą tylko 2-3 minuty. Dzięki za połów. : D
Odpowiedzi:
W
memcpy
przypadku miejsce docelowe nie może w ogóle pokrywać się ze źródłem. Dziękimemmove
temu może. Oznacza to, żememmove
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.źródło
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ć
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 potrzebujemymemmove
.źródło
Ze strony man memcpy .
źródło
Główną różnicą pomiędzy
memmove()
imemcpy()
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:
Zgodnie z oczekiwaniami wydrukuje się:
Ale w tym przykładzie wyniki nie będą takie same:
Wynik:
Dzieje się tak, ponieważ „memcpy ()” wykonuje następujące czynności:
źródło
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:
I to powinno całkiem dobrze wyjaśniać różnicę.
memmove
zawsze kopiuje w taki sposób, że nadal jest bezpiecznesrc
idst
zachodzi na siebie, alememcpy
po prostu nie obchodzi, jak mówi dokumentacja podczas używaniamemcpy
, 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óbskopiowanie pierwszego bajtu
src
dodst
już niszczy zawartość ostatnich bajtówsrc
sprzed ich skopiowania. Tylko kopiowanie „od tyłu do przodu” da poprawne wyniki.Teraz zamień
src
idst
: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. Jakmemcpy
zwykle używa najszybszego możliwego sposobu kopiowania pamięci w dowolnym systemie,memmove
jest zwykle raczej implementowany jako: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, alememcpy
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ś, jakmemcpy
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
idst
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
idst
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, alememmove
nigdy nie będą szybsze niż,memcpy
a jeśli masz pecha, może nawet wolniej, więc możesz wygrać tylko dzwonieniememcpy
.źródło
Jeden obsługuje nakładające się miejsca docelowe, a drugi nie.
źródło
po prostu z normy ISO / IEC: 9899 jest dobrze opisany.
I
Który z nich zwykle używam zgodnie z pytaniem, zależy od potrzebnej funkcjonalności.
W zwykłym tekście
memcpy()
nie pozwalas1
is2
nakłada się, podczas gdymemmove()
nie.źródło
Istnieją dwa oczywiste sposoby implementacji
mempcpy(void *dest, const void *src, size_t n)
(ignorowanie zwracanej wartości):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, przetestujedest<src
(w jakiś sposób zależne od platformy) i wykonać odpowiedni kierunekmemcpy()
.Kod użytkownika nie może tego oczywiście zrobić, ponieważ nawet po rzutowaniu
src
idst
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.
źródło
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
źródło
memcpy()
i niememcopy()
.