web-dev-qa-db-fra.com

memcpy () vs memmove ()

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

Sortie:

The string: aabbcc
New string: aaaabb
The string: aabbcc
New string: aaaabb
142
user534785

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).

112

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.

84
rxantos

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.

31
Billy ONeal

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
15
Neilvert Noval

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.

7
huubby

La différence entre memcpy et memmove est que

  1. 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.

  2. 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
3
R srinivas reddy

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.

1
singingsingh

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;

1
rockeet

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?