memcpy () vs memmove ()


157

Ben arasındaki farkı anlamaya çalışıyorum memcpy()ve memmove(), ve ben metni okumak zorunda memcpy()oysa örtüşen kaynağın bakım ve hedef almaz memmove()yapar.

Ancak, bu iki işlevi üst üste gelen bellek bloklarında yürüttüğümde, ikisi de aynı sonucu verir. Örneğin, memmove()yardım sayfasında aşağıdaki MSDN örneğini alın : -

Bunun dezavantajlarını memcpyve nasıl memmoveçözdüğünü anlamak için daha iyi bir örnek var mı ?

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

Çıktı:

The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb

1
Microsoft CRT'nin bir süredir güvenli bir memcpy () vardır.
Hans Passant

32
Bunun için "güvenli" nin doğru kelime olduğunu düşünmüyorum. Güvenli memcpyolur assertbölgeler kasıtlı kodunuzu böcek kaplanması yerine örtüşme bilmediğimiz.
R .. GitHub DURDURMA BUZA YARDIMCI OLMAK

6
"Geliştirici için güvenli" veya "son kullanıcı için güvenli" anlamına gelmenize bağlıdır. Söylendiği gibi yapmanın standartlara uygun olmasa bile son kullanıcı için daha güvenli bir seçim olduğunu iddia ediyorum.
kusma

glibc 2.19 beri - çalışmıyor The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen

Burada da görebilirsiniz .
Ren

Yanıtlar:


124

Örneğinizin garip bir davranış sergilemesine tamamen şaşırmadım. Kopyalamayı deneyin str1için str1+2yerine ve sonra ne olduğunu görün. (Aslında bir fark yaratmayabilir, derleyiciye / kütüphanelere bağlıdır.)

Genel olarak, memcpy basit (ama hızlı) bir şekilde uygulanır. Basitçe, sadece bir konumdan diğerine kopyalayarak verilerin (sırayla) üzerine döner. Bu, kaynağın okunurken üzerine yazılmasına neden olabilir.

Memmove, örtüşmeyi doğru şekilde ele almak için daha fazla iş yapar.

DÜZENLE:

(Ne yazık ki, iyi örnekler bulamıyorum, ancak bunlar yapacak). Kontrast memcpy ve memmove Burada gösterilen uygulamaları. memcpy sadece döngüler, memmove ise verilerin bozulmasını önlemek için hangi yönde döngü yapılacağını belirlemek için bir test gerçekleştirir. Bu uygulamalar oldukça basittir. Çoğu yüksek performanslı uygulama daha karmaşıktır (bayt yerine bir seferde kelime boyutu blokları kopyalamayı içerir).


2
1 Ayrıca, aşağıdaki uygulanmasında, memmoveçağırır memcpyişaretçileri test ettikten sonra bir dalında: student.cs.uwaterloo.ca/~cs350/common/os161-src-html/...
Pascal Cuoq

Kulağa harika geliyor. Visual Studio "güvenli" bir memcpy uygulamaktadır gibi görünüyor (gcc 4.1.1 ile birlikte, ben de RHEL 5 üzerinde test). Bu işlevlerin sürümlerini clc-wiki.net adresinden yazmak, net bir resim verir. Teşekkürler.
user534785

3
memcpy çakışan konuyla ilgilenmez, ama memmove yapar. Öyleyse neden memcpy'yi lib'den kaldırmıyorsunuz?
Alcott

37
@Alcott: Çünkü memcpydaha hızlı olabilir.
Billy ONeal

Yukarıdaki Pascal Cuoq'dan sabit / webarchive bağlantısı: web.archive.org/web/20130722203254/http://…
JWCS

95

Bellek üst üste memcpy gelemiyor veya tanımlanmamış davranış riskiyle karşı karşıyayken, bellek memmoveüst üste gelebilir.

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. 

Bazı memcpy uygulamaları çakışan girdiler için çalışmaya devam edebilir, ancak bu davranışı sayamazsınız. Memmove örtüşmeye izin vermelidir.


3
gerçekten thaks bana yardımcı oldu! Bilginiz için +1
Muthu Ganapathy Nathan

33

Çünkü memcpyörtüşen bölgeler ile uğraşmak zorunda değildir, doğru onlarla uğraşmaz anlamına gelmez. Çakışan bölgelere sahip çağrı, tanımlanmamış davranış üretir. Tanımsız davranış tamamen tek bir platformda beklediğiniz gibi çalışabilir; bu doğru veya geçerli olduğu anlamına gelmez.


10
Özellikle, platforma bağlı olarak, memcpytam olarak aynı şekilde uygulanması mümkündür memmove. Yani, derleyiciyi yazan kişi benzersiz bir memcpyişlev yazmaya uğraşmadı .
Cam

19

Memcpy ve memove da benzer şeyler yapıyor.

Ancak bir farkı gözden geçirmek için:

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

}

verir:

The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef

IMHO, bu örnek programın bazı kusurları vardır, çünkü str1 arabelleğine sınırlardan erişilir (kopyalanacak 10 bayt, tampon 7 bayt boyutundadır). Sınır dışı hatası, tanımlanmamış davranışa neden olur. Memcpy () / memmove () çağrılarının gösterilen sonuçlarındaki farklılıklar uygulamaya özeldir. Ve örnek çıktısı yukarıdaki programla tam olarak eşleşmiyor ... Ayrıca, strcpy_s () standart C AFAIK'in bir parçası değildir (MS'e özel, ayrıca bakınız: stackoverflow.com/questions/36723946/… ) - Lütfen ben yanılıyorum.
rel

7

Demo, "kötü" derleyici nedeniyle memcpy dezavantajları ortaya koymadı, Debug sürümünde bir iyilik yapar. Ancak sürüm, optimizasyon nedeniyle aynı çıktıyı verir.

    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  

Buradaki kayıt %eax, "zarif bir şekilde" örtüşme sorununu gideren geçici bir depolama alanı olarak oynar.

Dezavantajı, en azından bir kısmı 6 bayt kopyalanırken ortaya çıkar.

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

Çıktı:

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

Tuhaf görünüyor, bunun nedeni optimizasyon.

    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  

Bu yüzden her memmovezaman çakışan 2 bellek bloğunu kopyalamaya çalışırken seçim yapıyorum .


3

Arasındaki fark memcpyve memmoveolmasıdır

  1. içinde memmove, belirtilen boyuttaki kaynak bellek arabelleğe kopyalanır ve sonra hedefe taşınır. Bellek üst üste biniyorsa, hiçbir yan etkisi yoktur.

  2. durumunda memcpy(), kaynak bellek için alınan ekstra tampon yoktur. Kopyalama doğrudan bellekte yapılır, böylece bellek çakışması olduğunda beklenmedik sonuçlar elde ederiz.

Bunlar aşağıdaki kod ile gözlemlenebilir:

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

Çıktı:

hare hare rama hare rama
hare hare hare hare hare hare rama hare rama

6
-1 - memmove'nin verileri ayrı bir ara belleğe kopyalaması için herhangi bir gereklilik yoktur
jjwchoy

Bu örnek kavramın anlaşılmasına yardımcı olmaz .... derleyicilerin çoğu mem hamlesi ile aynı şeyi verecektir
Jasdeep Singh Arora

1
@jjwchoy Kavramsal olarak öyle. Tampon genellikle optimize edilir
MM

Aynı sonuç, Linux'ta.
CodyChan

2

Zaten diğer cevaplarda belirtildiği gibi, bellek çakışmalarını açıklayacak memmoveşekilde daha karmaşıktır memcpy. Memmove sonucu, srcarabellek içine kopyalanmış ve sonra ara belleğe kopyalanmış gibi tanımlanır dst. Bu, gerçek uygulamanın herhangi bir arabellek kullandığı anlamına gelmez, ancak muhtemelen bazı işaretçi aritmetiği kullanır.


1

derleyici memcpy'ı optimize edebilir, örneğin:

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

Bu memcpy şu şekilde optimize edilebilir: x = *(int*)some_pointer;


3
Böyle bir optimizasyona yalnızca hizalanmamış erişime izin veren mimarilerde izin verilir int. Bazı mimarilerde (örn. Cortex-M0), intdördün katı olmayan bir adresten 32 bit getirmeye çalışmak çökmeye neden olur (ancak memcpyişe yarayacaktır). Biri, atanmamış erişime izin veren bir CPU veya gerektiğinde, derleyiciyi tamsayıları ayrı ayrı getirilen baytlardan birleştirmeye yönlendiren bir anahtar sözcük içeren bir derleyici kullanacaksa, şöyle bir şey yapabilir #define UNALIGNED __unalignedve `` = = * (int UNALIGNED * ) some_pointer;
supercat

2
Bazı işlemciler hizalanmamış int erişim çökmesine izin vermez char x = "12345"; int *i; i = *(int *)(x + 1);Ancak bazıları bunu yapar, çünkü hata sırasında kopyayı düzeltirler. Böyle bir sistem üzerinde çalıştım ve performansın neden bu kadar zayıf olduğunu anlamak biraz zaman aldı.
user3431262

*(int *)some_pointerkatı bir takma ad ihlalidir, ancak muhtemelen derleyicinin bir int kopyalayan derleme çıktısı alacağını kastediyorsunuz
MM

1

Memcpy için http://clc-wiki.net/wiki/memcpy bağlantılarında verilen kod , aşağıdaki örneği kullanarak uyguladığımda aynı çıktıyı vermediği için beni biraz karıştırıyor gibi görünüyor.

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

Çıktı :

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

Ancak şimdi, memmove'un neden çakışan sorunu ele alacağını anlayabilirsiniz .


1

C11 standart taslak

C11 N1570 standart taslağı diyor ki:

7.24.2.1 "Memcpy işlevi":

2 Memcpy işlevi s2 ile gösterilen nesneden n1 ile gösterilen nesneye n karakter kopyalar. Kopyalama çakışan nesneler arasında gerçekleşiyorsa, davranış tanımsızdır.

7.24.2.2 "Memmove işlevi":

2 Memmove işlevi, s2 ile gösterilen nesneden n1 ile gösterilen nesneye n karakter kopyalar. Kopyalama, s2 ile gösterilen nesneden gelen n karakter, ilk olarak s1 ve s2 ile gösterilen nesnelerle çakışmayan geçici bir n karakter dizisine kopyalanır ve daha sonra geçici diziden n karakter kopyalanır s1 ile gösterilen nesne

Bu nedenle, herhangi bir çakışma memcpytanımlanmamış davranışa yol açar ve her şey olabilir: kötü, hiçbir şey veya hatta iyi. İyi olsa da nadirdir :-)

memmove ancak her şeyin bir ara tampon kullanılmış gibi olduğunu açıkça belirtir, bu yüzden açıkça örtüşmeler uygundur.

std::copyAncak C ++ daha bağışlayıcıdır ve örtüşmelere izin verir: std :: copy çakışan aralıkları işliyor mu?


memmovefazladan geçici bir n dizisi kullanın, bu yüzden fazladan bellek kullanıyor mu? Ama herhangi bir belleğe erişim vermediysek nasıl olur? (Hafızayı 2 kat kullanıyor).
4'te clmno

@clmno beklediğim herhangi bir işlev gibi yığın veya malloc tahsis :-)
Ciro Santilli :30 冠状 病 六四 事件 法轮功

1
Burada bir soru sordum , iyi bir cevap aldım. Teşekkür ederim. Viral gitti hackernews yazı gördüm (x86 bir) :)
clmno

-4

Ben tutulmayı kullanarak aynı programı çalıştırmak için denedim ve arasında net bir fark göstermektedir memcpyve memmove. memcpy()verilerin bozulmasına neden olan bellek konumunun çakışmasını umursamazken, memmove()önce verileri geçici değişkene kopyalayıp gerçek bellek konumuna kopyalar.

Yerden veri kopyalamak çalışırken str1üzere str1+2, çıkış arasında memcpy"dir aaaaaa". Soru nasıl olurdu? memcpy()soldan sağa her defasında bir bayt kopyalar. " aabbcc" Programınızda gösterildiği gibi, tüm kopyalama işlemleri aşağıdaki gibi gerçekleşir,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() önce verileri geçici değişkene kopyalar ve ardından gerçek bellek konumuna kopyalar.

  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)

Çıktı

memcpy : aaaaaa

memmove : aaaabb


2
Stack Overflow'a hoş geldiniz. Lütfen yakında Hakkında sayfasını okuyun . Ele alınması gereken çeşitli konular var. Her şeyden önce, bir soruya 18 aydan daha önce birden fazla cevap içeren bir cevap eklediniz. Eklemeyi garanti etmek için şaşırtıcı yeni bilgiler sağlamanız gerekir. İkincisi, Eclipse belirtirsiniz, ancak Eclipse bir C derleyicisi kullanan bir IDE'dir, ancak kodunuzun çalıştığı veya Eclipse'nin C derleyicisinin kullandığı platformu tanımlamazsınız. memmove()Ara bir yere kopyalandığını nasıl belirlediğinizi bilmek isterim . Gerektiğinde tersine kopyalamalıdır.
Jonathan Leffler

Teşekkürler. Derleyici hakkında, bu yüzden linux üzerinde gcc derleyici kullanıyorum. Linux'ta memove için verilerin çakışmasını önlemek için memove'un geçici değişkente verileri kopyalayacağını açıkça belirten bir man sayfası vardır. İşte bu adam sayfasının linki linux.die.net/man/3/memmove
Pratik

3
Aslında "sanki" der, bu aslında olan şey olduğu anlamına gelmez. O Verilen olabilir (bu yedek bellek alır nerede ilgili sorular olurdu gerçi) aslında bu şekilde yapmak, ama bu aslında ne ise biraz sürpriz fazla olacaktır. Kaynak adres hedef adresten büyükse, baştan sona kopyalamak yeterlidir (ileriye doğru kopya); kaynak adres hedef adresten azsa, baştan sona kopyalamak yeterlidir (geriye doğru kopya). Yardımcı hafıza gerekmez veya kullanılmaz.
Jonathan Leffler

cevabınızı koddaki gerçek verilerle açıklamaya çalışın, bu daha yararlı olacaktır.
HaseeB Mir
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.