Memmove ve memcpy arasındaki fark nedir?


Yanıtlar:


162

İle memcpy, hedef kaynakla hiçbir şekilde örtüşemez. Onunla memmoveolabilir. Bu , aynı varsayımları yapamayacağından memmoveçok az daha yavaş olabileceği anlamına gelir memcpy.

Örneğin, memcpyadresleri her zaman düşükten yükseğe kopyalayabilir. Hedef kaynaktan sonra çakışırsa, bu kopyalanmadan önce bazı adreslerin üzerine yazılacağı anlamına gelir. memmovebu durumda bunu algılar ve diğer yönde (yüksekten alta doğru) kopyalar. Ancak, bunu kontrol etmek ve başka (muhtemelen daha az verimli) bir algoritmaya geçmek zaman alır.


1
memcpy kullanırken, src ve dest adreslerinin çakışmayacağını nasıl garanti edebilirim? Src ve dest'in çakışmadığından kişisel olarak emin olmalı mıyım?
Alcott

6
@Alcott, çakışmadıklarını bilmiyorsanız memcpy kullanmayın - bunun yerine memmove kullanın. Örtüşme olmadığında memmove ve memcpy eşdeğerdir (memcpy çok, çok, çok az daha hızlı olsa da).
bdonlan

Uzun dizilerle çalışıyorsanız ve kopyalama işleminizi korumak istiyorsanız 'restrict' anahtar sözcüğünü kullanabilirsiniz. Örneğin, yönteminiz girdi ve çıktı dizilerini parametre olarak alırsa ve kullanıcının girdi ve çıktı ile aynı adresi geçmediğini doğrulamanız gerekir. Buradan daha fazlasını okuyun stackoverflow.com/questions/776283/…
DanielHsH

10
@DanielHsH 'restrict' derleyiciye vereceğiniz sözdür; Bu değildir zorunlu derleyici tarafından. Argümanlarınıza 'restrict' koyarsanız ve aslında çakışırsanız (veya daha genel olarak, kısıtlanmış verilere birden çok yerden türetilen göstericiden erişirseniz), programın davranışı tanımsızdır, garip hatalar meydana gelir ve derleyici genellikle sizi bu konuda uyarmaz.
bdonlan

@bdonlan Bu sadece derleyiciye verilmiş bir söz değil, arayanınız için bir gerekliliktir. Zorunlu olmayan bir gerekliliktir, ancak bir gereksinimi ihlal ederseniz, beklenmedik sonuçlar alırsanız şikayet edemezsiniz. Bir gereksinimi ihlal etmek, tanımsız olduğu gibi tanımlanmamış bir davranıştır i = i++ + 1; derleyici tam olarak bu kodu yazmanızı yasaklamaz, ancak bu talimatın sonucu herhangi bir şey olabilir ve farklı derleyiciler veya CPU'lar burada farklı değerler gösterecektir.
Mecki

33

memmoveörtüşen belleği memcpykaldırabilir, yapamaz.

Düşünmek

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

Açıkçası kaynak ve hedef artık örtüşüyor, "bar" ile "-bar" ın üzerine yazıyoruz. memcpyKaynak ve hedefin örtüşmesi, bu durumda ihtiyacımız olan durumlarda tanımsız bir davranıştır memmove.

memmove(&str[3],&str[4],4); //fine

5
neden ilk önce patlasın?

4
@ultraman: Çünkü belleğin çakışmamasını gerektiren düşük seviyeli assembley kullanılarak uygulanmış OLABİLİR. Bunu yaparsa, örneğin, işlemciye uygulamayı durduran bir sinyal veya donanım istisnası üretebilirsiniz. Belgeler, durumu ele almadığını belirtir, ancak standart, bu koşullar vialo uygulandığında ne olacağını belirtmez (bu, tanımlanmamış davranış olarak bilinir). Tanımlanmamış davranış her şeyi yapabilir.
Martin York

gcc 4.8.2 ile, Memcpy bile örtüşen kaynak ve hedef işaretçileri kabul eder ve sorunsuz çalışır.
GeekyJ

4
@jagsgediya Elbette olabilir. Ancak memcpy'nin bunu desteklemediği belgelendiğinden, bu uygulamaya özgü davranışa güvenmemelisiniz, bu yüzden memmove () vardır. Gcc'nin başka bir sürümünde farklı olabilir. Eğer gcc, glibc'de memcpy () 'i çağırmak yerine memcpy'yi satır içi yaparsa farklı olabilir, glibc'nin daha eski veya daha yeni bir sürümünde farklı olabilir.
hayır

Uygulamaya göre memcpy ve memmove aynı şeyi yaptı. Böyle derin tanımsız bir davranış.
Life

22

Gönderen memcpy adam sayfası.

Memcpy () işlevi, bellek alanı src'den bellek alanı hedef'e n bayt kopyalar. Hafıza alanları çakışmamalıdır. Bellek alanları çakışırsa memmove (3) kullanın .


12

Arasındaki temel fark memmove()ve memcpy()içinde olmasıdır memmove()bir tampon geçici hafıza - - kullanılır, bu nedenle örtüşen riski yoktur. Öte yandan, memcpy()veriyi kaynağın gösterdiği konumdan hedefin gösterdiği konuma doğrudan kopyalar . ( http://www.cplusplus.com/reference/cstring/memcpy/ )

Aşağıdaki örnekleri düşünün:

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

    Beklediğiniz gibi, bu çıktı:

    stackoverflow
    stackstacklow
    stackstacklow
  2. Ancak bu örnekte sonuçlar aynı olmayacak:

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

    Çıktı:

    stackoverflow
    stackstackovw
    stackstackstw

Bunun nedeni "memcpy ()" nin aşağıdakileri yapmasıdır:

1.  stackoverflow
2.  stacksverflow
3.  stacksterflow
4.  stackstarflow
5.  stackstacflow
6.  stackstacklow
7.  stackstacksow
8.  stackstackstw

2
Ancak, bahsettiğiniz çıktı tersine çevrilmiş gibi görünüyor !!
kumar

1
Aynı programı çalıştırdığımda şu sonucu alıyorum: stackoverflow stackstackstw stackstackstw // memcpy ve memmove arasında çıktıda HİÇBİR fark olmadığı anlamına gelir
kumar

4
"memmove ()" içinde bir tampon - geçici bir bellek - kullanılmasıdır; " Doğru değil. "Sanki" diyor, bu yüzden sadece böyle davranması gerekiyor, öyle olması gerekmiyor. Çoğu memmove uygulaması sadece bir XOR-takas yaptığı için bu gerçekten önemlidir.
dhein

2
memmove()Bir tampon kullanmak için uygulamasının gerekli olduğunu düşünmüyorum . Tamamen yerinde taşıma hakkına sahiptir (her okuma aynı adrese herhangi bir yazmadan önce tamamlandığı sürece).
Toby Speight

12

Her ikisini de uygulamanız gerektiğini varsayarsak, uygulama şöyle görünebilir:

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

Ve bu, farkı oldukça iyi açıklamalı. memmoveHala eğer güvenli şekilde her zaman kopya srcve dstörtüşme, oysa memcpykullanırken dokümantasyon söylediği gibi sadece umursamıyor memcpyiki bellek alanları, olmamalıdır örtüşme.

Örneğin, memcpy"önden arkaya" kopyalar ve bellek blokları bu şekilde hizalanmışsa

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

ilk baytı kopyalama srciçin dstşimdiden son bayt içeriği yok eden srcbu kopyalanmış önce. Yalnızca "arkadan öne" kopyalamak doğru sonuçlara yol açar.

Şimdi değiştirin srcve dst:

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

Bu durumda sadece "önden arkaya" kopyalamak güvenlidir, çünkü "arkadan öne" srckopyalamak, ilk baytı kopyalarken zaten ön tarafının yakınında yok olur.

memmoveYukarıdaki uygulamanın gerçekten örtüşüp örtüşmediğini test etmediğini bile fark etmişsinizdir, sadece göreceli konumlarını kontrol eder, ancak bu tek başına kopyayı güvenli hale getirecektir. Gibi memcpy, genellikle herhangi bir sistem üzerinde bellek kopyalamak mümkün olan en hızlı şekilde kullanır, memmovegenellikle oldukça olarak uygulanır:

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

Bazen, memcpyher zaman "önden arkaya" veya "arkadan öne" kopyalarsa , çakışan durumlardan birinde memmovede kullanılabilir memcpy, ancak memcpyverilerin nasıl hizalandığına ve / veya ne kadar verinin olacağına bağlı olarak farklı bir şekilde bile kopyalayabilir kopyalanır, bu nedenle memcpysisteminizde nasıl kopyaları test etseniz bile , o test sonucunun her zaman doğru olduğuna güvenemezsiniz.

Hangisini arayacağınıza karar verirken bu sizin için ne anlama geliyor?

  1. Bundan emin olmadığınız srcve dstçakışmadığınızdan emin değilseniz memmove, her zaman doğru sonuçlara yol açacağı ve genellikle ihtiyaç duyduğunuz kopya vakası için mümkün olduğu kadar hızlı olacağı için arayın .

  2. Bundan eminseniz srcve dstçakışmıyorsanız, memcpysonuç için hangisini aradığınız önemli olmayacağı için arayın, bu durumda her ikisi de doğru şekilde çalışacak, ancak memmoveasla daha hızlı olmayacak memcpyve şanssızsanız, hatta olabilir daha yavaş olun, böylece yalnızca aramayı kazanabilirsiniz memcpy.


+1 çünkü "ascii çizimleriniz" verileri bozmadan neden üst üste binme olmayabileceğini anlamak için
yararlıydı

10

Biri örtüşen hedefleri yönetir, diğeri yapmaz.


6

basitçe ISO / IEC: 9899 standardından iyi tanımlanmıştır.

7.21.2.1 Memcpy işlevi

[...]

2 memcpy işlevi s2 ile gösterilen nesneden n karakteri s1 ile gösterilen nesneye kopyalar. Örtüşen nesneler arasında kopyalama gerçekleşirse, davranış tanımsızdır.

Ve

7.21.2.2 memmove işlevi

[...]

2 memmove işlevi s2 ile gösterilen nesneden n karakteri s1 ile gösterilen nesneye kopyalar. Kopyalama, s2 ile gösterilen nesnedeki n karakter ilk önce s1 ve s2 ile gösterilen nesnelerle örtüşmeyen geçici bir n karakter dizisine kopyalanır ve ardından geçici dizideki n karakterleri içine kopyalanır gibi gerçekleşir. s1 ile gösterilen nesne.

Soruya göre genellikle hangisini kullandığım, ihtiyacım olan işlevselliğe bağlıdır.

Düz metinde memcpy()izin vermez s1ve s2örtüşmez memmove().


0

Uygulamanın iki bariz yolu vardır mempcpy(void *dest, const void *src, size_t n)(dönüş değerini göz ardı ederek):

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

İlk uygulamada, kopya düşük adreslerden yüksek adreslere ve ikinci uygulamada yüksekten alçağa doğru ilerler. Kopyalanacak aralık çakışırsa (örneğin bir çerçeve arabelleğini kaydırırken olduğu gibi), o zaman yalnızca bir işlem yönü doğrudur ve diğeri daha sonra okunacak konumların üzerine yazacaktır.

Bir memmove()uygulama, en basit haliyle, test edecek dest<src(platforma bağlı bir şekilde) ve uygun yönünü çalıştıracaktır memcpy().

Kullanıcı kodu elbette bunu yapamaz, çünkü dökümden sonra srcve dstbazı somut işaretçi türlerinde bile (genel olarak) aynı nesneye işaret etmezler ve bu nedenle karşılaştırılamazlar. Ancak standart kitaplık, Tanımsız Davranışa neden olmadan böyle bir karşılaştırmayı gerçekleştirmek için yeterli platform bilgisine sahip olabilir.


Gerçek hayatta uygulamaların daha büyük aktarımlardan (hizalama izin verdiğinde) ve / veya iyi veri önbelleği kullanımından maksimum performans elde etmek için önemli ölçüde daha karmaşık olma eğiliminde olduğunu unutmayın. Yukarıdaki kod, konuyu olabildiğince basitleştirmek içindir.


0

memmove çakışan kaynak ve hedef bölgelerle başa çıkabilirken memcpy bunu yapamaz. İkisi arasında memcpy çok daha verimlidir. Yani, eğer mümkünse memcpy'I KULLANMAK daha iyi.

Referans: https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr. Jerry Cain, (Stanford Intro Systems Lecture - 7) Saat: 36:00


Bu cevap "muhtemelen daha hızlı" diyor ve sadece küçük bir farkı gösteren nicel veriler sağlıyor. Bu cevap, birinin "çok daha verimli" olduğunu ileri sürer. Daha hızlı olanı ne kadar verimli buldunuz? BTW: Kastettiğini memcpy()ve olmadığını varsayıyorum memcopy().
chux - Monica'yı yeniden etkinleştir

Yorum, Dr. Jerry Cain'in dersine dayanılarak yapılmıştır. Saat 36: 00'daki dersini dinlemenizi rica ediyorum, sadece 2-3 dakika yeterli olacaktır. Ve yakaladığın için teşekkürler. : D
Ehsan
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.