Яка різниця між memmove та memcpy?


Відповіді:


162

З memcpy, адресат не може перекривати джерело на всіх. З memmoveним можна. Це означає, що це memmoveможе бути трохи повільніше memcpy, оскільки не може робити однакові припущення.

Наприклад, memcpyзавжди можна копіювати адреси від низьких до високих. Якщо місце призначення перекривається після джерела, це означає, що деякі адреси будуть перезаписані перед копіюванням. memmoveвиявив би це і скопіював у іншому напрямку - від високого до низького - у цьому випадку. Однак перевірка цього та перехід на інший (можливо, менш ефективний) алгоритм вимагає часу.


1
при використанні memcpy, як я можу гарантувати, що адреси src та dest не збігаються? Чи повинен я особисто переконатися, що src та dest не перетинаються?
Алькотт

6
@Alcott, не використовуй memcpy, якщо ти не знаєш, що вони не перекриваються - замість цього використовуй memmove. Коли немає перекриття, memmove та memcpy рівноцінні (хоча memcpy може бути дуже, дуже, дуже трохи швидшим).
bdonlan

Ви можете використовувати ключове слово "обмежити", якщо ви працюєте з довгими масивами і хочете захистити процес копіювання. Наприклад, якщо метод використовується як параметри вхідних і вихідних масивів, і ви повинні переконатися, що користувач не передає ту саму адресу, що і вхід і вихід. Детальніше тут stackoverflow.com/questions/776283 / ...
DanielHsH

10
@DanielHsH "обмежувати" - це обіцянка, яку ви зробите компілятором; воно не виконано компілятором. Якщо ви ставите «обмежувати» на свої аргументи і насправді маєте перекриття (або, загалом, доступ до обмежених даних із вказівника, отриманого з кількох місць), поведінка програми не визначена, трапляться дивні помилки та компілятор зазвичай не попередить вас про це.
bdonlan

@bdonlan Це не просто обіцянка компілятору, це вимога до вашого абонента. Це вимога, яка не виконується, але якщо ви порушите вимогу, ви не можете скаржитися, якщо ви отримаєте несподівані результати. Порушення вимоги - це невизначена поведінка, як i = i++ + 1і невизначена; компілятор не забороняє писати саме цей код, але результатом цієї інструкції може бути все що завгодно, і різні компілятори чи процесори покажуть тут різні значення.
Мецький

33

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

5
чому б спочатку підірвати?

4
@ultraman: Оскільки воно МОЖЕ бути реалізовано за допомогою асемблери низького рівня, що вимагає, щоб пам'ять не перекривалась. Наприклад, ви могли б, наприклад, генерувати сигнал або апаратне виключення для процесора, який припиняє роботу програми. Документація вказує, що вона не обробляє умову, але стандарт не вказує, що станеться, коли ці умови будуть розміщені (це відомо як невизначена поведінка). Невизначена поведінка може зробити що завгодно.
Мартін Йорк

з gcc 4.8.2, Even memcpy також приймає вказівники, що перекриваються джерелом та пунктом призначення, і працює добре.
GeekyJ

4
@jagsgediya Звичайно, це може. Але оскільки memcpy задокументована таким чином, що це не підтримує, не слід покладатися на специфічну поведінку цієї реалізації, тому memmove () існує. У іншій версії gcc це може бути інакше. Це може бути інакше, якщо gcc вбудовує memcpy замість виклику memcpy () у glibc, може бути різним у старій чи новій версії glibc тощо.
нос

З практики здається, що мемпі та мемове зробили те саме. Така глибока невизначена поведінка.
Життя

22

Із сторінки « memcpy man».

Функція memcpy () копіює n байт з області пам'яті src в область пам'яті dest. Області пам’яті не повинні перетинатися. Використовуйте memmove (3), якщо області пам'яті перекриваються.


12

Основна відмінність між memmove()і в memcpy()тому , що в буфера - тимчасова пам'ять - використовується, так що немає ніякого ризику дублювання. З іншого боку, безпосередньо копіює дані з місця, яке вказує джерело, до місця, вказаного пунктом призначення . ( http://www.cplusplus.com/reference/cstring/memcpy/ )memmove()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

2
Але, здається, результат, про який ви згадали, є зворотним !!
кумар

1
Коли я запускаю ту саму програму, я отримую такий результат: stackoverflow stackstackstw stackstackstw // означає, що немає різниці у виході між memcpy та memmove
kumar

4
"полягає в тому, що в" memmove () "використовується буфер - тимчасова пам'ять;" Неправда. там сказано "як би", тому воно просто має так поводитися, а не те, що має бути таким. Це дійсно актуально, оскільки більшість пам’ятних реалізацій просто роблять XOR-своп.
dhein

2
Я не думаю, що memmove()для використання потрібно використовувати буфер. Це абсолютно право на переміщення на місці (до тих пір, поки кожне читання завершиться перед тим, як записати на одну і ту ж адресу).
Toby Speight

12

Якщо припустити, що вам доведеться реалізувати обидва, реалізація може виглядати так:

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 виконуються копії у вашій системі, ви не можете розраховувати на те, що результат тесту буде завжди правильним.

Що це означає для вас, коли ви вирішуєте, кого дзвонити?

  1. Якщо ви точно не знаєте цього srcі dstне перетинаєтесь, дзвоніть, memmoveоскільки це завжди призведе до правильних результатів і, як правило, так швидко, як це можливо для потрібного випадку копіювання.

  2. Якщо ви точно знаєте це srcі dstне перетинаєтесь, дзвоніть, memcpyбо не має значення, кого з вас викликає результат, і те і інше буде працювати в цьому випадку правильно, але memmoveніколи не буде швидше, memcpyі якщо вам не пощастить, це може навіть будьте повільнішими, тому ви можете виграти лише дзвінки memcpy.


+1, тому що ваші "креслення ascii" були корисними, щоб зрозуміти, чому не може бути перекриття без пошкодження даних
Scylardor

10

Одна ручка перетинає напрямки, що перекриваються, а інша - ні.


6

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


0

Є два очевидних способи реалізації 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до конкретного типу вказівника вони (загалом) не вказують на один і той же об'єкт, і тому їх не можна порівняти. Але стандартна бібліотека може мати достатньо знань платформи, щоб виконати таке порівняння, не викликаючи не визначеного поведінки.


Зауважте, що в реальному житті реалізація, як правило, значно складніша, щоб отримати максимальну продуктивність від більших передач (коли вирівнювання дозволяє) та / або хорошого використання кешу даних. Код, наведений вище, полягає лише в тому, щоб зробити пробу максимально простою.


0

memmove може мати справу з перекриваються джерелами та регіонами призначення, тоді як memcpy не може. Серед двох, memcpy набагато ефективніше. Отже, краще ВИКОРИСТИТИ memcpy, якщо можете.

Довідка: https://www.youtube.com/watch?v=Yr1YnOVG-4g Доктор Джеррі Кейн, (Лекція Intro Systems Stanford - 7) Час: 36:00


Ця відповідь говорить "можливо, швидше", і дає кількісні дані, що свідчать лише про незначну різницю. Ця відповідь стверджує, що "набагато ефективніше". Наскільки ефективніше ви знайшли швидшого? До речі: я припускаю, що ви маєте на увазі, memcpy()а ні memcopy().
chux

Коментар зроблений на основі лекції доктора Джеррі Каїна. Я б просив вас слухати його лекцію в час 36:00, достатньо буде лише 2-3 хвилин. І дякую за улов. : D
Есан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.