Risposte:
Con memcpy
, la destinazione non può affatto sovrapporsi alla sorgente. Con memmove
esso può. Ciò significa che memmove
potrebbe essere leggermente più lento di memcpy
, in quanto non può fare le stesse ipotesi.
Ad esempio, memcpy
potrebbe 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. memmove
lo 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.
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.
memmove
può gestire la sovrapposizione della memoria, memcpy
no.
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 memcpy
se l'origine e la destinazione si sovrappongono, quindi in questo caso abbiamo bisogno di memmove
.
memmove(&str[3],&str[4],4); //fine
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:
#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
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
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).
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. memmove
copia sempre in modo tale che sia comunque sicuro se src
e si dst
sovrappongono, mentre memcpy
proprio non interessa come dice la documentazione quando si usa memcpy
, le due aree di memoria non devono sovrapporsi.
Ad esempio, se le memcpy
copie "dalla parte anteriore a quella posteriore" ei blocchi di memoria sono allineati in questo modo
[---- src ----]
[---- dst ---]
la copia del primo byte di src
to dst
distrugge già il contenuto degli ultimi byte di src
prima che questi siano stati copiati. Solo la copia "dal retro al davanti" porterà a risultati corretti.
Ora scambia src
e dst
:
[---- dst ----]
[---- src ---]
In questo caso è sicuro copiare solo "da davanti a dietro" poiché la copia "da dietro a davanti" distruggerebbe src
la parte anteriore già quando si copia il primo byte.
Potresti aver notato che l' memmove
implementazione sopra non verifica nemmeno se effettivamente si sovrappongono, controlla solo le loro posizioni relative, ma questo da solo renderà la copia sicura. Poiché di memcpy
solito utilizza il modo più veloce possibile per copiare la memoria su qualsiasi sistema, di memmove
solito è 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 memcpy
copia sempre "da davanti a dietro" o "da dietro a davanti", memmove
può anche essere utilizzato memcpy
in uno dei casi sovrapposti, ma memcpy
può 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 memcpy
copia sul tuo sistema, non puoi fare affidamento sul risultato del test per essere sempre corretto.
Cosa significa per te quando decidi quale chiamare?
A meno che tu non lo sappia per certo src
e dst
non si sovrapponga, chiama memmove
in quanto porterà sempre a risultati corretti ed è solitamente il più veloce possibile per il caso di copia richiesto.
Se lo sai per certo src
e dst
non si sovrappongono, chiama memcpy
perché non importa quale chiami per il risultato, entrambi funzioneranno correttamente in quel caso, ma memmove
non saranno mai più veloci di memcpy
e se sei sfortunato, potrebbe anche sii più lento, così puoi vincere solo chiamate memcpy
.
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 s1
e s2
di sovrapporsi, mentre lo memmove()
fa.
Esistono due modi ovvi per implementare mempcpy(void *dest, const void *src, size_t n)
(ignorando il valore restituito):
for (char *p=src, *q=dest; n-->0; ++p, ++q)
*q=*p;
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 src
e dst
ad 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.
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
memcpy()
e non memcopy()
.