Apa perbedaan antara memmove dan memcpy?

120

Apa perbedaan antara memmovedan memcpy? Mana yang biasa Anda gunakan dan bagaimana?

Komunitas
sumber
Perhatikan masalah yang mungkin timbul: lwn.net/Articles/414467
Zan Lynx

Jawaban:

162

Dengan memcpy, tujuan tidak boleh tumpang tindih dengan sumber sama sekali. Dengan memmoveitu bisa. Ini berarti memmovemungkin sangat sedikit lebih lambat dari memcpy, karena tidak dapat membuat asumsi yang sama.

Misalnya, memcpymungkin selalu menyalin alamat dari rendah ke tinggi. Jika tujuan tumpang tindih setelah sumber, ini berarti beberapa alamat akan ditimpa sebelum disalin. memmoveakan mendeteksi ini dan menyalin ke arah lain - dari tinggi ke rendah - dalam kasus ini. Namun, memeriksa ini dan beralih ke algoritme lain (yang mungkin kurang efisien) membutuhkan waktu.

bdonlan.dll
sumber
1
saat menggunakan memcpy, bagaimana saya bisa menjamin bahwa alamat src dan tujuan tidak tumpang tindih? Haruskah saya secara pribadi memastikan bahwa src dan dest tidak tumpang tindih?
Alcott
6
@Alcott, jangan gunakan memcpy jika Anda tidak tahu bahwa mereka tidak tumpang tindih - gunakan memmove sebagai gantinya. Ketika tidak ada tumpang tindih, memmove dan memcpy adalah setara (walaupun memcpy mungkin sangat, sangat, sangat sedikit lebih cepat).
bdonlan
Anda dapat menggunakan kata kunci 'batasi' jika Anda bekerja dengan array panjang dan ingin melindungi proses penyalinan Anda. Misalnya jika Anda menggunakan metode sebagai parameter input dan output array dan Anda harus memverifikasi bahwa pengguna tidak mengirimkan alamat yang sama sebagai input dan output. Baca lebih lanjut di sini stackoverflow.com/questions/776283/…
DanielHsH
10
@DanielHsH 'membatasi' adalah janji Anda membuat kompilator; itu tidak diberlakukan oleh kompilator. Jika Anda meletakkan 'batasi' pada argumen Anda dan memang, pada kenyataannya, tumpang tindih (atau lebih umum, mengakses data yang dibatasi dari pointer yang berasal dari banyak tempat), perilaku program tidak ditentukan, bug aneh akan terjadi, dan kompiler biasanya tidak akan memperingatkan Anda tentang hal itu.
bdonlan
@bdonlan Ini bukan hanya janji untuk kompilator, ini adalah persyaratan untuk pemanggil Anda. Ini adalah persyaratan yang tidak diberlakukan tetapi jika Anda melanggar persyaratan, Anda tidak dapat mengeluh jika mendapatkan hasil yang tidak diharapkan. Melanggar persyaratan adalah perilaku tidak terdefinisi, sama seperti i = i++ + 1tidak terdefinisi; kompilator tidak melarang Anda untuk menulis kode itu dengan tepat tetapi hasil dari instruksi itu dapat berupa apa saja dan kompiler atau CPU yang berbeda akan menunjukkan nilai yang berbeda di sini.
Mecki
33

memmovedapat menangani memori yang tumpang tindih, memcpytidak bisa.

Mempertimbangkan

char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up

Jelas sumber dan tujuan sekarang tumpang tindih, kami menimpa "-bar" dengan "bar". Ini adalah perilaku tidak terdefinisi yang menggunakan memcpyjika sumber dan tujuan tumpang tindih sehingga dalam kasus ini kita membutuhkannya memmove.

memmove(&str[3],&str[4],4); //fine
no
sumber
5
mengapa pertama kali meledak?
4
@ultraman: Karena MUNGKIN diimplementasikan menggunakan assembley tingkat rendah yang mengharuskan memori tidak tumpang tindih. Jika ya, Anda dapat misalnya menghasilkan sinyal atau pengecualian perangkat keras ke prosesor yang membatalkan aplikasi. Dokumentasi menetapkan bahwa ia tidak menangani kondisi, tetapi standar tidak menentukan apa yang akan terjadi ketika kondisi ini dilanggar (ini dikenal sebagai perilaku tidak terdefinisi). Perilaku tidak terdefinisi dapat melakukan apa saja.
Martin York
dengan gcc 4.8.2, Bahkan memcpy juga menerima sumber dan tujuan yang tumpang tindih dan berfungsi dengan baik.
GeekyJ
4
@jagsgya Yakin itu mungkin. Tetapi karena memcpy didokumentasikan untuk tidak mendukung ini, Anda tidak boleh mengandalkan perilaku spesifik implementasi itu, itulah mengapa memmove () ada. Ini mungkin berbeda di versi gcc lain. Mungkin berbeda jika gcc memasukkan memcpy ke dalam baris daripada memanggil ke memcpy () di glibc, ini mungkin berbeda pada versi glibc yang lebih lama atau lebih baru dan seterusnya.
no
Dari latihan, tampaknya memcpy dan memmove melakukan hal yang sama. Perilaku yang sangat tidak terdefinisi.
Kehidupan
22

Dari halaman manual memcpy .

Fungsi memcpy () menyalin n byte dari area memori src ke tujuan area memori. Area memori tidak boleh tumpang tindih. Gunakan memmove (3) jika area memori tumpang tindih.

John Carter
sumber
12

Perbedaan utama antara memmove()dan memcpy()adalah bahwa dalam memmove()sebuah penyangga - memori sementara - yang digunakan, sehingga tidak ada risiko tumpang tindih. Di sisi lain, memcpy()langsung menyalin data dari lokasi yang ditunjuk oleh sumber ke lokasi yang dituju . ( http://www.cplusplus.com/reference/cstring/memcpy/ )

Perhatikan contoh berikut:

  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;
    }

    Seperti yang Anda harapkan, ini akan dicetak:

    stackoverflow
    stackstacklow
    stackstacklow
  2. Tetapi dalam contoh ini, hasilnya tidak akan sama:

    #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;
    }

    Keluaran:

    stackoverflow
    stackstackovw
    stackstackstw

Itu karena "memcpy ()" melakukan hal berikut:

1.  stackoverflow
2.  stacksverflow
3.  stacksterflow
4.  stackstarflow
5.  stackstacflow
6.  stackstacklow
7.  stackstacksow
8.  stackstackstw
Mirac Suzgun
sumber
2
Tapi, tampaknya output yang Anda sebutkan terbalik !!
kumar
1
Ketika saya menjalankan program yang sama, saya mendapatkan hasil sebagai berikut: stackoverflow stackstackstw stackstackstw // berarti TIDAK ada perbedaan dalam output antara memcpy dan memmove
kumar
4
"adalah bahwa di" memmove () ", buffer - memori sementara - digunakan;" Tidak benar. ia berkata "seolah-olah" jadi ia harus berperilaku demikian, bukan harus seperti itu. Itu memang relevan karena kebanyakan implementasi memmove hanya melakukan XOR-swap.
dhein
2
Saya tidak berpikir implementasi memmove()diperlukan untuk menggunakan buffer. Sangat berhak untuk pindah di tempat (selama setiap pembacaan selesai sebelum ada penulisan ke alamat yang sama).
Toby Speight
12

Dengan asumsi Anda harus menerapkan keduanya, implementasinya akan terlihat seperti ini:

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
    }
}

Dan ini seharusnya menjelaskan perbedaannya dengan cukup baik. memmoveselalu menyalin sedemikian rupa, sehingga masih aman jika srcdan dsttumpang tindih, sedangkan memcpytidak peduli seperti yang dikatakan dokumentasi saat menggunakan memcpy, kedua area memori tidak boleh tumpang tindih.

Misalnya, jika memcpymenyalin "depan ke belakang" dan blok memori disejajarkan seperti ini

[---- src ----]
            [---- dst ---]

menyalin byte pertama dari srcke dstsudah menghancurkan konten byte terakhir srcsebelum ini disalin. Hanya menyalin "kembali ke depan" akan memberikan hasil yang benar.

Sekarang tukar srcdan dst:

[---- dst ----]
            [---- src ---]

Dalam hal ini, hanya aman untuk menyalin "depan ke belakang" karena menyalin "kembali ke depan" akan menghancurkan srcbagian depannya saat menyalin byte pertama.

Anda mungkin telah memperhatikan bahwa memmoveimplementasi di atas bahkan tidak menguji apakah mereka benar-benar tumpang tindih, itu hanya memeriksa posisi relatif mereka, tetapi itu saja akan membuat salinan aman. Karena memcpybiasanya menggunakan cara tercepat untuk menyalin memori pada sistem apa pun, memmovebiasanya diterapkan sebagai:

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);
    }
}

Terkadang, jika memcpyselalu menyalin "depan ke belakang" atau "belakang ke depan", memmovedapat juga digunakan memcpydi salah satu kasus yang tumpang tindih tetapi memcpybahkan dapat menyalin dengan cara yang berbeda tergantung pada bagaimana data diratakan dan / atau berapa banyak data yang akan disalin, jadi meskipun Anda menguji bagaimana memcpymenyalin pada sistem Anda, Anda tidak dapat mengandalkan hasil tes itu untuk selalu benar.

Apa artinya bagi Anda ketika memutuskan mana yang akan dihubungi?

  1. Kecuali Anda tahu pasti srcdan dsttidak tumpang tindih, panggil memmovekarena akan selalu memberikan hasil yang benar dan biasanya secepat mungkin untuk kasus salinan yang Anda perlukan.

  2. Jika Anda tahu pasti itu srcdan dsttidak tumpang tindih, panggil memcpykarena tidak masalah mana yang Anda panggil untuk hasilnya, keduanya akan bekerja dengan benar dalam kasus itu, tetapi memmovetidak akan pernah lebih cepat daripada memcpydan jika Anda tidak beruntung, bahkan mungkin menjadi lebih lambat, jadi Anda hanya bisa memenangkan panggilan memcpy.

Mecki
sumber
+1 karena "gambar ascii" Anda berguna untuk memahami mengapa mungkin tidak ada tumpang tindih tanpa merusak data
Scylardor
10

Satu menangani tujuan yang tumpang tindih, yang lainnya tidak.

KPexEA
sumber
6

hanya dari standar ISO / IEC: 9899 dijelaskan dengan baik.

7.21.2.1 Fungsi memcpy

[...]

2 Fungsi memcpy menyalin n karakter dari objek yang ditunjuk oleh s2 ke objek yang ditunjuk oleh s1. Jika penyalinan terjadi di antara objek yang tumpang tindih, perilakunya tidak ditentukan.

Dan

7.21.2.2 Fungsi memmove

[...]

2 Fungsi memmove menyalin n karakter dari objek yang ditunjuk oleh s2 ke objek yang ditunjuk oleh s1. Penyalinan terjadi seolah-olah karakter n dari objek yang ditunjukkan oleh s2 pertama kali disalin ke dalam larik sementara karakter n yang tidak tumpang tindih dengan objek yang ditunjukkan oleh s1 dan s2, dan kemudian karakter n dari larik sementara disalin ke objek yang ditunjukkan oleh s1.

Yang mana yang biasanya saya gunakan sesuai dengan pertanyaan, tergantung pada fungsi apa yang saya butuhkan.

Dalam teks biasa memcpy()tidak mengizinkan s1dan s2tumpang tindih, sementara memmove()tidak.

dhein
sumber
0

Ada dua cara yang jelas untuk mengimplementasikan mempcpy(void *dest, const void *src, size_t n)(mengabaikan nilai kembalian):

  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];

Dalam implementasi pertama, proses menyalin dari alamat rendah ke alamat tinggi, dan yang kedua, dari alamat tinggi ke rendah. Jika rentang yang akan disalin tumpang tindih (seperti yang terjadi saat menggulir framebuffer, misalnya), maka hanya satu arah operasi yang benar, dan yang lainnya akan menimpa lokasi yang kemudian akan dibaca.

Sebuah memmove()implementasi, pada yang paling sederhana, akan menguji dest<src(dalam beberapa cara tergantung platform), dan menjalankan arah yang tepat memcpy().

Kode pengguna tidak dapat melakukan itu tentu saja, karena bahkan setelah pengecoran srcdan dstke beberapa jenis penunjuk konkret, mereka tidak (secara umum) menunjuk ke objek yang sama sehingga tidak dapat dibandingkan. Tetapi pustaka standar dapat memiliki pengetahuan platform yang cukup untuk melakukan perbandingan seperti itu tanpa menyebabkan Perilaku Tidak Terdefinisi.


Perhatikan bahwa dalam kehidupan nyata, implementasi cenderung jauh lebih kompleks, untuk mendapatkan kinerja maksimum dari transfer yang lebih besar (jika penyelarasan memungkinkan) dan / atau pemanfaatan cache data yang baik. Kode di atas hanya untuk membuat intinya sesederhana mungkin.

Toby Speight
sumber
0

memmove dapat menangani daerah sumber dan tujuan yang tumpang tindih, sedangkan memcpy tidak bisa. Di antara keduanya, memcpy jauh lebih efisien. Jadi, lebih baik GUNAKAN memcpy jika Anda bisa.

Referensi: https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr. Jerry Cain, (Kuliah Sistem Intro Stanford - 7) Waktu: 36:00

Ehsan
sumber
Jawaban ini mengatakan "mungkin sedikit lebih cepat" dan memberikan data kuantitatif yang menunjukkan hanya sedikit perbedaan. Jawaban ini menegaskan bahwa seseorang "jauh lebih efisien". Menurut Anda, seberapa efisien Anda menemukan cara yang lebih cepat? BTW: Saya menganggap Anda bermaksud memcpy()dan tidak memcopy().
chux - Kembalikan Monica
Komentar tersebut dibuat berdasarkan ceramah Dr. Jerry Cain. Saya akan meminta Anda mendengarkan ceramahnya pada pukul 36:00, hanya 2-3 menit saja sudah cukup. Dan terima kasih untuk tangkapannya. : D
Ehsan