memcpy () vs memmove ()

Я пытаюсь понять разницу между memcpy() и memmove() , и я прочитал текст, что memcpy() не заботится о перекрывающемся источнике и получателе, в то время как memmove() делает.

Однако, когда я выполняю эти две функции на перекрывающихся блоках памяти, они оба дают одинаковый результат. Например, возьмите следующий пример MSDN на memmove() : –

Есть ли лучший пример для понимания недостатков memcpy и того, как memmove решает его?

 // crt_memcpy.c // Illustrate overlapping copy: memmove always handles it correctly; memcpy may handle // it correctly. #include  #include  #include  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 ); } 

Вывод:

 The string: aabbcc New string: aaaabb The string: aabbcc New string: aaaabb 

    Я не совсем удивлен, что ваш пример не показывает странного поведения. Попробуйте скопировать str1 на str1+2 и посмотреть, что произойдет потом. (На самом деле это не имеет значения, зависит от компилятора / библиотек.)

    В общем случае memcpy реализуется простым (но быстрым) способом. Упрощенно он просто перебирает данные (по порядку), копируя их из одного места в другое. Это может привести к тому, что источник будет перезаписан во время чтения.

    Memmove делает больше работы, чтобы обеспечить правильное перекрытие перекрытия.

    РЕДАКТИРОВАТЬ:

    (К сожалению, я не могу найти достойные примеры, но они будут делать). Контрастируйте приведенные здесь примеры memcpy и memmove . memcpy, а memmove выполняет проверку, чтобы определить, в каком направлении нужно входить, чтобы избежать искажения данных. Эти реализации довольно просты. Большинство высокопроизводительных реализаций более сложны (включая копирование блоков размера слова за раз, а не байтов).

    Память в memcpy не может перекрываться или вы рискуете неопределенным поведением, а память в memmove может перекрываться.

     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. 

    Некоторые реализации memcpy могут по-прежнему работать для перекрывающихся входов, но вы не можете рассчитывать на это поведение. Хотя memmove должен допускать перекрытие.

    Просто потому, что memcpy не имеет дело с перекрывающимися областями, не означает, что он не справляется с ними правильно. Вызов с перекрывающимися областями вызывает неопределенное поведение. Неопределенное поведение может работать полностью, как вы ожидаете на одной платформе; это не значит, что это правильно или действительно.

    И memcpy, и memove выполняют аналогичные действия.

    Но посмотреть на одну разницу:

     #include  #include  #include  char str1[17] = "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( "The string: %s\n", str1 ); memmove( (str1+6), str1, 10 ); printf( "New string: %s\n", str1 ); } 

    дает:

     The string: abcdef New string: abcdefabcdefabcd The string: abcdef New string: abcdefabcdef 

    Ваша демонстрация не выявила недостатки memcpy из-за «плохого» компилятора, это делает вам одолжение в версии Debug. Однако версия выпуска дает вам тот же результат, но из-за оптимизации.

      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 

    Регистр %eax здесь играет как временное хранилище, которое «элегантно» исправляет проблему перекрытия.

    Недостаток возникает при копировании 6 байтов, ну, по крайней мере, его части.

     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); } 

    Вывод:

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

    Выглядит странно, это вызвано оптимизацией.

      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 

    Вот почему я всегда выбираю memmove при попытке скопировать 2 перекрытых блока памяти.

    Разница между memcpy и memmove заключается в том, что

    1. в memmove исходная память указанного размера копируется в буфер, а затем перемещается в пункт назначения. Поэтому, если память перекрывается, побочных эффектов нет.

    2. в случае memcpy() дополнительный буфер для исходной памяти отсутствует. Копирование выполняется непосредственно в памяти, так что когда происходит перекрытие памяти, мы получаем неожиданные результаты.

    Их можно наблюдать по следующему коду:

     //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); } 

    Выход:

     hare hare rama hare rama hare hare hare hare hare hare rama hare rama 

    Как уже указывалось в других ответах, memmove более memmove , чем memcpy , так что он учитывает перекрытия памяти. Результат memmove определяется так, как если бы src был скопирован в буфер, а затем буфер скопирован в dst . Это НЕ означает, что фактическая реализация использует любой буфер, но, вероятно, делает некоторую арифметику указателя.

    компилятор может оптимизировать memcpy, например:

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

    Этот memcpy может быть оптимизирован как: x = *(int*)some_pointer;

    Код, приведенный в ссылках http://clc-wiki.net/wiki/memcpy для memcpy, кажется, немного путает меня, поскольку он не дает того же результата, когда я его реализовал, используя приведенный ниже пример.

     #include  #include  #include  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(); } в #include  #include  #include  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(); } , #include  #include  #include  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(); } 

    Вывод :

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

    Но теперь вы можете понять, почему memmove позаботится о перекрывающейся проблеме.

    Я попытался запустить ту же самую программу, используя eclipse, и это показывает явное различие между memcpy и memmove . memcpy() не заботится о перекрытии расположения памяти, что приводит к повреждению данных, а memmove() сначала копирует данные во временную переменную, а затем копирует их в фактическую ячейку памяти.

    При попытке скопировать данные из местоположения str1 в str1+2 , вывод memcpy – « aaaaaa ». Вопрос будет таким? memcpy() будет копировать один байт за один раз слева направо. Как показано в вашей программе « aabbcc », все aabbcc будут выполняться, как показано ниже,

    1. aabbcc -> aaabcc

    2. aaabcc -> aaaacc

    3. aaaacc -> aaaaac

    4. aaaaac -> aaaaaa

    memmove() сначала скопирует данные во временную переменную, а затем скопирует их в фактическую ячейку памяти.

    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)

    Выход

    memcpy : aaaaaa

    memmove : aaaabb

    Давайте будем гением компьютера.