memcpy () contre memmove ()


157

J'essaie de comprendre la différence entre memcpy()et memmove(), et j'ai lu le texte qui memcpy()ne prend pas en compte la source et la destination qui se chevauchent alors que le memmove()fait.

Cependant, lorsque j'exécute ces deux fonctions sur des blocs de mémoire qui se chevauchent, elles donnent toutes deux le même résultat. Par exemple, prenez l'exemple MSDN suivant sur la memmove()page d'aide: -

Existe-t-il un meilleur exemple pour comprendre les inconvénients memcpyet comment les memmoverésoudre?

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

Production:

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

1
Le Microsoft CRT a un memcpy () sûr depuis un certain temps.
Hans Passant

32
Je ne pense pas que «sûr» soit le mot juste pour cela. Un coffre memcpy- fort voudrait assertque les régions ne se chevauchent pas plutôt que de masquer intentionnellement les bogues dans votre code.
R .. GitHub STOP HELPING ICE

6
Cela dépend si vous voulez dire "sans danger pour le développeur" ou "sans danger pour l'utilisateur final". Je dirais que faire ce que l'on dit, même si ce n'est pas conforme aux normes, est le choix le plus sûr pour l'utilisateur final.
kusma le

depuis la glibc 2.19 - ne fonctionne pas The string: aabbcc New string: aaaaaa The string: aabbcc New string: aaaabb
askovpen

Vous pouvez également voir ici .
Ren

Réponses:


124

Je ne suis pas entièrement surpris que votre exemple ne présente aucun comportement étrange. Essayez plutôt str1de copier str1+2et voyez ce qui se passe ensuite. (Peut ne pas faire de 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 ne fait que faire une boucle sur les données (dans l'ordre), en copiant d'un emplacement à l'autre. Cela peut entraîner l'écrasement de la source lors de sa lecture.

Memmove fait plus de travail pour s'assurer qu'il gère correctement le chevauchement.

ÉDITER:

(Malheureusement, je ne peux pas trouver d'exemples décents, mais ceux-ci feront l'affaire). Comparez les implémentations memcpy et memmove illustrées ici. memcpy ne fait que faire une boucle, tandis que memmove effectue un test pour déterminer dans quelle direction effectuer la boucle pour éviter de corrompre les données. Ces implémentations sont assez simples. La plupart des implémentations hautes performances sont plus compliquées (impliquant la copie de blocs de taille de mot à la fois plutôt que d'octets).


2
+1 De plus, dans l'implémentation suivante, memmoveappelle memcpydans une branche après avoir testé les pointeurs: student.cs.uwaterloo.ca/~cs350/common/os161-src-html
Pascal Cuoq

Cela sonne bien. On dirait que Visual Studio implémente un memcpy "sûr" (avec gcc 4.1.1, j'ai également testé sur RHEL 5). L'écriture des versions de ces fonctions à partir de clc-wiki.net donne une image claire. Merci.
user534785

3
memcpy ne s'occupe pas du problème de chevauchement, mais memmove le fait. Alors pourquoi ne pas éliminer memcpy de la lib?
Alcott

37
@Alcott: Parce que ça memcpypeut être plus rapide.
Billy ONeal

Lien fixe / webarchive de Pascal Cuoq ci-dessus: web.archive.org/web/20130722203254/http
//...

95

La mémoire dans memcpy ne peut pas se chevaucher ou vous risquez un comportement indéfini, tandis que la mémoire dans memmovepeut 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 ce comportement. Alors que memmove doit permettre le chevauchement.


3
ça m'a vraiment aidé thaks! +1 pour votre info
Muthu Ganapathy Nathan

33

Ce memcpyn'est pas parce qu'il n'a pas à faire face à des régions qui se chevauchent que cela ne signifie pas qu'il les traite 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 prévu sur une seule plateforme; cela ne veut pas dire que c'est correct ou valide.


10
En particulier, selon la plate-forme, il est possible qu'elle memcpysoit implémentée exactement de la même manière que memmove. Autrement dit, celui qui a écrit le compilateur n'a pas pris la peine d'écrire une memcpyfonction unique .
Came

19

Memcpy et memove font des choses similaires.

Mais pour voir une différence:

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

}

donne:

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

À mon humble avis, cet exemple de programme a quelques défauts, car le tampon str1 est accessible hors des limites (10 octets à copier, le tampon fait 7 octets). L'erreur hors limites entraîne un comportement indéfini. Les différences entre les résultats affichés des appels à memcpy () / memmove () sont spécifiques à l'implémentation. Et l'exemple de sortie ne correspond pas exactement au programme ci-dessus ... Aussi, strcpy_s () ne fait pas partie du standard C AFAIK (spécifique à MS, voir aussi: stackoverflow.com/questions/36723946/… ) - Veuillez me corriger si je j'ai tort.
rel

7

Votre démo n'a pas exposé les inconvénients de memcpy à cause d'un "mauvais" compilateur, elle vous rend 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 %eaxjoue ici comme un stockage temporaire, qui corrige "élégamment" le problème de chevauchement.

L'inconvénient apparaît lors de la copie de 6 octets, au moins une partie de celui-ci.

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

Production:

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

Ça a l'air bizarre, c'est aussi causé par 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 memmoveen essayant de copier 2 blocs de mémoire superposés.


3

La différence entre memcpyet memmoveest 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(), aucun tampon supplémentaire n'est utilisé pour la mémoire source. La copie se fait directement sur la mémoire afin qu'en cas de chevauchement de mémoire, nous obtenions 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

6
-1 - il n'est pas nécessaire que memmove copie réellement les données dans un tampon séparé
jjwchoy

cet exemple n'aide pas à comprendre le concept ... car la plupart des compilateurs donneront la même chose que la sortie du mouvement mem
Jasdeep Singh Arora

1
@jjwchoy Conceptuellement, c'est le cas. Le tampon serait généralement optimisé
MM

Le même résultat, sous Linux.
CodyChan

2

Comme déjà souligné dans d'autres réponses, memmoveest plus sophistiqué que memcpytel qu'il tient compte des chevauchements de mémoire. Le résultat de memmove est défini comme si le srcétait copié dans un tampon, puis copié dans le tampon dst. Cela ne signifie PAS que l'implémentation réelle utilise un tampon, mais fait probablement une arithmétique de pointeur.


1

compilateur pourrait optimiser memcpy, par exemple:

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

Ce memcpy peut être optimisé comme: x = *(int*)some_pointer;


3
Une telle optimisation n'est autorisée que sur les architectures qui autorisent des intaccès non alignés . Sur certaines architectures (par exemple Cortex-M0), tenter de récupérer un 32 bits à intpartir d'une adresse qui n'est pas un multiple de quatre provoquera un crash (mais memcpyfonctionnera). Si l'on utilise un processeur qui permet un accès non aligné ou un compilateur avec un mot-clé qui dirige le compilateur pour assembler des entiers à partir d'octets extraits séparément si nécessaire, on pourrait faire quelque chose comme #define UNALIGNED __unalignedet puis `x = * (int UNALIGNED * ) some_pointer;
supercat du

2
Certains processeurs n'autorisent pas le crash d'accès int non aligné, char x = "12345"; int *i; i = *(int *)(x + 1);mais certains le font, car ils corrigent la copie pendant la panne. J'ai travaillé sur un système comme celui-ci et il m'a fallu un peu de temps pour comprendre pourquoi les performances étaient si mauvaises.
user3431262

*(int *)some_pointerest une violation stricte d'aliasing, mais vous voulez probablement dire que le compilateur afficherait l'assembly qui copie un int
MM

1

Le code donné dans les liens http://clc-wiki.net/wiki/memcpy pour memcpy semble me dérouter 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();
}

Production :

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

Mais vous pouvez maintenant comprendre pourquoi memmove s'occupera du problème de chevauchement.


1

Projet standard C11

Le projet standard 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 l'objet pointé par s1. Si la copie a lieu entre des objets qui se chevauchent, le comportement n'est pas défini.

7.24.2.2 "La fonction memmove":

2 La fonction memmove copie n caractères de l'objet pointé par s2 dans l'objet pointé par s1. La copie a lieu comme si les n caractères de l'objet pointé par s2 étaient d'abord copiés dans un tableau temporaire de n caractères qui ne chevauche pas 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 memcpyconduit à un comportement indéfini, et tout peut arriver: mauvais, rien ou même bien. Le bien est rare cependant :-)

memmove cependant dit clairement que tout se passe comme si un tampon intermédiaire est utilisé, donc clairement les chevauchements sont OK.

std::copyCependant, C ++ est plus indulgent et autorise les chevauchements: std :: copy gère-t-il les plages qui se chevauchent?


memmoveutilise un tableau temporaire supplémentaire de n, donc utilise-t-il de la mémoire supplémentaire? Mais comment le faire si nous ne lui avons donné accès à aucune mémoire. (Il utilise 2x la mémoire).
clmno

@clmno il alloue sur pile ou malloc comme toute autre fonction à laquelle je m'attendrais :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
J'avais posé une question ici , j'avais aussi une bonne réponse. Je vous remercie. J'ai vu votre message hackernews qui est devenu viral (le x86) :)
CLMNO

-4

J'ai essayé d'exécuter le même programme en utilisant eclipse et cela montre une différence claire entre memcpyet memmove. memcpy()ne se soucie pas du chevauchement de l'emplacement de la mémoire qui entraîne la corruption des données, alors que memmove()copiera d'abord les données dans une variable temporaire, puis copiera dans l'emplacement de mémoire réel.

Lors de la tentative de copie des données de l'emplacement str1vers str1+2, la sortie de memcpyest " aaaaaa". La question serait comment? memcpy()copiera un octet à la fois de gauche à droite. Comme indiqué dans votre programme " aabbcc", toutes les copies auront lieu comme ci-dessous,

  1. aabbcc -> aaabcc

  2. aaabcc -> aaaacc

  3. aaaacc -> aaaaac

  4. aaaaac -> aaaaaa

memmove() copiera d'abord les données dans la variable temporaire, puis copiera dans l'emplacement de mémoire réel.

  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)

La sortie est

memcpy : aaaaaa

memmove : aaaabb


2
Bienvenue dans Stack Overflow. Veuillez lire la page À propos bientôt. Il y a plusieurs problèmes à résoudre. Tout d'abord, vous avez ajouté une réponse à une question avec plusieurs réponses d'il y a environ 18 mois. Pour justifier l'ajout, vous devrez fournir de nouvelles informations surprenantes. Deuxièmement, vous spécifiez Eclipse, mais Eclipse est un IDE qui utilise un compilateur C, mais vous n'identifiez pas la plate-forme sur laquelle votre code s'exécute ou le compilateur C qu'utilise Eclipse. Je serais intéressé de savoir comment vous assurez que les memmove()copies vers un emplacement intermédiaire. Il doit simplement copier à l'envers si nécessaire.
Jonathan Leffler

Merci. À propos du compilateur, j'utilise donc le compilateur gcc sous Linux. Il existe une page de manuel dans Linux pour le memove qui spécifie clairement que memove copiera les données dans une variable temporaire pour éviter le chevauchement des données. Voici le lien de cette page de manuel linux.die.net/man/3/memmove
Pratik Panchal

3
Il dit en fait «comme si», ce qui ne veut pas dire que c'est ce qui se passe réellement. Certes, il pourrait réellement le faire de cette façon (bien qu'il y ait des questions sur la provenance de la mémoire de rechange), mais je serais plus qu'un peu surpris si c'était ce qu'il fait réellement. Si l'adresse source est supérieure à l'adresse cible, il suffit de copier du début à la fin (copie en avant); si l'adresse source est inférieure à l'adresse cible, il suffit de copier de la fin au début (copie à l'envers). Aucune mémoire auxiliaire n'est nécessaire ou utilisée.
Jonathan Leffler

essayez d'expliquer votre réponse avec des données réelles dans le code, ce serait plus utile.
HaseeB Mir
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.