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


157

Я пытаюсь понять разницу между 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
В Microsoft CRT уже давно есть безопасный memcpy ().
Ганс Пассант

32
Я не думаю, что «безопасный» - правильное слово для этого. Безопасным memcpyбыло бы assertто, что регионы не перекрываются, а не намеренно скрывают ошибки в вашем коде.
R .. GitHub ОСТАНОВИТЬ ПОМОЩЬ ЛЬДУ

6
Зависит от того, имеете ли вы в виду «безопасный для разработчика» или «безопасный для конечного пользователя». Я бы сказал, что делать как сказано, даже если это не соответствует стандартам, является более безопасным выбором для конечного пользователя.
Кусма

поскольку glibc 2.19 - не работает The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen

Вы также можете увидеть здесь .
Рен

Ответы:


124

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

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

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

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

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


2
+1 Также, в следующей реализации, memmoveвызовы memcpyв одной ветке после тестирования указателей: student.cs.uwaterloo.ca/~cs350/common/os161-src-html/…
Паскаль Куок

Это звучит великолепно. Похоже, Visual Studio реализует «безопасный» memcpy (наряду с gcc 4.1.1, я также тестировал на RHEL 5). Написание версий этих функций из clc-wiki.net дает четкую картину. Спасибо.
user534785

3
memcpy не заботится о проблеме перекрытия, но это делает memmove. Тогда почему бы не удалить memcpy из библиотеки?
Олкотт

37
@ Олкотт: Потому что memcpyможет быть быстрее.
Билли ONEAL

Исправлена ​​ссылка на веб-архив от Паскаля Куока выше: web.archive.org/web/20130722203254/http://…
JWCS

95

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


3
это действительно помогло мне, спасибо! +1 для вашей информации
Muthu Ganapathy Натан

33

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


10
В частности, в зависимости от платформы возможно, что memcpyона реализована точно так же, как и memmove. То есть тот, кто написал компилятор, не удосужился написать уникальную memcpyфункцию.
Cam

19

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

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

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

}

дает:

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

ИМХО, в этом примере программы есть некоторые недостатки, поскольку доступ к буферу str1 осуществляется за пределами (10 байт для копирования, размер буфера составляет 7 байт). Ошибка выхода за границы приводит к неопределенному поведению. Различия в показанных результатах вызовов memcpy () / memmove () зависят от конкретной реализации. И пример вывода не совсем соответствует программе выше ... Кроме того, strcpy_s () не является частью стандартного C AFAIK (для MS, см. Также: stackoverflow.com/questions/36723946/… ) - Пожалуйста, исправьте меня, если я я не прав
отн

7

Ваша демоверсия не выявила недостатков 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

Разница между 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

6
-1 - для memmove не требуется фактически копировать данные в отдельный буфер
jjwchoy

этот пример не помогает понять концепцию .... так как большинство компиляторов выдает то же, что и вывод mem mem
Jasdeep Singh Arora

1
@jjwchoy Концептуально это делает. Буфер обычно оптимизируют
MM

Тот же результат, на Linux.
CodyChan

2

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


1

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

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

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


3
Такая оптимизация допустима только на архитектурах, которые допускают выравниваемый intдоступ. На некоторых архитектурах (например, Cortex-M0) попытка получить 32-битный intадрес, который не кратен четырем, вызовет сбой (но memcpyбудет работать). Если кто-либо будет использовать ЦП, который разрешает не выровненный доступ, или использовать компилятор с ключевым словом, которое указывает компилятору собирать целые числа из отдельно выбранных байтов, когда это необходимо, можно сделать что-то подобное, #define UNALIGNED __unalignedа затем `x = * (int UNALIGNED * ) some_pointer;
суперкат

2
Некоторые процессоры не допускают сбой доступа к unaligned, char x = "12345"; int *i; i = *(int *)(x + 1);но некоторые делают, потому что они исправляют копию во время сбоя. Я работал над такой системой, и потребовалось немного времени, чтобы понять, почему производительность такая низкая.
user3431262

*(int *)some_pointerэто строгое нарушение псевдонимов, но вы, вероятно, имеете в виду, что компилятор будет выводить сборку, которая копирует int
MM

1

Код, приведенный в ссылках 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

С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 перекрывающиеся диапазоны?


memmoveиспользовать дополнительный временный массив n, поэтому он использует дополнительную память? Но как это возможно, если мы не дали ему доступ к какой-либо памяти. (Используется в 2 раза больше памяти).
clmno

@clmno он распределяет по стеку или malloc, как и любая другая функция, которую я ожидаю :-)
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

1
Я задал вопрос здесь , тоже получил хороший ответ. Спасибо. Увидел ваш пост на hackernews, который стал вирусным (x86) :)
clmno

-4

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

При попытке скопировать данные из местоположения str1в str1+2, вывод memcpy" aaaaaa". Вопрос будет как? memcpy()будет копировать один байт за раз слева направо. Как показано в вашей программе " 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


2
Добро пожаловать в переполнение стека. Пожалуйста, прочитайте страницу О скоро. Существуют различные проблемы для решения. Прежде всего, вы добавили ответ на вопрос с несколькими ответами от 18 месяцев или около того назад. Чтобы гарантировать добавление, вам нужно будет предоставить поразительную новую информацию. Во-вторых, вы указываете Eclipse, но Eclipse - это IDE, использующая компилятор C, но вы не определяете платформу, на которой работает ваш код, или компилятор C, который использует Eclipse. Мне было бы интересно узнать, как вы узнаете, что memmove()копирует в промежуточное местоположение. Он должен просто копировать в обратном порядке, когда это необходимо.
Джонатан Леффлер

Спасибо. О компиляторе, поэтому я использую компилятор gcc на Linux. В linux есть справочная страница для memove, в которой четко указано, что memove будет копировать данные во временную переменную, чтобы избежать их наложения. Вот ссылка на эту справочную
Пратик

3
Это на самом деле говорит «как будто», что не означает, что это то, что на самом деле происходит. Конечно, он мог бы сделать это таким образом (хотя возникнут вопросы о том, откуда у него запасная память), но я был бы более чем удивлен, если бы это было именно так. Если исходный адрес больше целевого, достаточно скопировать его с начала до конца (прямое копирование); если исходный адрес меньше целевого адреса, его достаточно скопировать с конца на начало (обратное копирование). Вспомогательная память не нужна или не используется.
Джонатан Леффлер

попытайтесь объяснить свой ответ с фактическими данными в коде, это было бы более полезно.
HaseeB Мир,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.