memcpy() против 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
12 ответов
Я не совсем удивлен, что ваш пример не демонстрирует странного поведения. Попробуйте скопировать 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 <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
Ваше демо не выявило недостатков 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 перекрывающихся блока памяти.
С11 стандартная тяга
Проект стандарта 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 перекрывающиеся диапазоны?
Разница между 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
Как уже указывалось в других ответах, 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 <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 позаботится о перекрывающихся проблемах.
Это пример, который я написал, пожалуйста, обратитесь к нему. Проблема перезаписи копии памяти возникает только в том случае, если область, в которой находится dst, находится за областью src.
#include "stdio.h"
#include "string.h"
#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0])
#define PRINT_ARRAY(arr, size, format) \
do { \
for (size_t i = 0; i < size; i++) { \
printf(format, arr[i]); \
} \
printf("\n"); \
} while (0)
int test_memcpy_memmove(void)
{
/* T1 src and dst memory do not overlap at all */
char s1[] = {'a', 'b', 'c', '\0'};
char s2[] = {'1', '2', '3', '\0'};
printf("T1--------------------------:\n");
printf("before s1 and s2:\n");
PRINT_ARRAY(s1, ARRAY_SIZE(s1), "%c");
PRINT_ARRAY(s2, ARRAY_SIZE(s1), "%c");
printf("after use memcpy s2:\n");
memcpy(s2, s1, 3);
PRINT_ARRAY(s2, ARRAY_SIZE(s1), "%c");
/* T2 src and dst memory overlap, and the area where dst is located is in front of the src area */
printf("T2--------------------------:\n");
char a[10] = {'a', 'b', 'c', 'd', 'e', 'f', '\0', '\0', '\0', '\0'};
char b[10] = {0};
memcpy(b, a, sizeof(char) * 10);
printf("before a and b:\n");
PRINT_ARRAY(a, ARRAY_SIZE(a), "%c");
PRINT_ARRAY(b, ARRAY_SIZE(b), "%c");
printf("after use memcpy a:\n");
memcpy(&a[0], &a[2], 3);
PRINT_ARRAY(a, ARRAY_SIZE(a), "%c"); // cdedef
printf("after use memmove b:\n");
memmove(&b[0], &b[2], 3);
PRINT_ARRAY(a, ARRAY_SIZE(a), "%c"); // cdedef
/* T3 !!! The memory of src and dst overlap, and the area of dst is behind the src area. */
printf("T3--------------------------:\n");
char c[10] = {'a', 'b', 'c', 'd', 'e', 'f', '\0', '\0', '\0', '\0'};
char d[10] = {0};
memcpy(d, c, sizeof(char) * 10);
printf("before c and d:\n");
PRINT_ARRAY(c, ARRAY_SIZE(c), "%c");
PRINT_ARRAY(d, ARRAY_SIZE(d), "%c");
printf("after use memcpy c:\n");
memcpy(&c[2], &c[0], 3);
PRINT_ARRAY(c, ARRAY_SIZE(c),
"%c"); // ababaf >>uses memcpy to overwrite the original characters expected to be copied
printf("after use memmove d:\n");
memmove(&d[2], &d[0], 3);
PRINT_ARRAY(d, ARRAY_SIZE(d),
"%c"); // ababcf >>uses memmove and does not overwrite the characters to be copied
return 0;
}
Я попытался запустить ту же программу, используя 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