Jawaban:
Dengan memcpy
, tujuan tidak boleh tumpang tindih dengan sumber sama sekali. Dengan memmove
itu bisa. Ini berarti memmove
mungkin sangat sedikit lebih lambat dari memcpy
, karena tidak dapat membuat asumsi yang sama.
Misalnya, memcpy
mungkin selalu menyalin alamat dari rendah ke tinggi. Jika tujuan tumpang tindih setelah sumber, ini berarti beberapa alamat akan ditimpa sebelum disalin. memmove
akan 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.
i = i++ + 1
tidak 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.
memmove
dapat menangani memori yang tumpang tindih, memcpy
tidak 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 memcpy
jika sumber dan tujuan tumpang tindih sehingga dalam kasus ini kita membutuhkannya memmove
.
memmove(&str[3],&str[4],4); //fine
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:
#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
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
memmove()
diperlukan untuk menggunakan buffer. Sangat berhak untuk pindah di tempat (selama setiap pembacaan selesai sebelum ada penulisan ke alamat yang sama).
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. memmove
selalu menyalin sedemikian rupa, sehingga masih aman jika src
dan dst
tumpang tindih, sedangkan memcpy
tidak peduli seperti yang dikatakan dokumentasi saat menggunakan memcpy
, kedua area memori tidak boleh tumpang tindih.
Misalnya, jika memcpy
menyalin "depan ke belakang" dan blok memori disejajarkan seperti ini
[---- src ----]
[---- dst ---]
menyalin byte pertama dari src
ke dst
sudah menghancurkan konten byte terakhir src
sebelum ini disalin. Hanya menyalin "kembali ke depan" akan memberikan hasil yang benar.
Sekarang tukar src
dan dst
:
[---- dst ----]
[---- src ---]
Dalam hal ini, hanya aman untuk menyalin "depan ke belakang" karena menyalin "kembali ke depan" akan menghancurkan src
bagian depannya saat menyalin byte pertama.
Anda mungkin telah memperhatikan bahwa memmove
implementasi 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 memcpy
biasanya menggunakan cara tercepat untuk menyalin memori pada sistem apa pun, memmove
biasanya 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 memcpy
selalu menyalin "depan ke belakang" atau "belakang ke depan", memmove
dapat juga digunakan memcpy
di salah satu kasus yang tumpang tindih tetapi memcpy
bahkan dapat menyalin dengan cara yang berbeda tergantung pada bagaimana data diratakan dan / atau berapa banyak data yang akan disalin, jadi meskipun Anda menguji bagaimana memcpy
menyalin pada sistem Anda, Anda tidak dapat mengandalkan hasil tes itu untuk selalu benar.
Apa artinya bagi Anda ketika memutuskan mana yang akan dihubungi?
Kecuali Anda tahu pasti src
dan dst
tidak tumpang tindih, panggil memmove
karena akan selalu memberikan hasil yang benar dan biasanya secepat mungkin untuk kasus salinan yang Anda perlukan.
Jika Anda tahu pasti itu src
dan dst
tidak tumpang tindih, panggil memcpy
karena tidak masalah mana yang Anda panggil untuk hasilnya, keduanya akan bekerja dengan benar dalam kasus itu, tetapi memmove
tidak akan pernah lebih cepat daripada memcpy
dan jika Anda tidak beruntung, bahkan mungkin menjadi lebih lambat, jadi Anda hanya bisa memenangkan panggilan memcpy
.
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 s1
dan s2
tumpang tindih, sementara memmove()
tidak.
Ada dua cara yang jelas untuk mengimplementasikan mempcpy(void *dest, const void *src, size_t n)
(mengabaikan nilai kembalian):
for (char *p=src, *q=dest; n-->0; ++p, ++q)
*q=*p;
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 src
dan dst
ke 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.
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
memcpy()
dan tidak memcopy()
.