Próbuję zrozumieć różnicę między memcpy()
i memmove()
, i przeczytałem tekst, memcpy()
który nie uwzględnia nakładającego się źródła i miejsca docelowego, podczas gdy memmove()
tak.
Jednak gdy wykonuję te dwie funkcje na nakładających się blokach pamięci, obie dają ten sam wynik. Na przykład weź następujący przykład MSDN na stronie memmove()
pomocy: -
Czy istnieje lepszy przykład, aby zrozumieć wady memcpy
i jak memmove
je rozwiązać?
// crt_memcpy.c
// Illustrate overlapping copy: memmove always handles it correctly; memcpy may handle
// it correctly.
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[7] = "aabbcc";
int main( void )
{
printf( "The string: %s\n", str1 );
memcpy( str1 + 2, str1, 4 );
printf( "New string: %s\n", str1 );
strcpy_s( str1, sizeof(str1), "aabbcc" ); // reset string
printf( "The string: %s\n", str1 );
memmove( str1 + 2, str1, 4 );
printf( "New string: %s\n", str1 );
}
Wynik:
The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb
memcpy
byłobyassert
, gdyby regiony nie zachodziły na siebie, a nie celowo ukrywały błędy w kodzie.The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
Odpowiedzi:
Nie jestem całkowicie zaskoczony, że twój przykład nie wykazuje dziwnego zachowania. Zamiast tego spróbuj skopiować
str1
dostr1+2
i zobacz, co się wtedy stanie. (Może nie mieć znaczenia, zależy od kompilatora / bibliotek).Ogólnie rzecz biorąc, memcpy jest implementowane w prosty (ale szybki) sposób. Upraszczając, po prostu zapętla dane (w kolejności), kopiując z jednej lokalizacji do drugiej. Może to spowodować nadpisanie źródła podczas odczytu.
Memmove wykonuje więcej pracy, aby upewnić się, że poprawnie obsługuje nakładanie się.
EDYTOWAĆ:
(Niestety nie mogę znaleźć przyzwoitych przykładów, ale te wystarczą). Kontrast memcpy i memmove implementacje pokazany tutaj. memcpy po prostu zapętla, podczas gdy memmove wykonuje test w celu określenia, w którym kierunku zapętlić, aby uniknąć uszkodzenia danych. Te implementacje są raczej proste. Większość wysokowydajnych implementacji jest bardziej skomplikowana (polega na kopiowaniu bloków rozmiaru słowa na raz, a nie bajtów).
źródło
memmove
wywołujememcpy
w jednej gałęzi po przetestowaniu kursory: student.cs.uwaterloo.ca/~cs350/common/os161-src-html/...memcpy
może być szybciej.Pamięć w
memcpy
nie może się nakładać lub ryzykujesz niezdefiniowane zachowanie, podczas gdy pamięć wmemmove
może się nakładać.Niektóre implementacje memcpy mogą nadal działać w przypadku nakładania się danych wejściowych, ale nie można policzyć tego zachowania. Podczas gdy memmove musi pozwalać na nakładanie się.
źródło
To, że
memcpy
nie ma do czynienia z nakładającymi się regionami, nie oznacza, że nie radzi sobie z nimi poprawnie. Wywołanie z nakładającymi się regionami powoduje niezdefiniowane zachowanie. Niezdefiniowane zachowanie może działać zgodnie z oczekiwaniami na jednej platformie; to nie znaczy, że jest poprawne lub ważne.źródło
memcpy
jest, że jest on zaimplementowany dokładnie w taki sam sposób, jakmemmove
. Oznacza to, że ktokolwiek napisał kompilator, nie zawracał sobie głowy pisaniem unikalnejmemcpy
funkcji.Zarówno memcpy, jak i memove robią podobne rzeczy.
Ale żeby zauważyć jedną różnicę:
daje:
źródło
Twoje demo nie ujawniło wad memcpy z powodu "złego" kompilatora, robi ci to przysługę w wersji Debug. Wydana wersja daje jednak te same wyniki, ale ze względu na optymalizację.
Rejestr
%eax
działa tutaj jako tymczasowy magazyn, który „elegancko” rozwiązuje problem nakładania się.Wada pojawia się przy kopiowaniu 6 bajtów, no cóż, przynajmniej jej części.
Wynik:
Wygląda dziwnie, jest to również spowodowane optymalizacją.
Dlatego zawsze wybieram
memmove
, próbując skopiować 2 nakładające się bloki pamięci.źródło
Różnica między
memcpy
imemmove
jest takaw
memmove
programie pamięć źródłowa o określonym rozmiarze jest kopiowana do bufora, a następnie przenoszona do miejsca docelowego. Więc jeśli pamięć się nakłada, nie ma żadnych skutków ubocznych.w przypadku
memcpy()
braku dodatkowego bufora dla pamięci źródłowej. Kopiowanie odbywa się bezpośrednio w pamięci, więc gdy pamięć się nakłada, otrzymujemy nieoczekiwane wyniki.Można to zaobserwować za pomocą następującego kodu:
Wynik to:
źródło
Jak już wskazano w innych odpowiedziach,
memmove
jest bardziej wyrafinowany niżmemcpy
taki, że odpowiada za nakładanie się pamięci. Wynik memmove jest definiowany tak, jakbysrc
plik został skopiowany do bufora, a następnie do buforadst
. NIE oznacza to, że rzeczywista implementacja używa jakiegokolwiek bufora, ale prawdopodobnie wykonuje jakąś arytmetykę wskaźników.źródło
kompilator może zoptymalizować memcpy, na przykład:
Ten memcpy można zoptymalizować jako:
x = *(int*)some_pointer;
źródło
int
dostęp bez wyrównania . Na niektórych architekturach (np. Cortex-M0) próba pobrania 32-bitowegoint
adresu z adresu, który nie jest wielokrotnością czterech, spowoduje awarię (alememcpy
zadziała). Jeśli ktoś będzie korzystał z procesora, który umożliwia dostęp bez wyrównania, lub używając kompilatora ze słowem kluczowym, które kieruje kompilator do składania liczb całkowitych z oddzielnie pobieranych bajtów, gdy jest to konieczne, można zrobić coś takiego, jak#define UNALIGNED __unaligned
`x = * (int UNALIGNED * ) some_pointer;char x = "12345"; int *i; i = *(int *)(x + 1);
wyrównania, ale niektóre tak, ponieważ naprawiają kopię podczas błędu. Pracowałem nad takim systemem i trochę czasu zajęło zrozumienie, dlaczego wydajność jest tak słaba.*(int *)some_pointer
ścisły naruszenie aliasing, ale prawdopodobnie oznacza, że kompilator będzie zespół, który kopie int wyjścieKod podany w linkach http://clc-wiki.net/wiki/memcpy dla memcpy wydaje mi się nieco dezorientujący, ponieważ nie daje takich samych wyników, gdy zaimplementowałem go za pomocą poniższego przykładu.
Wynik :
Ale teraz możesz zrozumieć, dlaczego memmove zajmie się nakładającym się problemem.
źródło
Wersja standardowa C11
C11 N1570 standardowy projekt mówi:
7.24.2.1 „Funkcja memcpy”:
7.24.2.2 „Funkcja memmove”:
Dlatego każde nakładanie się
memcpy
prowadzi do nieokreślonego zachowania i wszystko może się zdarzyć: złe, nic lub nawet dobre. Jednak dobre jest rzadkie :-)memmove
jednak wyraźnie mówi, że wszystko dzieje się tak, jakby używany był bufor pośredni, więc wyraźnie nakładanie się jest OK.C ++
std::copy
jest jednak bardziej wyrozumiały i pozwala na nakładanie się: Czy std :: copy obsługuje nakładające się zakresy?źródło
memmove
używa dodatkowej tymczasowej tablicy n, więc czy używa dodatkowej pamięci? Ale jak to możliwe, jeśli nie daliśmy mu dostępu do żadnej pamięci. (Wykorzystuje 2x więcej pamięci).Próbowałem uruchomić ten sam program za pomocą eclipse i pokazuje wyraźną różnicę między
memcpy
imemmove
.memcpy()
nie dba o pokrywających się z miejsca w pamięci, co powoduje uszkodzenie danych, podczas gdymemmove()
skopiuje dane do zmiennej tymczasowej, a następnie skopiować do aktualnej lokalizacji pamięci.Podczas próby skopiowania danych z lokalizacji
str1
dostr1+2
, wyjściememcpy
to „aaaaaa
”. Pytanie brzmi: jak?memcpy()
kopiuje po jednym bajcie od lewej do prawej. Jak pokazano w programie, "aabbcc
" kopiowanie będzie przebiegać jak poniżej,aabbcc -> aaabcc
aaabcc -> aaaacc
aaaacc -> aaaaac
aaaaac -> aaaaaa
memmove()
skopiuje dane najpierw do zmiennej tymczasowej, a następnie skopiuje do rzeczywistej lokalizacji pamięci.aabbcc(actual) -> aabbcc(temp)
aabbcc(temp) -> aaabcc(act)
aabbcc(temp) -> aaaacc(act)
aabbcc(temp) -> aaaabc(act)
aabbcc(temp) -> aaaabb(act)
Wyjście jest
memcpy
:aaaaaa
memmove
:aaaabb
źródło
memmove()
kopiuje do pośredniej lokalizacji. W razie potrzeby należy po prostu skopiować odwrotnie.