Відповіді:
З 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().