Вопрос

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

Это было полезно?

Решение

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

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

Другие советы

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

Рассмотрим

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

Очевидно, что источник и назначение теперь перекрываются, мы перезаписываем Quot & & -бар Quot; с " bar " ;. Это неопределенное поведение с использованием <=>, если источник и назначение совпадают, поэтому в этом случае нам нужно <=>.

memmove(&str[3],&str[4],4); //fine

Со страницы memcpy man.

  

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

Основное различие между 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

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

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

7.21.2.1 Функция memcpy

[...]

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

И

7.21.2.2 Функция memmove

[...]

Копирование происходит Как будто N -символы из объекта, на который указано S2, сначала скопируются во временный массив N -символов, которые не перекрываются Объекты, на которые указывает S1 и S2, а затем N -символы из временного массива копируются в объект, на который указывается S1.

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

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

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

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 к 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.

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

<Ол>
  • for (char *p=src, *q=dest;  n-->0;  ++p, ++q)
        *q=*p;
    
  • char *p=src, *q=dest;
    while (n-->0)
        q[n]=p[n];
    
  • В первой реализации копирование выполняется с низкого на высокий адрес, а во второй - с высокого на низкий. Если копируемый диапазон перекрывается (например, в случае прокрутки буфера кадров), то только одно направление операции является правильным, а другое будет перезаписывать местоположения, которые впоследствии будут считываться.

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

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

    <Ч>

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

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top