В чем разница между memmove
и memcpy
? Какой из них вы обычно используете и как?
В чем разница между memmove
и memcpy
? Какой из них вы обычно используете и как?
Ответы:
С memcpy
, пункт назначения вообще не может перекрывать источник. С memmove
ним можно. Это означает, что это memmove
может быть немного медленнее, чем memcpy
, поскольку он не может делать те же предположения.
Например, memcpy
всегда можно копировать адреса снизу вверх. Если место назначения перекрывается после источника, это означает, что некоторые адреса будут перезаписаны перед копированием. memmove
обнаружит это и скопирует в другом направлении - от высокого к низкому - в этом случае. Однако проверка этого и переключение на другой (возможно, менее эффективный) алгоритм требует времени.
i = i++ + 1
undefined; компилятор не запрещает вам писать именно этот код, но результатом этой инструкции может быть что угодно, и разные компиляторы или процессоры будут показывать здесь разные значения.
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
Основное различие между memmove()
и в memcpy()
том , что в буфера - временная память - используется, так что нет никакого риска дублирования. С другой стороны, данные напрямую копируются из места, указанного источником, в место, указанное адресатом . ( http://www.cplusplus.com/reference/cstring/memcpy/ )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, 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
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
копируется в своей системе, вы не можете рассчитывать на то, что результат теста всегда будет правильным.
Что это значит для вас, когда вы решаете, в какой из них позвонить?
Если вы не знаете это наверняка src
и dst
не перекрываетесь, вызывайте, memmove
поскольку это всегда приводит к правильным результатам и обычно выполняется настолько быстро, насколько это возможно для требуемого вам варианта копирования.
Если вы знаете это наверняка src
и dst
не пересекаетесь, позвоните, memcpy
поскольку не имеет значения, какой из них вы вызываете для результата, оба будут работать правильно в этом случае, но memmove
никогда не будут быстрее, чем, memcpy
а если вам не повезло, это может даже будьте медленнее, так что вы можете только выиграть колл memcpy
.
просто из стандарта 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()
.
Есть два очевидных способа реализовать 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
memcpy()
а не memcopy()
.