В чем разница между memmove и memcpy?


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

11 94

11 ответов:

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

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

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

считают

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

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

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

с memcpy man page.

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

основное различие между memmove() и memcpy(), что в memmove() a буфер - временная память-используется, поэтому нет риска перекрытия. С другой стороны, 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

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

просто от стандарта ИСО / ИЭК: 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() делает.

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

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.

Я пробовал работать над кодом: основная причина, по которой я хочу знать разницу memcpy и memmove.

#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

Теперь Я заменил memmove С memcpy и я получил тот же вывод

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

    puts(first);

    memcpy(second+5, second, 7);

    puts(second);

    return 0;
}

выход:

stackoverflow
stackstackovw
stackstackstw

char string [] = "stackoverflow";

char *first, *second;

first = string;

second = string;

puts(string);

o / p - stackoverflow

memcpy(first+5, first,7);

puts(first);

здесь 7 символов, указанных вторым т. е. "stackov" вставляется в +5 позиции в результате

o / p - stackstackovw

memcpy(second+5, second, 7);

puts(second);

здесь входная строка- "stackstackovw", 7 символов, указываемых секундой (т. е." stackst"), копируется в буфер, а затем вставляется в место +5, что приводит к

o/p-stackstackstw

0-----+5
stack stackst Вт

есть два очевидных способа реализации 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];
    

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

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

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


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

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

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