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

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


person Community    schedule 29.07.2009    source источник
comment
Обратите внимание на проблемы, которые могут возникнуть: lwn.net/Articles/414467   -  person Zan Lynx    schedule 22.04.2013


Ответы (9)


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

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

person bdonlan    schedule 29.07.2009
comment
при использовании memcpy, как я могу гарантировать, что адреса src и dest не перекрываются? Должен ли я лично убедиться, что src и dest не перекрываются? - person Alcott; 09.09.2011
comment
@Alcott, не используйте memcpy, если вы не знаете, что они не перекрываются - вместо этого используйте memmove. Когда нет перекрытия, memmove и memcpy эквивалентны (хотя memcpy может быть очень, очень, очень немного быстрее). - person bdonlan; 10.09.2011
comment
Вы можете использовать ключевое слово restrict, если вы работаете с длинными массивами и хотите защитить процесс копирования. Например, если ваш метод принимает в качестве параметров массивы ввода и вывода, и вы должны убедиться, что пользователь не передает тот же адрес, что и ввод и вывод. Подробнее читайте здесь stackoverflow .com / questions / 776283 / - person DanielHsH; 01.01.2015
comment
@DanielHsH 'restrict' - это обещание, которое вы дадите компилятору; это не принудительно компилятором. Если вы поместите параметр «ограничить» в свои аргументы и на самом деле имеют перекрытие (или, в более общем смысле, доступ к ограниченным данным из указателя, полученного из нескольких мест), поведение программы будет неопределенным, возникнут странные ошибки и компилятор обычно не предупреждает об этом. - person bdonlan; 09.02.2015
comment
@bdonlan Это не просто обещание компилятору, это требование для вызывающей стороны. Это требование не соблюдается, но если вы его нарушите, вы не сможете пожаловаться, если получите неожиданный результат. Нарушение требования является неопределенным поведением, как и i = i++ + 1 неопределенным; компилятор не запрещает вам писать именно этот код, но результатом этой инструкции может быть что угодно, и разные компиляторы или процессоры будут показывать здесь разные значения. - person Mecki; 15.04.2018
comment
Глядя на эти комментарии, кажется, что всегда используйте memmove. - person AlphaGoku; 23.07.2019

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

Учитывать

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

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

memmove(&str[3],&str[4],4); //fine
person nos    schedule 29.07.2009
comment
@ultraman: Потому что это МОЖЕТ быть реализовано с использованием ассемблера низкого уровня, который требует, чтобы память не перекрывалась. Если это так, вы можете, например, сгенерировать сигнал или аппаратное исключение для процессора, который прерывает приложение. В документации указано, что он не обрабатывает условие, но в стандарте не указано, что произойдет, когда эти условия будут выполнены (это известно как неопределенное поведение). Неопределенное поведение может делать что угодно. - person Martin York; 29.07.2009
comment
с gcc 4.8.2, даже memcpy также принимает перекрывающиеся указатели источника и назначения и работает нормально. - person GeekyJ; 26.06.2015
comment
@jagsgediya Конечно, может. Но так как memcpy задокументировано, чтобы не поддерживать это, вы не должны полагаться на это поведение, специфичное для реализации, поэтому существует memmove (). В другой версии gcc все может быть иначе. Это могло бы быть иначе, если бы gcc встраивал memcpy вместо вызова memcpy () в glibc, это могло бы быть иначе в более старой или новой версии glibc и так далее. - person nos; 26.06.2015
comment
На практике кажется, что memcpy и memmove сделали то же самое. Такое глубокое неопределенное поведение. - person Life; 09.02.2016

На странице руководства memcpy.

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

person John Carter    schedule 29.07.2009

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

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 memcpy ( 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.

person Mecki    schedule 14.04.2018
comment
+1, потому что ваши рисунки ascii были полезны, чтобы понять, почему не может быть перекрытия без повреждения данных - person Scylardor; 17.07.2019
comment
Обратите внимание, что результат реляционного сравнения указателей на разные объекты / массивы не указан. Таким образом, невозможно безопасно определить, накладываются ли два объекта друг на друга. Фактически, типичная реализация memmove копирует данные спереди назад, если dst < src в противном случае копирует данные спереди назад. Это безопасно, потому что если есть наложение, результат определен (и правильный), поэтому поведение безопасно, в противном случае, если наложения нет, результат не указан, но копирование в обоих направлениях безопасно. - person VainMan; 24.09.2020

Основное различие между 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
person Mirac Suzgun    schedule 07.08.2014
comment
Но похоже, что результат, который вы упомянули, перевернут! - person kumar; 20.11.2014
comment
Когда я запускаю ту же программу, я получаю следующий результат: stackoverflow stackstackstw stackstackstw // означает, что НЕТ разницы в выводе между memcpy и memmove - person kumar; 20.11.2014
comment
заключается в том, что в memmove () используется буфер - временная память; Это не правда. он говорит, как будто это так, он просто должен вести себя так, а не так, как будто это должно быть. Это действительно актуально, поскольку большинство реализаций memmove просто выполняют замену по XOR. - person dhein; 08.05.2015
comment
Я не думаю, что для использования буфера требуется реализация memmove(). Он имеет полное право перемещаться на месте (при условии, что каждое чтение завершается до любой записи по тому же адресу). - person Toby Speight; 20.05.2016

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

person KPexEA    schedule 29.07.2009

просто из стандарта 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() допускает.

person dhein    schedule 08.05.2015

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


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

person Toby Speight    schedule 30.05.2016

memmove может иметь дело с перекрывающимися областями источника и назначения, в то время как memcpy не может. Среди них memcpy намного эффективнее. Так что лучше ИСПОЛЬЗУЙТЕ memcpy, если можете.

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

person Ehsan    schedule 10.12.2016
comment
Этот ответ говорит, что, возможно, немного быстрее и предоставляет количественные данные, указывающие лишь на небольшую разницу. Этот ответ утверждает, что один намного эффективнее. Насколько эффективнее, по вашему мнению, будет более быстрый? Кстати: я полагаю, вы имеете в виду memcpy(), а не memcopy(). - person chux - Reinstate Monica; 10.12.2016
comment
Комментарий сделан на основе лекции доктора Джерри Кейна. Прошу вас послушать его лекцию в 36:00, всего 2-3 минуты хватит. И спасибо за улов. : D - person Ehsan; 10.12.2016