В чем разница между 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/)
Рассмотрим следующие примеры:
-
#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
-
Но в этом примере результаты не будут такими же:
#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
копирует в вашей системе, вы не можете полагаться на то, что результаты теста всегда будут правильными.
Что это значит для вас, когда вы решаете, кому позвонить?
-
Если вы точно не знаете, что
src
иdst
не перекрываются, вызовитеmemmove
как это всегда приведет к правильным результатам и, как правило, будет настолько быстрым, насколько это возможно для необходимого вам случая копирования. -
Если вы точно знаете, что
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);
В этом втором блоке кода программа потребует в два раза больше памяти, чем первый блок. Однако этот второй блок должен выполняться значительно быстрее, чем первый блок, особенно по мере увеличения размера.
То, как я пытался объяснить практические различия в двух... но, возможно, я ошибаюсь и в этом?