memcpy () против memmove () - PullRequest
       98

memcpy () против memmove ()

146 голосов
/ 11 декабря 2010

Я пытаюсь понять разницу между 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 <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 );
}

Выход:

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

Ответы [ 11 ]

113 голосов
/ 11 декабря 2010

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

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

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

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

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

87 голосов
/ 26 июля 2011

Память в 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 должен допускать перекрытие.

32 голосов
/ 11 декабря 2010

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

16 голосов
/ 11 декабря 2010

И memcpy, и memove делают схожие вещи.

Но чтобы заметить одно отличие:

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

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
7 голосов
/ 07 ноября 2013

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

    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 перекрывающихся блока памяти.

3 голосов
/ 25 июля 2013

Разница между 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
2 голосов
/ 20 апреля 2016

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

1 голос

C11 стандартная тяга

В C11 N1570 стандартная черновая версия говорит:

7.24.2.1 «Функция memcpy»:

2 Функция memcpy копирует n символов из объекта, на который указывает s2, в объект, на который указывает s1. Если копирование происходит между объектами, которые перекрываются, поведение не определено.

7.24.2.2 «Функция memmove»:

2 Функция memmove копирует n символов из объекта, на который указывает s2, в объект, на который указывает s1. Копирование происходит так, как будто n символов из объекта указанные s2 сначала копируются во временный массив из n символов, который не перекрывают объекты, на которые указывают s1 и s2, а затем n символов из временный массив копируется в объект, на который указывает s1

Следовательно, любое совпадение memcpy приводит к неопределенному поведению, и может произойти все что угодно: плохое, ничто или даже хорошее. Хорошо это редко, хотя: -)

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

C ++ std::copy более простителен и допускает перекрытия: Обрабатывает ли std :: copy диапазоны перекрытия?

1 голос
/ 30 марта 2014

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

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

Выход:

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

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

1 голос
/ 21 июня 2013

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

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

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

...