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

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

Ответ 1

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

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

Ответ 2

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

Рассмотрим

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

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

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

Ответ 3

На странице memcpy

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

7.21.2.1 Функция memcpy

[...]

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

и

7.21.2.2 Функция memmove

[...]

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

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

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

Ответ 7

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

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 выше реализация 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.

Ответ 8

Я пробовал работать над кодом: Основная причина, по которой я хочу знать разницу 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

Ответ 9

char string [] = "stackoverflow";

char *first, *second;

first = string;

second = string;

puts(string);

o/p - stackoverflow

memcpy(first+5, first,7);

puts(first);

Здесь 7 символов, на которые указывает второй i.e "stackov", вставленный в +5, в результате

o/p - stackstackovw

memcpy(second+5, second, 7);

puts(second);

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

o/p - stackstackstw

0 ----- + 5
stack stackst w

Ответ 10

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

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

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

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


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

Ответ 11

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

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

Ответ 12

memmove, который заботится о наложении памяти в отличие от memset. но что память перекрывает в любом случае?

Предположим, у нас есть массив из 5 символов, где каждый символ имеет длину байта

+++++++++++++++++++++++++++++++
| 'a' | 'b' | 'c' | 'd' | 'e' |
+++++++++++++++++++++++++++++++
 0x100 0x101 0x102 0x103 0x104

теперь согласно man-странице memcpy, он принимает 3 аргумента, указатель на целевой блок памяти, указатель на исходный блок памяти и размер копируемых байтов.

Что если пункт назначения 0x102, источник 0x100 и размер 3? здесь происходит совпадение памяти. то есть 0x100 будет скопировано в 0x102, 0x101 будет скопировано в 0x103, а 0x102 будет скопировано в 0x104.

обратите внимание, что мы сначала скопировали в 0x102, а затем скопировали из 0x102, что означает, что значение, которое изначально было в 0x102, было потеряно, поскольку мы перезаписали его значением, которое мы скопировали в 0x102, прежде чем копировать из него. так что мы бы в конечном итоге что-то вроде

+++++++++++++++++++++++++++++++
| 'a' | 'b' | 'a' | 'b' | 'a' |
+++++++++++++++++++++++++++++++
 0x100 0x101 0x102 0x103 0x104


 instead of

+++++++++++++++++++++++++++++++
| 'a' | 'b' | 'a' | 'b' | 'c' |
+++++++++++++++++++++++++++++++
 0x100 0x101 0x102 0x103 0x104

как такая функция, как memmove, заботится о перекрытии памяти? в соответствии со своей man-страницей, он сначала копирует байты, которые должны быть скопированы во временный массив, а затем вставляет их в блок назначения в отличие от функции, такой как memcpy, которая копирует непосредственно из исходного блока в блок назначения.

Ответ 13

Техническая разница была хорошо документирована предыдущими ответами. Следует также отметить практические различия:

Memcpy потребуется примерно вдвое больше памяти для выполнения одной и той же задачи, но memmove может занимать значительно больше времени, чем memcpy.

Пример:

Скажем, у вас есть список из 100 предметов в памяти, принимающих 100 МБ памяти. Вы хотите сбросить 1-й элемент, чтобы у вас было 99.

Для получения нового списка из 99 предметов Memcpy потребуются оригинальные 100 МБ и дополнительные 99 МБ. Приблизительно 199 МБ для выполнения операции, но должны быть очень быстрыми.

Memmove, в худшем сценарии потребуются оригинальные 100 МБ и будет перемещать каждый пункт памяти 1 пункта одновременно. Для этого требуется только 100 МБ, но будет значительно медленнее, чем Memcpy.

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

- ИЗМЕНИТЬ, ЧТОБЫ ПОМОЧЬ ЗАВЕРШИТЬ МОЙ КАРПИЙСКИЙ ОТВЕТ -
Да, реализация memcpy() и memmove(), вероятно, не отличается в использовании памяти (я действительно не знаю), но то, как вы их используете, сильно повлияет на использование памяти вашей программой. Это то, что я подразумевал под практическими различиями memcpy() и memmove().

int SIZE = 100;
Item *ptr_item = (Item *) malloc(size_of(Item) * SIZE);
Item *ptr_src_item = ptr_item + 1;
Item *ptr_dst_item = ptr_item;
memmove(ptr_dst_item, ptr_src_item, SIZE - 1);

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

int SIZE = 100;
Item *ptr_src_item = (Item *) malloc(size_of(Item) * SIZE);
Item *ptr_dst_item = (Item *) malloc(size_of(Item) * (SIZE - 1));
memcpy(ptr_dst_item, ptr_src_item, SIZE - 1);

В этом втором блоке кода программа потребует в два раза больше памяти, чем первый блок. Однако этот второй блок должен выполняться значительно быстрее, чем первый блок, особенно по мере увеличения размера.

То, как я пытался объяснить практические различия в двух... но, возможно, я ошибаюсь и в этом?