J'essaie de comprendre la différence entre memcpy()
et memmove()
, et j'ai lu le texte selon lequel memcpy()
ne prend pas soin de la source et de la destination qui se chevauchent, alors que memmove()
le fait.
Cependant, lorsque j'exécute ces deux fonctions sur des blocs de mémoire qui se chevauchent, elles donnent le même résultat. Par exemple, prenons l'exemple MSDN suivant sur la page d'aide memmove()
: -
Existe-t-il un meilleur exemple pour comprendre les inconvénients de memcpy
et comment memmove
le résout?
// 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 );
}
The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb
Je ne suis pas entièrement surpris que votre exemple ne présente aucun comportement étrange. Essayez de copier str1
dans str1+2
à la place et voyez ce qui se passe ensuite. (Peut ne pas réellement faire la différence, dépend du compilateur/des bibliothèques.)
En général, memcpy est implémenté de manière simple (mais rapide). De manière simpliste, il suffit de faire une boucle sur les données (dans l’ordre) pour les copier d’un emplacement à l’autre. Cela peut entraîner l’écrasement de la source lors de la lecture.
Memmove fait plus de travail pour s’assurer qu’il gère correctement le chevauchement.
MODIFIER:
(Malheureusement, je ne trouve pas d’exemples décents, mais ceux-ci suffiront). Contrastez les implémentations memcpy et memmove montrées ici. memcpy ne fait que boucler, tandis que memmove effectue un test pour déterminer la direction dans laquelle effectuer une boucle afin d'éviter de corrompre les données. Ces implémentations sont plutôt simples. La plupart des implémentations hautes performances sont plus compliquées (la copie de blocs de taille Word à la fois plutôt que d'octets).
La mémoire dans memcpy
ne peut pas se chevaucher ou vous risquez un comportement indéfini, tandis que la mémoire dans memmove
peut se chevaucher.
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.
Certaines implémentations de memcpy peuvent toujours fonctionner pour des entrées qui se chevauchent, mais vous ne pouvez pas compter pour ce comportement. Même si memmove doit permettre le chevauchement.
Ce n'est pas parce que memcpy
ne doit pas traiter de régions qui se chevauchent que cela ne les traite pas correctement L'appel avec des régions qui se chevauchent produit un comportement indéfini. Un comportement non défini peut fonctionner entièrement comme vous le souhaitez sur une plate-forme. cela ne signifie pas que c'est correct ou valide.
Memcpy et memove font les mêmes choses.
Mais pour remarquer une différence:
#include <memory.h>
#include <string.h>
#include <stdio.h>
char str1[17] = "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( "The string: %s\n", str1 );
memmove( (str1+6), str1, 10 );
printf( "New string: %s\n", str1 );
}
donne:
The string: abcdef
New string: abcdefabcdefabcd
The string: abcdef
New string: abcdefabcdef
Votre démo n'a pas exposé les inconvénients de Memcpy à cause d'un "mauvais" compilateur, elle vous rend un service dans la version Debug. Une version finale, cependant, vous donne le même résultat, mais à cause de l'optimisation.
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
Le registre %eax
joue ici comme stockage temporaire, ce qui corrige "élégamment" le problème de chevauchement.
L'inconvénient se dégage lors de la copie de 6 octets, du moins en partie.
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);
}
Sortie:
The string: aabbccdd
New string: aaaabbbb
The string: aabbccdd
New string: aaaabbcc
Ça a l'air bizarre, ça vient aussi de l'optimisation.
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
C'est pourquoi je choisis toujours memmove
lorsque j'essaie de copier 2 blocs de mémoire superposés.
La différence entre memcpy
et memmove
est que
dans memmove
, la mémoire source de la taille spécifiée est copiée dans la mémoire tampon puis déplacée vers la destination. Donc, si la mémoire se chevauche, il n'y a pas d'effets secondaires.
dans le cas de memcpy()
, il n'y a pas de tampon supplémentaire utilisé pour la mémoire source. La copie est effectuée directement sur la mémoire de sorte que lorsque la mémoire se chevauche, nous obtenons des résultats inattendus.
Ceux-ci peuvent être observés par le code suivant:
//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);
}
La sortie est:
hare hare rama hare rama
hare hare hare hare hare hare rama hare rama
Comme il a déjà été souligné dans d'autres réponses, memmove
est plus sophistiqué que memcpy
, de sorte qu'il rend compte des chevauchements de mémoire. Le résultat de memmove est défini comme si la src
était copiée dans un tampon, puis dans le tampon copié dans dst
. Cela ne signifie PAS que l'implémentation actuelle utilise un tampon, mais utilise probablement une arithmétique de pointeur.
Le code indiqué dans les liens http://clc-wiki.net/wiki/memcpy for memcpy semble me confondre un peu, car il ne donne pas le même résultat lorsque je l'ai implémenté en utilisant l'exemple ci-dessous.
#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();
}
Sortie:
The string: abcdefghij
Actual memcpy output: aabcdefghi
Implemented memcpy output: aaaaaaaaaa
Implemented memmove output: aabcdefghi
Mais vous pouvez maintenant comprendre pourquoi memmove s’occupe des problèmes qui se chevauchent.
le compilateur pourrait optimiser memcpy, par exemple:
int x;
memcpy(&x, some_pointer, sizeof(int));
Cette mémoire peut être optimisée comme suit: x = *(int*)some_pointer;
Projet standard C11
Le projet de norme C11 N1570 dit:
7.24.2.1 "La fonction memcpy":
2 La fonction memcpy copie n caractères de l’objet pointé par s2 dans le fichier objet pointé par s1. Si la copie a lieu entre des objets qui se chevauchent, le comportement est indéfini.
7.24.2.2 "La fonction memmove":
2 La fonction memmove copie n caractères de l’objet pointé par s2 dans le fichier objet pointé par s1. La copie se déroule comme si les n caractères de l’objet s2 sont tout d’abord copiés dans un tableau temporaire de n caractères qui ne le fait pas chevauchent les objets pointés par s1 et s2, puis les n caractères du tableau temporaire sont copiés dans l'objet pointé par s1
Par conséquent, tout chevauchement sur memcpy
conduit à un comportement indéfini, et tout peut arriver: mauvais, rien ou même bon. Bon c'est rare cependant :-)
memmove
indique cependant clairement que tout se passe comme si un tampon intermédiaire était utilisé, les chevauchements sont donc OK.
C++ std::copy
est cependant plus tolérant et autorise les chevauchements: std :: copy gère-t-il les plages qui se chevauchent?