В чем разница между memmove и memcpy? - PullRequest
106 голосов
/ 29 июля 2009

В чем разница между memmove и memcpy? Какой из них вы обычно используете и как?

Ответы [ 9 ]

148 голосов
/ 29 июля 2009

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

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

31 голосов
/ 29 июля 2009

memmove может обрабатывать перекрывающуюся память, memcpy не может.

Рассмотрим

char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up

Очевидно, что источник и пункт назначения теперь пересекаются, мы перезаписываем "-бар" с "баром". Это неопределенное поведение с использованием memcpy, если источник и назначение совпадают, поэтому в этом случае нам нужно memmove.

memmove(&str[3],&str[4],4); //fine
22 голосов
/ 29 июля 2009

Из справочной страницы memcpy .

Функция memcpy () копирует n байтов из области памяти src в область памяти Dest. Области памяти не должны перекрытия. Используйте memmove (3), если память области перекрываются.

13 голосов
/ 07 августа 2014

Основное различие между memmove() и memcpy() заключается в том, что в memmove() используется буфер - временная память - поэтому нет риска перекрытия. С другой стороны, memcpy() напрямую копирует данные из местоположения, на которое указывает источник , в местоположение, указанное пунктом назначения . (http://www.cplusplus.com/reference/cstring/memcpy/)

Рассмотрим следующие примеры:

  1. #include <stdio.h>
    #include <string.h>
    
    int main (void)
    {
        char string [] = "stackoverflow";
        char *first, *second;
        first = string;
        second = string;
    
        puts(string);
        memcpy(first+5, first, 5);
        puts(first);
        memmove(second+5, second, 5);
        puts(second);
        return 0;
    }
    

    Как вы и ожидали, это напечатает:

    stackoverflow
    stackstacklow
    stackstacklow
    
  2. Но в этом примере результаты не будут такими же:

    #include <stdio.h>
    #include <string.h>
    
    int main (void)
    {
        char string [] = "stackoverflow";
        char *third, *fourth;
        third = string;
        fourth = string;
    
        puts(string);
        memcpy(third+5, third, 7);
        puts(third);
        memmove(fourth+5, fourth, 7);
        puts(fourth);
        return 0;
    }
    

    Выход:

    stackoverflow
    stackstackovw
    stackstackstw
    

Это потому, что memcpy () делает следующее:

1.  stackoverflow
2.  stacksverflow
3.  stacksterflow
4.  stackstarflow
5.  stackstacflow
6.  stackstacklow
7.  stackstacksow
8.  stackstackstw
10 голосов
/ 29 июля 2009

Один обрабатывает перекрывающиеся пункты назначения, а другой нет.

6 голосов
/ 08 мая 2015

просто из стандарта ISO / IEC: 9899 это хорошо описано.

7.21.2.1 Функция memcpy

[...]

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

И

7.21.2.2 Функция memmove

[...]

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

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

В простом тексте memcpy() не позволяет перекрывать s1 и s2, тогда как memmove() делает.

4 голосов
/ 15 апреля 2018

Предполагая, что вам придется реализовать оба варианта, реализация может выглядеть так:

void memmove ( void * dst, const void * src, size_t count ) {
    if ((uintptr_t)src < (uintptr_t)dst) {
        // Copy from back to front

    } else if ((uintptr_t)dst < (uintptr_t)src) {
        // Copy from front to back
    }
}

void mempy ( void * dst, const void * src, size_t count ) {
    if ((uintptr_t)src != (uintptr_t)dst) {
        // Copy in any way you want
    }
}

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

например. если memcpy копирует "спереди назад" и блоки памяти выровнены как это

[---- src ----]
            [---- dst ---]

Затем копирование первого байта из src do dst уже уничтожает содержимое последних байтов src до того, как они были скопированы. Так что здесь можно копировать только «назад на фронт».

Теперь поменяйте местами src и dst:

[---- dst ----]
            [---- src ---]

В этом случае безопаснее копировать «спереди назад», поскольку копирование «спереди назад» уничтожит src около его фронта уже при копировании первого байта.

Возможно, вы заметили, что реализация memmove, описанная выше, даже не проверяет, действительно ли они перекрываются, она просто проверяет их относительные позиции, но только это сделает копию безопасной. Поскольку memcpy обычно использует самый быстрый способ копирования памяти в любой системе, memmove обычно реализуется как:

void memmove ( void * dst, const void * src, size_t count ) {
    if ((uintptr_t)src < (uintptr_t)dst
        && (uintptr_t)src + count > (uintptr_t)dst) {
        // Copy from back to front

    } else if ((uintptr_t)dst < (uintptr_t)src
        && (uintptr_t)dst + count > (uintptr_t)src
    ) {
        // Copy from front to back

    } else {
        // They don't overlap for sure
        memcpy(dst, src, count);
    }
}

Иногда, если memcpy всегда копирует «спереди назад» или «спереди назад», memmove может также использовать memcpy в одном из перекрывающихся случаев, но memcpy может даже копировать по-другому в зависимости от о том, как данные выровнены и / или сколько данных должно быть скопировано, поэтому даже если вы проверяли, как memcpy копирует в вашей системе, вы не можете полагаться на то, что результаты теста всегда будут правильными.

Что это значит для вас, когда вы решаете, кому позвонить?

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

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

0 голосов
/ 10 декабря 2016

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

Ссылка: https://www.youtube.com/watch?v=Yr1YnOVG-4g Доктор Джерри Кейн, (лекция по Стэнфордским интро системам - 7) Время: 36: 00

0 голосов
/ 30 мая 2016

Существует два очевидных способа реализации mempcpy(void *dest, const void *src, size_t n) (игнорируя возвращаемое значение):

  1. for (char *p=src, *q=dest;  n-->0;  ++p, ++q)
        *q=*p;
    
  2. char *p=src, *q=dest;
    while (n-->0)
        q[n]=p[n];
    

В первой реализации копирование выполняется с низкого на высокий адрес, а во втором - с высокого на низкий. Если копируемый диапазон перекрывается (как, например, в случае прокрутки буфера кадров), то будет правильным только одно направление работы, а другое будет перезаписывать местоположения, которые впоследствии будут считываться.

Реализация memmove(), в самом простом случае, проверит dest<src (некоторым зависящим от платформы способом) и выполнит соответствующее направление memcpy().

Код пользователя, разумеется, не может этого сделать, потому что даже после приведения src и dst к какому-либо конкретному типу указателя они (в общем) не указывают на один и тот же объект и поэтому не могут сравниваться , Но у стандартной библиотеки может быть достаточно знаний о платформе, чтобы выполнить такое сравнение, не вызывая неопределенного поведения.


Обратите внимание, что в реальной жизни реализации, как правило, значительно сложнее, чтобы получить максимальную производительность за счет более крупных передач (когда это позволяет выравнивание) и / или хорошего использования кэша данных. Приведенный выше код просто для того, чтобы сделать это как можно проще.

...