memcpy () vs memmove ()

157

Saya mencoba memahami perbedaan antara memcpy()dan memmove(), dan saya telah membaca teks yang memcpy()tidak menangani sumber dan tujuan yang tumpang tindih sedangkan yang memmove()tidak.

Namun, ketika saya menjalankan kedua fungsi ini pada blok memori yang tumpang tindih, keduanya memberikan hasil yang sama. Misalnya, ambil contoh MSDN berikut di memmove()halaman bantuan: -

Apakah ada contoh yang lebih baik untuk memahami kelemahan memcpydan bagaimana memmovemenyelesaikannya?

// crt_memcpy.c
// Illustrate overlapping copy: memmove always handles it correctly; memcpy may handle
// it correctly.

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[7] = "aabbcc";

int main( void )
{
    printf( "The string: %s\n", str1 );
    memcpy( str1 + 2, str1, 4 );
    printf( "New string: %s\n", str1 );

    strcpy_s( str1, sizeof(str1), "aabbcc" );   // reset string

    printf( "The string: %s\n", str1 );
    memmove( str1 + 2, str1, 4 );
    printf( "New string: %s\n", str1 );
}

Keluaran:

The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb
pengguna534785
sumber
1
Microsoft CRT telah memiliki memcpy aman () untuk beberapa saat.
Hans Passant
32
Saya tidak berpikir "aman" adalah kata yang tepat untuk itu. Sebuah aman memcpyakan assertbahwa daerah tidak tumpang tindih bukan sengaja menutupi bug dalam kode Anda.
R .. GitHub BERHENTI MEMBANTU ICE
6
Bergantung pada apakah maksud Anda "aman untuk pengembang" atau "aman untuk pengguna akhir". Saya berpendapat bahwa melakukan seperti yang diperintahkan, bahkan jika itu tidak memenuhi standar adalah pilihan yang lebih aman bagi pengguna akhir.
kusma
sejak glibc 2.19 - tidak berfungsi The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen
Anda juga bisa lihat di sini .
Ren

Jawaban:

124

Saya tidak sepenuhnya terkejut bahwa contoh Anda tidak menunjukkan perilaku aneh. Coba salin str1ke str1+2gantinya dan lihat apa yang terjadi kemudian. (Mungkin tidak benar-benar membuat perbedaan, tergantung pada kompiler / perpustakaan.)

Secara umum, memcpy diimplementasikan dengan cara yang sederhana (tapi cepat). Secara sederhana, itu hanya loop atas data (dalam urutan), menyalin dari satu lokasi ke lokasi lain. Ini dapat menyebabkan sumber ditimpa saat sedang dibaca.

Memmove melakukan lebih banyak pekerjaan untuk memastikannya menangani tumpang tindih dengan benar.

EDIT:

(Sayangnya, saya tidak dapat menemukan contoh yang layak, tetapi ini akan berhasil). Bandingkan implementasi memcpy dan memmove di sini. memcpy just loop, sementara memmove melakukan tes untuk menentukan arah mana yang harus di-loop agar tidak merusak data. Implementasi ini agak sederhana. Sebagian besar implementasi berkinerja tinggi lebih rumit (melibatkan menyalin blok ukuran kata pada suatu waktu daripada byte).

developmentalinsanity
sumber
2
+1 Juga, dalam implementasi berikut, melakukan memmovepanggilan memcpydalam satu cabang setelah menguji petunjuk: student.cs.uwaterloo.ca/~cs350/common/os161-src-html/…
Pascal Cuoq
Kedengarannya bagus. Sepertinya Visual Studio mengimplementasikan memcpy "aman" (bersama dengan gcc 4.1.1, saya juga menguji RHEL 5). Menulis versi dari fungsi-fungsi ini dari clc-wiki.net memberikan gambaran yang jelas. Terima kasih.
user534785
3
memcpy tidak menangani masalah yang tumpang tindih, tetapi memmove melakukannya. Lalu mengapa tidak menghilangkan memcpy dari lib?
Alcott
37
@Alcott: Karena memcpybisa lebih cepat.
Billy ONeal
Memperbaiki / tautan webarchive dari Pascal Cuoq di atas: web.archive.org/web/20130722203254/http://…
JWCS
94

Memori dalam memcpy tidak bisa tumpang tindih atau Anda berisiko perilaku tidak terdefinisi, sedangkan memori dalam memmovebisa tumpang tindih.

char a[16];
char b[16];

memcpy(a,b,16);           // valid
memmove(a,b,16);          // Also valid, but slower than memcpy.
memcpy(&a[0], &a[1],10);  // Not valid since it overlaps.
memmove(&a[0], &a[1],10); // valid. 

Beberapa implementasi memcpy mungkin masih berfungsi untuk input yang tumpang tindih tetapi Anda tidak dapat menghitung perilaku itu. Sementara memmove harus memungkinkan untuk tumpang tindih.

rxantos
sumber
3
itu benar-benar membantu saya mengucapkan terima kasih! +1 untuk info Anda
Muthu Ganapathy Nathan
33

Hanya karena memcpytidak harus berurusan dengan daerah yang tumpang tindih, tidak berarti itu tidak berurusan dengan mereka dengan benar. Panggilan dengan wilayah yang tumpang tindih menghasilkan perilaku yang tidak terdefinisi. Perilaku tidak terdefinisi dapat bekerja sepenuhnya seperti yang Anda harapkan pada satu platform; itu tidak berarti itu benar atau valid.

Billy ONeal
sumber
10
Khususnya, tergantung pada platform, mungkin saja memcpyditerapkan dengan cara yang persis sama memmove. Artinya, siapa pun yang menulis kompiler tidak repot menulis memcpyfungsi yang unik .
Cam
19

Baik memcpy dan memove melakukan hal serupa.

Tetapi untuk melihat satu perbedaan:

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[7] = "abcdef";

int main()
{

   printf( "The string: %s\n", str1 );
   memcpy( (str1+6), str1, 10 );
   printf( "New string: %s\n", str1 );

   strcpy_s( str1, sizeof(str1), "aabbcc" );   // reset string


   printf("\nstr1: %s\n", str1);
   printf( "The string: %s\n", str1 );
   memmove( (str1+6), str1, 10 );
   printf( "New string: %s\n", str1 );

}

memberi:

The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef
Neilvert Noval
sumber
IMHO, program contoh ini memiliki beberapa kelemahan, karena buffer str1 diakses di luar batas (10 byte untuk disalin, buffer berukuran 7 byte). Kesalahan di luar batas menghasilkan perilaku yang tidak terdefinisi. Perbedaan dalam hasil yang ditampilkan dari panggilan memcpy () / memmove () spesifik untuk implementasi. Dan contoh output tidak sama persis dengan program di atas ... Juga, strcpy_s () bukan bagian dari standar C AFAIK (spesifik MS, lihat juga: stackoverflow.com/questions/36723946/… ) - Tolong koreksi saya jika saya salah
rel
7

Demo Anda tidak mengekspos kelemahan memcpy karena kompiler "buruk", itu membantu Anda dalam versi Debug. Versi rilis, bagaimanapun, memberi Anda output yang sama, tetapi karena optimasi.

    memcpy(str1 + 2, str1, 4);
00241013  mov         eax,dword ptr [str1 (243018h)]  // load 4 bytes from source string
    printf("New string: %s\n", str1);
00241018  push        offset str1 (243018h) 
0024101D  push        offset string "New string: %s\n" (242104h) 
00241022  mov         dword ptr [str1+2 (24301Ah)],eax  // put 4 bytes to destination
00241027  call        esi  

Register di %eaxsini berfungsi sebagai penyimpanan sementara, yang "secara elegan" memperbaiki masalah yang tumpang tindih.

Kekurangannya muncul ketika menyalin 6 byte, well, setidaknya sebagian.

char str1[9] = "aabbccdd";

int main( void )
{
    printf("The string: %s\n", str1);
    memcpy(str1 + 2, str1, 6);
    printf("New string: %s\n", str1);

    strcpy_s(str1, sizeof(str1), "aabbccdd");   // reset string

    printf("The string: %s\n", str1);
    memmove(str1 + 2, str1, 6);
    printf("New string: %s\n", str1);
}

Keluaran:

The string: aabbccdd
New string: aaaabbbb
The string: aabbccdd
New string: aaaabbcc

Terlihat aneh, itu disebabkan oleh optimasi juga.

    memcpy(str1 + 2, str1, 6);
00341013  mov         eax,dword ptr [str1 (343018h)] 
00341018  mov         dword ptr [str1+2 (34301Ah)],eax // put 4 bytes to destination, earlier than the above example
0034101D  mov         cx,word ptr [str1+4 (34301Ch)]  // HA, new register! Holding a word, which is exactly the left 2 bytes (after 4 bytes loaded to %eax)
    printf("New string: %s\n", str1);
00341024  push        offset str1 (343018h) 
00341029  push        offset string "New string: %s\n" (342104h) 
0034102E  mov         word ptr [str1+6 (34301Eh)],cx  // Again, pulling the stored word back from the new register
00341035  call        esi  

Inilah sebabnya saya selalu memilih memmoveketika mencoba menyalin 2 blok memori yang tumpang tindih.

huubby
sumber
3

Perbedaan antara memcpydan memmoveitu

  1. di memmove, memori sumber ukuran tertentu disalin ke buffer lalu dipindahkan ke tujuan. Jadi jika ingatannya tumpang tindih, tidak ada efek samping.

  2. dalam hal memcpy(), tidak ada buffer tambahan yang diambil untuk memori sumber. Penyalinan dilakukan langsung pada memori sehingga ketika ada memori yang tumpang tindih, kami mendapatkan hasil yang tidak terduga.

Ini dapat diamati dengan kode berikut:

//include string.h, stdio.h, stdlib.h
int main(){
  char a[]="hare rama hare rama";

  char b[]="hare rama hare rama";

  memmove(a+5,a,20);
  puts(a);

  memcpy(b+5,b,20);
  puts(b);
}

Output adalah:

hare hare rama hare rama
hare hare hare hare hare hare rama hare rama
R srinivas reddy
sumber
6
-1 - tidak ada persyaratan untuk memmove untuk benar-benar menyalin data ke buffer terpisah
jjwchoy
contoh ini tidak membantu dalam memahami konsep .... karena sebagian besar kompiler akan memberikan hasil yang sama dengan mem move
Jasdeep Singh Arora
1
@ jjwchoy Secara konseptual itu. Buffer biasanya akan dioptimalkan
MM
Hasil yang sama, di Linux.
CodyChan
2

Seperti yang telah ditunjukkan dalam jawaban lain, memmovelebih canggih daripada memcpyyang menyebabkan tumpang tindih memori. Hasil memmove didefinisikan seolah-olah srcdisalin ke dalam buffer dan kemudian disalin ke dalam buffer dst. Ini TIDAK berarti bahwa implementasi aktual menggunakan buffer apa pun, tetapi mungkin melakukan beberapa aritmatika pointer.

Огњен Шобајић
sumber
1

kompiler dapat mengoptimalkan memcpy, misalnya:

int x;
memcpy(&x, some_pointer, sizeof(int));

Memcpy ini dapat dioptimalkan sebagai: x = *(int*)some_pointer;

rockeet
sumber
3
Optimalisasi seperti itu hanya diperbolehkan pada arsitektur yang memungkinkan intakses yang tidak selaras . Pada beberapa arsitektur (misalnya Cortex-M0), berusaha mengambil 32-bit intdari alamat yang bukan kelipatan empat akan menyebabkan crash (tetapi memcpyakan berfungsi). Jika seseorang akan menggunakan CPU yang memungkinkan akses yang tidak selaras atau menggunakan kompiler dengan kata kunci yang mengarahkan kompiler untuk merakit integer dari byte yang diambil secara terpisah jika diperlukan, seseorang dapat melakukan sesuatu seperti #define UNALIGNED __unaligneddan kemudian `x = * (int UNALIGNED * ) some_pointer;
supercat
2
Beberapa prosesor tidak mengizinkan crash akses int unaligned char x = "12345"; int *i; i = *(int *)(x + 1);Tetapi beberapa melakukannya, karena mereka memperbaiki salinan selama kesalahan. Saya bekerja pada sistem seperti ini, dan butuh sedikit waktu untuk memahami mengapa kinerjanya sangat buruk.
user3431262
*(int *)some_pointeradalah pelanggaran alias ketat, tetapi Anda mungkin berarti bahwa kompiler akan menampilkan perakitan yang menyalin int
MM
1

Kode yang diberikan dalam tautan http://clc-wiki.net/wiki/memcpy untuk memcpy agak membingungkan saya, karena tidak memberikan output yang sama ketika saya menerapkannya menggunakan contoh di bawah ini.

#include <memory.h>
#include <string.h>
#include <stdio.h>

char str1[11] = "abcdefghij";

void *memcpyCustom(void *dest, const void *src, size_t n)
{
    char *dp = (char *)dest;
    const char *sp = (char *)src;
    while (n--)
        *dp++ = *sp++;
    return dest;
}

void *memmoveCustom(void *dest, const void *src, size_t n)
{
    unsigned char *pd = (unsigned char *)dest;
    const unsigned char *ps = (unsigned char *)src;
    if ( ps < pd )
        for (pd += n, ps += n; n--;)
            *--pd = *--ps;
    else
        while(n--)
            *pd++ = *ps++;
    return dest;
}

int main( void )
{
    printf( "The string: %s\n", str1 );
    memcpy( str1 + 1, str1, 9 );
    printf( "Actual memcpy output: %s\n", str1 );

    strcpy_s( str1, sizeof(str1), "abcdefghij" );   // reset string

    memcpyCustom( str1 + 1, str1, 9 );
    printf( "Implemented memcpy output: %s\n", str1 );

    strcpy_s( str1, sizeof(str1), "abcdefghij" );   // reset string

    memmoveCustom( str1 + 1, str1, 9 );
    printf( "Implemented memmove output: %s\n", str1 );
    getchar();
}

Output:

The string: abcdefghij
Actual memcpy output: aabcdefghi
Implemented memcpy output: aaaaaaaaaa
Implemented memmove output: aabcdefghi

Tapi sekarang Anda bisa mengerti mengapa memmove akan menangani masalah yang tumpang tindih.

singingsingh
sumber
1

C11 draft standar

The C11 N1570 standar rancangan mengatakan:

7.24.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, perilaku tidak terdefinisi.

7.24.2.2 "Fungsi memmove":

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

Oleh karena itu, setiap tumpang tindih memcpymengarah pada perilaku yang tidak terdefinisi, dan apa pun bisa terjadi: buruk, tidak ada, atau bahkan baik. Bagus jarang terjadi :-)

memmove Namun jelas mengatakan bahwa semuanya terjadi seolah-olah buffer perantara digunakan, jadi jelas tumpang tindih OK.

C ++ std::copylebih memaafkan, dan memungkinkan tumpang tindih: Apakah std :: copy menangani rentang yang tumpang tindih?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
memmovegunakan array sementara ekstra n, jadi apakah itu menggunakan memori tambahan? Tapi bagaimana bisa jika kita belum memberikannya akses ke memori apa pun. (Menggunakan memori 2x).
clmno
@clmno itu dialokasikan pada stack atau malloc seperti fungsi lain yang saya harapkan :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 事件
1
Saya telah mengajukan pertanyaan di sini , mendapat jawaban yang bagus juga. Terima kasih. Lihat posting hackernews Anda yang menjadi viral (yang x86) :)
clmno
-4

Saya telah mencoba menjalankan program yang sama menggunakan eclipse dan itu menunjukkan perbedaan yang jelas antara memcpydan memmove. memcpy()tidak peduli tentang tumpang tindih lokasi memori yang mengakibatkan kerusakan data, sementara memmove()akan menyalin data ke variabel sementara terlebih dahulu dan kemudian menyalin ke lokasi memori aktual.

Saat mencoba menyalin data dari lokasi str1ke str1+2, output dari memcpyadalah " aaaaaa". Pertanyaannya bagaimana? memcpy()akan menyalin satu byte pada satu waktu dari kiri ke kanan. Seperti yang ditunjukkan dalam program Anda " aabbcc" maka semua penyalinan akan terjadi seperti di bawah ini,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() akan menyalin data ke variabel sementara terlebih dahulu dan kemudian menyalin ke lokasi memori aktual.

  1. aabbcc(actual) -> aabbcc(temp)

  2. aabbcc(temp) -> aaabcc(act)

  3. aabbcc(temp) -> aaaacc(act)

  4. aabbcc(temp) -> aaaabc(act)

  5. aabbcc(temp) -> aaaabb(act)

Output adalah

memcpy : aaaaaa

memmove : aaaabb

Pratik Panchal
sumber
2
Selamat datang di Stack Overflow. Silakan baca halaman Tentang segera. Ada berbagai masalah untuk diatasi. Pertama dan terutama, Anda menambahkan jawaban untuk pertanyaan dengan beberapa jawaban dari 18 bulan yang lalu. Untuk menjamin tambahan, Anda perlu memberikan informasi baru yang mengejutkan. Kedua, Anda menentukan Eclipse, tetapi Eclipse adalah IDE yang menggunakan kompiler C, tetapi Anda tidak mengidentifikasi platform di mana kode Anda berjalan atau menggunakan kompiler C Eclipse. Saya tertarik untuk mengetahui bagaimana Anda memastikan memmove()salinan itu ke lokasi perantara. Seharusnya hanya menyalin secara terbalik bila perlu.
Jonathan Leffler
Terima kasih. Tentang kompiler, jadi saya menggunakan kompiler gcc di linux. Ada halaman manual di linux untuk memove yang secara jelas menentukan bahwa memove akan menyalin data dalam variabel sementara untuk menghindari tumpang tindih data. Inilah tautan dari halaman manual itu linux.die.net/man/3/memmove
Pratik Panchal
3
Itu sebenarnya mengatakan "seolah-olah", yang tidak berarti bahwa itulah yang sebenarnya terjadi. Memang itu benar-benar bisa melakukannya dengan cara itu (walaupun akan ada pertanyaan tentang dari mana ia mendapatkan memori cadangan), tetapi saya akan lebih dari sedikit terkejut jika itu yang sebenarnya dilakukan. Jika alamat sumber lebih besar dari alamat target, cukup untuk menyalin dari awal hingga akhir (salin ke depan); jika alamat sumber kurang dari alamat target, itu cukup untuk menyalin dari ujung ke awal (salinan ke belakang). Tidak ada memori tambahan yang dibutuhkan atau digunakan.
Jonathan Leffler
coba jelaskan jawaban Anda dengan data aktual dalam kode, itu akan lebih membantu.
HaseeB Mir