Відповіді:
З memcpy
, адресат не може перекривати джерело на всіх. З memmove
ним можна. Це означає, що це memmove
може бути трохи повільніше memcpy
, оскільки не може робити однакові припущення.
Наприклад, memcpy
завжди можна копіювати адреси від низьких до високих. Якщо місце призначення перекривається після джерела, це означає, що деякі адреси будуть перезаписані перед копіюванням. memmove
виявив би це і скопіював у іншому напрямку - від високого до низького - у цьому випадку. Однак перевірка цього та перехід на інший (можливо, менш ефективний) алгоритм вимагає часу.
i = i++ + 1
і невизначена; компілятор не забороняє писати саме цей код, але результатом цієї інструкції може бути все що завгодно, і різні компілятори чи процесори покажуть тут різні значення.
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 Функція пам'яті
[...]
2 Функція пам'яті копіює 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 Доктор Джеррі Кейн, (Лекція Intro Systems Stanford - 7) Час: 36:00
memcpy()
а ні memcopy()
.