Qual'è la differenza tra memmove e memcpy?


Risposte:


162

Con memcpy, la destinazione non può affatto sovrapporsi alla sorgente. Con memmoveesso può. Ciò significa che memmovepotrebbe essere leggermente più lento di memcpy, in quanto non può fare le stesse ipotesi.

Ad esempio, memcpypotrebbe sempre copiare gli indirizzi da basso ad alto. Se la destinazione si sovrappone alla sorgente, significa che alcuni indirizzi verranno sovrascritti prima di essere copiati. memmovelo rileverebbe e copierebbe nell'altra direzione, dall'alto verso il basso, in questo caso. Tuttavia, controllarlo e passare a un altro algoritmo (possibilmente meno efficiente) richiede tempo.


1
quando si usa memcpy, come posso garantire che gli indirizzi src e dest non si sovrappongano? Devo assicurarmi personalmente che src e dest non si sovrappongano?
Alcott

6
@Alcott, non usare memcpy se non sai che non si sovrappongono - usa invece memmove. Quando non c'è sovrapposizione, memmove e memcpy sono equivalenti (sebbene memcpy potrebbe essere molto, molto, leggermente più veloce).
bdonlan

È possibile utilizzare la parola chiave "restringere" se si lavora con array lunghi e si desidera proteggere il processo di copia. Ad esempio, se il metodo accetta come parametri input e output array e si deve verificare che l'utente non passi lo stesso indirizzo di input e output. Per saperne di più qui stackoverflow.com/questions/776283/...
DanielHsH

10
@DanielHsH 'Limit' è una promessa che fai al compilatore; non è applicato dal compilatore. Se metti "restringere" i tuoi argomenti e, di fatto, si sovrappongono (o più in generale, accedi ai dati limitati dal puntatore derivato da più posizioni), il comportamento del programma è indefinito, si verificheranno strani bug e il compilatore di solito non ti avviserà.
bdonlan

@bdonlan Non è solo una promessa al compilatore, è un requisito per il chiamante. È un requisito che non viene applicato, ma se si viola un requisito, non è possibile lamentarsi se si ottengono risultati imprevisti. Violare un requisito è un comportamento indefinito, proprio come lo i = i++ + 1è undefined; il compilatore non ti vieta di scrivere esattamente quel codice ma il risultato di quell'istruzione può essere qualsiasi cosa e diversi compilatori o CPU mostreranno valori diversi qui.
Mecki

33

memmovepuò gestire la sovrapposizione della memoria, memcpyno.

Tener conto di

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

Ovviamente l'origine e la destinazione ora si sovrappongono, sovrascriviamo "-bar" con "bar". È un comportamento indefinito memcpyse l'origine e la destinazione si sovrappongono, quindi in questo caso abbiamo bisogno di memmove.

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

5
perché prima dovrebbe saltare in aria?

4
@ultraman: Perché POTREBBE essere stato implementato utilizzando un assemblaggio di basso livello che richiede che la memoria non si sovrapponga. In tal caso, ad esempio potresti generare un segnale o un'eccezione hardware al processore che interrompe l'applicazione. La documentazione specifica che non gestisce la condizione, ma lo standard non specifica cosa accadrà quando queste condizioni vengono attivate (questo è noto come comportamento indefinito). Un comportamento indefinito può fare qualsiasi cosa.
Martin York

con gcc 4.8.2, anche memcpy accetta anche puntatori di origine e destinazione sovrapposti e funziona bene.
GeekyJ

4
@jagsgediya Certo che potrebbe. Ma poiché memcpy è documentato per non supportarlo, non dovresti fare affidamento su quel comportamento specifico dell'implementazione, ecco perché esiste memmove (). Potrebbe essere diverso in un'altra versione di gcc. Potrebbe essere diverso se gcc inline memcpy invece di chiamare memcpy () in glibc, potrebbe essere diverso su una versione più vecchia o più recente di glibc e così via.
nn

Dalla pratica, sembra che memcpy e memmove abbiano fatto la stessa cosa. Un comportamento così profondo e indefinito.
Vita

22

Dalla pagina man di memcpy .

La funzione memcpy () copia n byte dall'area di memoria src all'area di memoria dest. Le aree di memoria non dovrebbero sovrapporsi. Usa memmove (3) se le aree di memoria si sovrappongono.


12

La differenza principale tra memmove()e memcpy()è che in memmove()un buffer viene utilizzata la memoria temporanea, quindi non vi è alcun rischio di sovrapposizione. D'altra parte, memcpy()copia direttamente i dati dalla posizione puntata dalla sorgente alla posizione puntata dalla destinazione . ( http://www.cplusplus.com/reference/cstring/memcpy/ )

Considera i seguenti esempi:

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

    Come previsto, verrà stampato:

    stackoverflow
    stackstacklow
    stackstacklow
  2. Ma in questo esempio, i risultati non saranno gli stessi:

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

    Produzione:

    stackoverflow
    stackstackovw
    stackstackstw

È perché "memcpy ()" fa quanto segue:

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

2
Ma sembra che l'output che hai citato sia invertito !!
kumar

1
Quando eseguo lo stesso programma, ottengo il seguente risultato: stackoverflow stackstackstw stackstackstw // significa che NON c'è differenza nell'output tra memcpy e memmove
kumar

4
"è che in" memmove () "viene utilizzato un buffer - una memoria temporanea;" Non è vero. dice "come se" quindi deve comportarsi così, non che deve essere in quel modo. Questo è davvero rilevante in quanto la maggior parte delle implementazioni di memmove fa solo uno scambio XOR.
Dhein

2
Non penso che l'implementazione di memmove()sia richiesta per utilizzare un buffer. È perfettamente autorizzato a spostarsi sul posto (a condizione che ogni lettura venga completata prima di qualsiasi scrittura allo stesso indirizzo).
Toby Speight

12

Supponendo che dovresti implementare entrambi, l'implementazione potrebbe essere simile a quella:

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

E questo dovrebbe spiegare abbastanza bene la differenza. memmovecopia sempre in modo tale che sia comunque sicuro se srce si dstsovrappongono, mentre memcpyproprio non interessa come dice la documentazione quando si usa memcpy, le due aree di memoria non devono sovrapporsi.

Ad esempio, se le memcpycopie "dalla parte anteriore a quella posteriore" ei blocchi di memoria sono allineati in questo modo

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

la copia del primo byte di srcto dstdistrugge già il contenuto degli ultimi byte di srcprima che questi siano stati copiati. Solo la copia "dal retro al davanti" porterà a risultati corretti.

Ora scambia srce dst:

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

In questo caso è sicuro copiare solo "da davanti a dietro" poiché la copia "da dietro a davanti" distruggerebbe srcla parte anteriore già quando si copia il primo byte.

Potresti aver notato che l' memmoveimplementazione sopra non verifica nemmeno se effettivamente si sovrappongono, controlla solo le loro posizioni relative, ma questo da solo renderà la copia sicura. Poiché di memcpysolito utilizza il modo più veloce possibile per copiare la memoria su qualsiasi sistema, di memmovesolito è piuttosto implementato come:

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

A volte, se memcpycopia sempre "da davanti a dietro" o "da dietro a davanti", memmovepuò anche essere utilizzato memcpyin uno dei casi sovrapposti, ma memcpypuò anche copiare in un modo diverso a seconda di come i dati sono allineati e / o di quanti dati devono essere copiato, quindi anche se hai testato come memcpycopia sul tuo sistema, non puoi fare affidamento sul risultato del test per essere sempre corretto.

Cosa significa per te quando decidi quale chiamare?

  1. A meno che tu non lo sappia per certo srce dstnon si sovrapponga, chiama memmovein quanto porterà sempre a risultati corretti ed è solitamente il più veloce possibile per il caso di copia richiesto.

  2. Se lo sai per certo srce dstnon si sovrappongono, chiama memcpyperché non importa quale chiami per il risultato, entrambi funzioneranno correttamente in quel caso, ma memmovenon saranno mai più veloci di memcpye se sei sfortunato, potrebbe anche sii più lento, così puoi vincere solo chiamate memcpy.


+1 perché i tuoi "disegni ascii" sono stati utili per capire perché potrebbero non esserci sovrapposizioni senza
alterare

10

Uno gestisce le destinazioni sovrapposte, l'altro no.


6

semplicemente dallo standard ISO / IEC: 9899 è ben descritto.

7.21.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 è indefinito.

E

7.21.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 puntato da s2 venissero prima copiati in un array temporaneo di n caratteri che non si sovrappone agli oggetti puntati da s1 e s2, quindi gli n caratteri dall'array temporaneo vengono copiati in l'oggetto puntato da s1.

Quale utilizzo di solito in base alla domanda, dipende dalla funzionalità di cui ho bisogno.

In testo normale memcpy()non consente s1e s2di sovrapporsi, mentre lo memmove()fa.


0

Esistono due modi ovvi per implementare mempcpy(void *dest, const void *src, size_t n)(ignorando il valore restituito):

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

Nella prima implementazione, la copia procede da indirizzi bassi a indirizzi alti e nella seconda da indirizzi alti a bassi. Se l'intervallo da copiare si sovrappone (come nel caso dello scorrimento di un framebuffer, ad esempio), solo una direzione di funzionamento è corretta e l'altra sovrascriverà le posizioni che verranno successivamente lette.

Un memmove()attuazione, nel modo più semplice, verificherà dest<src(in qualche modo dipendente dalla piattaforma), ed eseguire il corrispondente senso di memcpy().

Il codice utente non può farlo, naturalmente, perché anche dopo la fusione srce dstad un certo tipo di puntatore concreto, non lo fanno (in generale) punto nello stesso oggetto e quindi non possono essere confrontati. Ma la libreria standard può avere una conoscenza della piattaforma sufficiente per eseguire tale confronto senza causare un comportamento indefinito.


Si noti che nella vita reale, le implementazioni tendono ad essere significativamente più complesse, per ottenere le massime prestazioni da trasferimenti più grandi (quando l'allineamento lo consente) e / o un buon utilizzo della cache dei dati. Il codice sopra è solo per rendere il punto il più semplice possibile.


0

memmove può gestire la sovrapposizione di regioni di origine e destinazione, mentre memcpy no. Tra i due, memcpy è molto più efficiente. Quindi, meglio usare memcpy se puoi.

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


Questa risposta dice "forse un po 'più veloce" e fornisce dati quantitativi che indicano solo una leggera differenza. Questa risposta afferma che uno è "molto più efficiente". Quanto più efficiente hai trovato quello più veloce? BTW: Presumo che dire memcpy()e non memcopy().
chux - Ripristina Monica

Il commento è basato sulla conferenza del Dr. Jerry Cain. Ti chiedo di ascoltare la sua conferenza alle 36:00, solo 2-3 minuti saranno sufficienti. E grazie per la cattura. : D
Ehsan
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.