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 <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
Ответы
Ответ 1
Я не совсем удивлен тем, что ваш пример не показывает странного поведения. Попробуйте скопировать str1
в str1+2
и посмотреть, что произойдет потом. (На самом деле это не влияет, зависит от компилятора/библиотек).
В общем случае memcpy реализуется простым (но быстрым образом). Упрощенно он просто перебирает данные (по порядку), копируя их из одного места в другое. Это может привести к тому, что источник будет перезаписан во время чтения.
Memmove делает больше работы для обеспечения правильной обработки перекрытия.
EDIT:
(к сожалению, я не могу найти достойные примеры, но они будут делать). Контраст memcpy и memmove реализации, показанные здесь, memcpy, а memmove выполняет проверку, чтобы определить, в каком направлении нужно входить, чтобы избежать искажения данных. Эти реализации довольно просты. Большинство высокопроизводительных реализаций более сложны (включая копирование блоков размера слова за раз, а не байтов).
Ответ 2
Память в memcpy не может перекрываться или вы рискуете поведением undefined, тогда как память в 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 должен допускать перекрытие.
Ответ 3
Просто потому, что memcpy
не нужно иметь дело с перекрывающимися областями, не означает, что он не справляется с ними правильно. Вызов с перекрывающимися областями вызывает поведение undefined. undefined поведение может работать полностью, как вы ожидаете на одной платформе; это не означает, что это правильно или действительно.
Ответ 4
Оба 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
Ответ 5
Ваша демонстрация не выявила недостатки 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 перекрытых блока памяти.
Ответ 6
Разница между memcpy
и memmove
заключается в том, что
-
в memmove
, исходная память указанного размера копируется в буфер и затем перемещается в пункт назначения. Поэтому, если память перекрывается, побочных эффектов нет.
-
в случае 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
Ответ 7
Как уже указывалось в других ответах, memmove
более сложный, чем memcpy
, так что он учитывает перекрытия памяти. Результат memmove определяется так, как если бы src
был скопирован в буфер и затем скопирован в dst
. Это НЕ означает, что фактическая реализация использует любой буфер, но, вероятно, делает некоторую арифметику указателя.
Ответ 8
компилятор может оптимизировать memcpy, например:
int x;
memcpy(&x, some_pointer, sizeof(int));
Эта memcpy может быть оптимизирована как: x = *(int*)some_pointer;
Ответ 9
Код, приведенный в ссылках 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 будет заботиться о перекрывающейся проблеме.
Ответ 10
Я попытался запустить ту же программу с использованием eclipse, и она показывает явное различие между memcpy
и memmove
. memcpy()
не заботится о перекрытии расположения памяти, что приводит к повреждению данных, а memmove()
сначала копирует данные во временную переменную, а затем копирует их в фактическую ячейку памяти.
При попытке скопировать данные из местоположения str1
в str1+2
вывод memcpy
равен "aaaaaa
". Вопрос будет таким?
memcpy()
будет копировать один байт за один раз слева направо. Как показано в вашей программе "aabbcc
",
все копии будут выполняться, как показано ниже,
-
aabbcc -> aaabcc
-
aaabcc -> aaaacc
-
aaaacc -> aaaaac
-
aaaaac -> aaaaaa
memmove()
сначала скопирует данные во временную переменную, а затем скопирует их в фактическую ячейку памяти.
-
aabbcc(actual) -> aabbcc(temp)
-
aabbcc(temp) -> aaabcc(act)
-
aabbcc(temp) -> aaaacc(act)
-
aabbcc(temp) -> aaaabc(act)
-
aabbcc(temp) -> aaaabb(act)
Выход
memcpy
: aaaaaa
memmove
: aaaabb