memcpy () vs memmove ()

157

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 memcpyi jak memmoveje 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
user534785
źródło
1
Microsoft CRT od dłuższego czasu ma bezpieczną memcpy ().
Hans Passant
32
Myślę, że „bezpieczny” nie jest odpowiednim określeniem. Bezpiecznie memcpybyłoby assert, gdyby regiony nie zachodziły na siebie, a nie celowo ukrywały błędy w kodzie.
R .. GitHub PRZESTAŃ POMÓC W LODZIE
6
Zależy od tego, czy masz na myśli „bezpieczny dla programisty” czy „bezpieczny dla użytkownika końcowego”. Twierdziłbym, że postępowanie zgodnie z zaleceniami, nawet jeśli nie jest zgodne ze standardami, jest bezpieczniejszym wyborem dla użytkownika końcowego.
kusma
od glibc 2.19 - nie działa The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen
Możesz również zobaczyć tutaj .
Ren

Odpowiedzi:

124

Nie jestem całkowicie zaskoczony, że twój przykład nie wykazuje dziwnego zachowania. Zamiast tego spróbuj skopiować str1do str1+2i 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).

rozwojowa niemoc
źródło
2
+1 Także w poniższym realizacji, memmovewywołuje memcpyw jednej gałęzi po przetestowaniu kursory: student.cs.uwaterloo.ca/~cs350/common/os161-src-html/...
Pascal Cuoq
To brzmi świetnie. Wygląda na to, że Visual Studio implementuje "bezpieczny" memcpy (razem z gcc 4.1.1, testowałem również na RHEL 5). Napisanie wersji tych funkcji z witryny clc-wiki.net daje jasny obraz. Dzięki.
user534785
3
memcpy nie zajmuje się problemem nakładania się, ale memmove tak. Dlaczego więc nie wyeliminować memcpy z biblioteki?
Alcott,
37
@Alcott: Ponieważ memcpymoże być szybciej.
Billy ONeal
Naprawiono / webarchive link od Pascala Cuoq powyżej: web.archive.org/web/20130722203254/http://…
JWCS
94

Pamięć w memcpy nie może się nakładać lub ryzykujesz niezdefiniowane zachowanie, podczas gdy pamięć w memmovemoże się nakładać.

char a[16];
char b[16];

memcpy(a,b,16);           // valid
memmove(a,b,16);          // Also valid, but slower than memcpy.
memcpy(&a[0], &a[1],10);  // Not valid since it overlaps.
memmove(&a[0], &a[1],10); // valid. 

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ę.

rxantos
źródło
3
To naprawdę pomogło mi dzięki! +1 dla twojej informacji
Muthu Ganapathy Nathan
33

To, że memcpynie 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.

Billy ONeal
źródło
10
W szczególności, w zależności od platformy, możliwe memcpyjest, że jest on zaimplementowany dokładnie w taki sam sposób, jak memmove. Oznacza to, że ktokolwiek napisał kompilator, nie zawracał sobie głowy pisaniem unikalnej memcpyfunkcji.
Cam
19

Zarówno memcpy, jak i memove robią podobne rzeczy.

Ale żeby zauważyć jedną różnicę:

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[7] = "abcdef";

int main()
{

   printf( "The string: %s\n", str1 );
   memcpy( (str1+6), str1, 10 );
   printf( "New string: %s\n", str1 );

   strcpy_s( str1, sizeof(str1), "aabbcc" );   // reset string


   printf("\nstr1: %s\n", str1);
   printf( "The string: %s\n", str1 );
   memmove( (str1+6), str1, 10 );
   printf( "New string: %s\n", str1 );

}

daje:

The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef
Neilvert Noval
źródło
IMHO, ten przykładowy program ma pewne wady, ponieważ bufor str1 jest dostępny poza granicami (10 bajtów do skopiowania, bufor ma rozmiar 7 bajtów). Błąd poza granicami powoduje niezdefiniowane zachowanie. Różnice w wyświetlanych wynikach wywołań memcpy () / memmove () są specyficzne dla implementacji. A przykładowe wyjście nie pasuje dokładnie do powyższego programu ... Ponadto, strcpy_s () nie jest częścią standardowego C AFAIK (specyficzne dla MS, zobacz także: stackoverflow.com/questions/36723946/ ... ) - Popraw mnie, jeśli ja 'Mylę.
rel
7

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ę.

    memcpy(str1 + 2, str1, 4);
00241013  mov         eax,dword ptr [str1 (243018h)]  // load 4 bytes from source string
    printf("New string: %s\n", str1);
00241018  push        offset str1 (243018h) 
0024101D  push        offset string "New string: %s\n" (242104h) 
00241022  mov         dword ptr [str1+2 (24301Ah)],eax  // put 4 bytes to destination
00241027  call        esi  

Rejestr %eaxdział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.

char str1[9] = "aabbccdd";

int main( void )
{
    printf("The string: %s\n", str1);
    memcpy(str1 + 2, str1, 6);
    printf("New string: %s\n", str1);

    strcpy_s(str1, sizeof(str1), "aabbccdd");   // reset string

    printf("The string: %s\n", str1);
    memmove(str1 + 2, str1, 6);
    printf("New string: %s\n", str1);
}

Wynik:

The string: aabbccdd
New string: aaaabbbb
The string: aabbccdd
New string: aaaabbcc

Wygląda dziwnie, jest to również spowodowane optymalizacją.

    memcpy(str1 + 2, str1, 6);
00341013  mov         eax,dword ptr [str1 (343018h)] 
00341018  mov         dword ptr [str1+2 (34301Ah)],eax // put 4 bytes to destination, earlier than the above example
0034101D  mov         cx,word ptr [str1+4 (34301Ch)]  // HA, new register! Holding a word, which is exactly the left 2 bytes (after 4 bytes loaded to %eax)
    printf("New string: %s\n", str1);
00341024  push        offset str1 (343018h) 
00341029  push        offset string "New string: %s\n" (342104h) 
0034102E  mov         word ptr [str1+6 (34301Eh)],cx  // Again, pulling the stored word back from the new register
00341035  call        esi  

Dlatego zawsze wybieram memmove, próbując skopiować 2 nakładające się bloki pamięci.

huubby
źródło
3

Różnica między memcpyi memmovejest taka

  1. w memmoveprogramie 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.

  2. 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:

//include string.h, stdio.h, stdlib.h
int main(){
  char a[]="hare rama hare rama";

  char b[]="hare rama hare rama";

  memmove(a+5,a,20);
  puts(a);

  memcpy(b+5,b,20);
  puts(b);
}

Wynik to:

hare hare rama hare rama
hare hare hare hare hare hare rama hare rama
R srinivas reddy
źródło
6
-1 - nie ma wymogu, aby memmove faktycznie kopiowało dane do oddzielnego bufora
jjwchoy
ten przykład nie pomaga w zrozumieniu koncepcji ... ponieważ większość kompilatorów da to samo, co wyjście mem move
Jasdeep Singh Arora
1
@jjwchoy Koncepcyjnie tak. Bufor byłby zwykle optymalizowany na zewnątrz
MM
Ten sam wynik w systemie Linux.
CodyChan
2

Jak już wskazano w innych odpowiedziach, memmovejest bardziej wyrafinowany niż memcpytaki, że odpowiada za nakładanie się pamięci. Wynik memmove jest definiowany tak, jakby srcplik został skopiowany do bufora, a następnie do bufora dst. NIE oznacza to, że rzeczywista implementacja używa jakiegokolwiek bufora, ale prawdopodobnie wykonuje jakąś arytmetykę wskaźników.

Огњен Шобајић
źródło
1

kompilator może zoptymalizować memcpy, na przykład:

int x;
memcpy(&x, some_pointer, sizeof(int));

Ten memcpy można zoptymalizować jako: x = *(int*)some_pointer;

rakieta
źródło
3
Taka optymalizacja jest dopuszczalna tylko na architekturach, które pozwalają na intdostęp bez wyrównania . Na niektórych architekturach (np. Cortex-M0) próba pobrania 32-bitowego intadresu z adresu, który nie jest wielokrotnością czterech, spowoduje awarię (ale memcpyzadział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;
supercat
2
Niektóre procesory nie pozwalają na awarię dostępu bez 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.
user3431262
*(int *)some_pointerścisły naruszenie aliasing, ale prawdopodobnie oznacza, że kompilator będzie zespół, który kopie int wyjście
MM
1

Kod 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.

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[11] = "abcdefghij";

void *memcpyCustom(void *dest, const void *src, size_t n)
{
    char *dp = (char *)dest;
    const char *sp = (char *)src;
    while (n--)
        *dp++ = *sp++;
    return dest;
}

void *memmoveCustom(void *dest, const void *src, size_t n)
{
    unsigned char *pd = (unsigned char *)dest;
    const unsigned char *ps = (unsigned char *)src;
    if ( ps < pd )
        for (pd += n, ps += n; n--;)
            *--pd = *--ps;
    else
        while(n--)
            *pd++ = *ps++;
    return dest;
}

int main( void )
{
    printf( "The string: %s\n", str1 );
    memcpy( str1 + 1, str1, 9 );
    printf( "Actual memcpy output: %s\n", str1 );

    strcpy_s( str1, sizeof(str1), "abcdefghij" );   // reset string

    memcpyCustom( str1 + 1, str1, 9 );
    printf( "Implemented memcpy output: %s\n", str1 );

    strcpy_s( str1, sizeof(str1), "abcdefghij" );   // reset string

    memmoveCustom( str1 + 1, str1, 9 );
    printf( "Implemented memmove output: %s\n", str1 );
    getchar();
}

Wynik :

The string: abcdefghij
Actual memcpy output: aabcdefghi
Implemented memcpy output: aaaaaaaaaa
Implemented memmove output: aabcdefghi

Ale teraz możesz zrozumieć, dlaczego memmove zajmie się nakładającym się problemem.

śpiewanieh
źródło
1

Wersja standardowa C11

C11 N1570 standardowy projekt mówi:

7.24.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.

7.24.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

Dlatego każde nakładanie się memcpyprowadzi 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::copyjest jednak bardziej wyrozumiały i pozwala na nakładanie się: Czy std :: copy obsługuje nakładające się zakresy?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
memmoveuż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).
clmno
@clmno alokuje na stosie lub malloc jak każda inna funkcja, której oczekiwałbym :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Zadałem tutaj pytanie , też dostałem dobrą odpowiedź. Dziękuję Ci. Widziałem twój post hackernews, który stał się wirusowy (ten x86) :)
clmno
-4

Próbowałem uruchomić ten sam program za pomocą eclipse i pokazuje wyraźną różnicę między memcpyi memmove. memcpy()nie dba o pokrywających się z miejsca w pamięci, co powoduje uszkodzenie danych, podczas gdy memmove()skopiuje dane do zmiennej tymczasowej, a następnie skopiować do aktualnej lokalizacji pamięci.

Podczas próby skopiowania danych z lokalizacji str1do str1+2, wyjście memcpyto „ 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,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() skopiuje dane najpierw do zmiennej tymczasowej, a następnie skopiuje do rzeczywistej lokalizacji pamięci.

  1. aabbcc(actual) -> aabbcc(temp)

  2. aabbcc(temp) -> aaabcc(act)

  3. aabbcc(temp) -> aaaacc(act)

  4. aabbcc(temp) -> aaaabc(act)

  5. aabbcc(temp) -> aaaabb(act)

Wyjście jest

memcpy : aaaaaa

memmove : aaaabb

Pratik Panchal
źródło
2
Witamy w Stack Overflow. Przeczytaj wkrótce stronę Informacje . Jest wiele kwestii, którymi należy się zająć. Przede wszystkim dodałeś odpowiedź na pytanie z wieloma odpowiedziami sprzed 18 miesięcy. Aby uzasadnić dodanie, musisz podać zaskakujące nowe informacje. Po drugie, określasz Eclipse, ale Eclipse to IDE, które używa kompilatora C, ale nie identyfikujesz platformy, na której działa twój kod, ani kompilatora C, którego używa Eclipse. Chciałbym wiedzieć, jak ustalasz, że memmove()kopiuje do pośredniej lokalizacji. W razie potrzeby należy po prostu skopiować odwrotnie.
Jonathan Leffler
Dzięki. O kompilatorze, więc używam kompilatora gcc w systemie Linux. W Linuksie istnieje strona podręcznika dla memove, która jasno określa, że ​​memove będzie kopiować dane do zmiennej tymczasowej, aby uniknąć nakładania się danych. Oto link do tej strony podręcznika
Pratik Panchal
3
W rzeczywistości mówi „jak gdyby”, co nie oznacza, że ​​tak się dzieje. Przyznaję, że faktycznie mógłby to zrobić w ten sposób (choć pojawiałyby się pytania o to, skąd bierze zapasową pamięć), ale byłbym bardziej niż trochę zaskoczony, gdyby tak właśnie było. Jeśli adres źródłowy jest większy niż adres docelowy, wystarczy skopiować od początku do końca (kopia do przodu); jeśli adres źródłowy jest mniejszy niż adres docelowy, wystarczy skopiować od końca do początku (kopia wsteczna). Żadna dodatkowa pamięć nie jest potrzebna ani używana.
Jonathan Leffler
spróbuj wyjaśnić swoją odpowiedź rzeczywistymi danymi w kodzie, które byłyby bardziej pomocne.
HaseeB Mir