memcpy () vs memmove ()


157

Sto cercando di capire la differenza tra memcpy()e memmove(), e ho letto il testo che memcpy()non si occupa della fonte e della destinazione sovrapposte memmove(), invece.

Tuttavia, quando eseguo queste due funzioni su blocchi di memoria sovrapposti, entrambi danno lo stesso risultato. Ad esempio, prendere il seguente esempio MSDN nella memmove()pagina della guida: -

C'è un esempio migliore per comprendere gli svantaggi memcpye come memmoverisolverlo?

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

Produzione:

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

1
Microsoft CRT ha avuto un memcpy () sicuro per un bel po '.
Hans Passant,

32
Non penso che "sicuro" sia la parola giusta per questo. Un sicuro memcpysarebbe assertche le regioni non si sovrappongano piuttosto che nascondere intenzionalmente bug nel tuo codice.
R .. GitHub smette di aiutare ICE l'

6
Dipende se si intende "sicuro per lo sviluppatore" o "sicuro per l'utente finale". Direi che fare come detto, anche se non conforme agli standard, è la scelta più sicura per l'utente finale.
Kusma,

da glibc 2.19 - non funziona The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen

Puoi anche vedere qui .
Ren

Risposte:


124

Non sono del tutto sorpreso che il tuo esempio non mostri comportamenti strani. Prova invece str1a copiare str1+2e guarda cosa succede dopo. (Potrebbe non fare davvero la differenza, dipende dal compilatore / librerie.)

In generale, memcpy è implementato in modo semplice (ma veloce). Semplicisticamente, esegue il loop dei dati (in ordine), copiandoli da una posizione all'altra. Ciò può comportare la sovrascrittura della fonte mentre viene letta.

Memmove fa più lavoro per assicurarsi che gestisca correttamente la sovrapposizione.

MODIFICARE:

(Sfortunatamente, non riesco a trovare esempi decenti, ma questi lo faranno). Contrasta le implementazioni memcpy e memmove mostrate qui. memcpy esegue semplicemente dei cicli, mentre memmove esegue un test per determinare in quale direzione eseguire il loop per evitare di corrompere i dati. Queste implementazioni sono piuttosto semplici. La maggior parte delle implementazioni ad alte prestazioni sono più complicate (comportano la copia di blocchi di dimensioni di parole alla volta anziché byte).


2
+1 Inoltre, nella seguente implementazione, memmovechiama memcpyin un ramo dopo aver testato i puntatori: student.cs.uwaterloo.ca/~cs350/common/os161-src-html/…
Pascal Cuoq

Suona bene. Sembra che Visual Studio implementa un memcpy "sicuro" (insieme a gcc 4.1.1, ho testato anche su RHEL 5). Scrivere le versioni di queste funzioni da clc-wiki.net dà un'immagine chiara. Grazie.
user534785 l'

3
memcpy non si occupa del problema della sovrapposizione, ma lo fa memmove. Allora perché non eliminare memcpy dalla libreria?
Alcott,

37
@Alcott: perché memcpypuò essere più veloce.
Billy ONeal,

Collegamento fisso / webarchive da Pascal Cuoq sopra: web.archive.org/web/20130722203254/http://…
JWCS

94

La memoria in memcpy non può sovrapporsi o si rischia un comportamento indefinito, mentre la memoria in memmovepuò sovrapporsi.

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. 

Alcune implementazioni di memcpy potrebbero comunque funzionare per input sovrapposti ma non è possibile contare di quel comportamento. Mentre memmove deve consentire la sovrapposizione.


3
mi ha davvero aiutato grazie! +1 per le tue informazioni
Muthu Ganapathy Nathan,

33

Solo perché memcpynon ha a che fare con regioni sovrapposte, non significa che non le tratti correttamente. La chiamata con aree sovrapposte produce un comportamento indefinito. Il comportamento indefinito può funzionare interamente come previsto su una piattaforma; ciò non significa che sia corretto o valido.


10
In particolare, a seconda della piattaforma, è possibile che memcpysia implementato esattamente allo stesso modo di memmove. Cioè, chiunque abbia scritto il compilatore non si è preoccupato di scrivere una memcpyfunzione unica .
Cam

19

Sia memcpy che memove fanno cose simili.

Ma per individuare una differenza:

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

}

dà:

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

IMHO, questo programma di esempio presenta alcuni difetti, poiché al buffer str1 si accede senza limiti (10 byte da copiare, il buffer ha una dimensione di 7 byte). L'errore fuori limite provoca un comportamento indefinito. Le differenze nei risultati mostrati delle chiamate memcpy () / memmove () sono specifiche dell'implementazione. E l'output di esempio non corrisponde esattamente al programma sopra ... Inoltre, strcpy_s () non fa parte dello standard C AFAIK (specifico per MS, vedi anche: stackoverflow.com/questions/36723946/… ) - Correggimi se I sbaglio.
rel

7

La tua demo non ha rivelato svantaggi memcpy a causa del compilatore "cattivo", ti fa un favore nella versione di debug. Una versione di rilascio, tuttavia, ti dà lo stesso output, ma a causa dell'ottimizzazione.

    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  

Il registro %eaxqui viene riprodotto come memoria temporanea, che "elegantemente" risolve il problema di sovrapposizione.

Lo svantaggio emerge quando si copiano 6 byte, beh, almeno in parte.

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

Produzione:

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

Sembra strano, è causato anche dall'ottimizzazione.

    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  

Questo è il motivo per cui scelgo sempre memmovequando provo a copiare 2 blocchi di memoria sovrapposti.


3

La differenza tra memcpye memmoveè quella

  1. in memmove, la memoria di origine della dimensione specificata viene copiata nel buffer e quindi spostata nella destinazione. Quindi se la memoria si sovrappone, non ci sono effetti collaterali.

  2. in caso contrario memcpy(), non è stato utilizzato alcun buffer aggiuntivo per la memoria di origine. La copia viene eseguita direttamente sulla memoria in modo che quando si verifica la sovrapposizione della memoria, si ottengano risultati imprevisti.

Questi possono essere osservati dal seguente codice:

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

L'output è:

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

6
-1 - non è necessario che memmove copi effettivamente i dati in un buffer separato
jjwchoy,

questo esempio non aiuta a comprendere il concetto .... dato che la maggior parte dei compilatori darà lo stesso risultato dell'output di spostamento dei mem
Jasdeep Singh Arora,

1
@jjwchoy Concettualmente lo fa. Il buffer verrebbe normalmente ottimizzato
MM

Lo stesso risultato, su Linux.
CodyChan,

2

Come già sottolineato in altre risposte, memmoveè più sofisticato di memcpycosì da tenere conto delle sovrapposizioni di memoria. Il risultato di memmove è definito come se srcfosse stato copiato in un buffer e quindi copiato nel buffer dst. Ciò NON significa che l'implementazione effettiva utilizzi alcun buffer, ma probabilmente fa un po 'di aritmetica del puntatore.


1

il compilatore potrebbe ottimizzare memcpy, ad esempio:

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

Questo memcpy può essere ottimizzato come: x = *(int*)some_pointer;


3
Tale ottimizzazione è consentita solo su architetture che consentono intaccessi non allineati . Su alcune architetture (ad esempio Cortex-M0), il tentativo di recuperare un 32 bit intda un indirizzo che non è un multiplo di quattro causerà un arresto anomalo (ma memcpyfunzionerebbe). Se uno utilizzerà una CPU che consente l'accesso non allineato o utilizza un compilatore con una parola chiave che indica al compilatore di assemblare interi da byte recuperati separatamente quando necessario, si potrebbe fare qualcosa di simile #define UNALIGNED __unalignede quindi `x = * (int UNALIGNED * ) some_pointer;
supercat

2
Alcuni processori non consentono un arresto dell'accesso int non allineato, char x = "12345"; int *i; i = *(int *)(x + 1);ma altri lo fanno perché riparano la copia durante l'errore. Ho lavorato su un sistema come questo e ci è voluto un po 'di tempo per capire perché le prestazioni erano così scarse.
user3431262

*(int *)some_pointerè una violazione alias rigorosa, ma probabilmente intendi che il compilatore avrebbe prodotto un assembly che copia un int
MM

1

Il codice fornito nei link http://clc-wiki.net/wiki/memcpy per memcpy sembra confondermi un po ', in quanto non dà lo stesso output quando l'ho implementato usando l'esempio seguente.

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

Produzione :

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

Ma ora puoi capire perché memmove si occuperà del problema di sovrapposizione.


1

Tiraggio standard C11

La bozza standard C11 N1570 dice:

7.24.2.1 "La funzione memcpy":

2 La funzione memcpy copia n caratteri dall'oggetto puntato da s2 nell'oggetto puntato da s1. Se la copia avviene tra oggetti che si sovrappongono, il comportamento non è definito.

7.24.2.2 "La funzione memmove":

2 La funzione memmove copia n caratteri dall'oggetto puntato da s2 nell'oggetto puntato da s1. La copia avviene come se gli n caratteri dell'oggetto indicato da s2 vengano prima copiati in un array temporaneo di n caratteri che non si sovrappongono agli oggetti indicati da s1 e s2, quindi gli n caratteri dell'array temporaneo vengono copiati in l'oggetto indicato da s1

Pertanto, qualsiasi sovrapposizione su memcpyporta a comportamenti indefiniti e tutto può succedere: cattivo, niente o addirittura buono. Buono è raro però :-)

memmove tuttavia dice chiaramente che tutto accade come se fosse usato un buffer intermedio, quindi chiaramente le sovrapposizioni sono OK.

Il C ++ std::copyè tuttavia più tollerante e consente sovrapposizioni: std :: copy gestisce intervalli sovrapposti?


memmoveusa un array temporaneo extra di n, quindi usa memoria extra? Ma come può se non gli abbiamo concesso l'accesso a qualsiasi memoria. (Sta usando il doppio della memoria).
clmno

@clmno alloca su stack o malloc come qualsiasi altra funzione che mi aspetto :-)
Ciro Santilli 27 冠状 病 六四 事件 法轮功

1
Avevo fatto una domanda qui , avevo anche una buona risposta. Grazie. Ho visto il tuo post di hackernews diventato virale (quello x86) :)
clmno

-4

Ho provato a eseguire lo stesso programma usando eclipse e mostra una chiara differenza tra memcpye memmove. memcpy()non si preoccupa della sovrapposizione della posizione della memoria che provoca il danneggiamento dei dati, mentre prima memmove()copia i dati nella variabile temporanea e poi li copia nella posizione di memoria effettiva.

Durante il tentativo di copiare i dati dalla posizione str1in str1+2, l'output di memcpyè " aaaaaa". La domanda sarebbe come? memcpy()copierà un byte alla volta da sinistra a destra. Come mostrato nel programma " aabbcc", tutte le copie avverranno come di seguito,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() copierà prima i dati nella variabile temporanea, quindi li copierà nella posizione di memoria effettiva.

  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)

L'output è

memcpy : aaaaaa

memmove : aaaabb


2
Benvenuto in Stack Overflow. Si prega di leggere presto la pagina Informazioni . Ci sono vari problemi da affrontare. Innanzitutto, hai aggiunto una risposta a una domanda con più risposte di circa 18 mesi fa. Per giustificare l'aggiunta, è necessario fornire nuove informazioni sorprendenti. In secondo luogo, si specifica Eclipse, ma Eclipse è un IDE che utilizza un compilatore C, ma non si identifica la piattaforma in cui è in esecuzione il codice o il compilatore C utilizzato da Eclipse. Sarei interessato a sapere come accertare che le memmove()copie in una posizione intermedia. Dovrebbe semplicemente copiare al contrario quando necessario.
Jonathan Leffler,

Grazie. A proposito del compilatore, quindi sto usando il compilatore gcc su Linux. C'è una pagina man in linux per il memove che specifica chiaramente che memove copierà i dati in una variabile temporanea per evitare la sovrapposizione dei dati. Ecco il link di quella pagina man linux.die.net/man/3/memmove
Panchal

3
In realtà dice "come se", il che non significa che sia ciò che realmente accade. Concesso che potrebbe effettivamente farlo in quel modo (anche se ci sarebbero domande su da dove ottiene la memoria di riserva), ma sarei più che un po 'sorpreso se fosse quello che effettivamente fa. Se l'indirizzo di origine è maggiore dell'indirizzo di destinazione, è sufficiente copiarlo dall'inizio alla fine (copia in avanti); se l'indirizzo di origine è inferiore all'indirizzo di destinazione, è sufficiente copiarlo dalla fine all'inizio (copia all'indietro). Non è necessaria o utilizzata memoria ausiliaria.
Jonathan Leffler,

prova a spiegare la tua risposta con dati reali in codice, che sarebbe più utile.
HaseeB Mir,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.