Quelle est la différence entre memmove et memcpy?

120

Quelle est la différence entre memmoveet memcpy? Lequel utilisez-vous habituellement et comment?

Communauté
la source
Notez les problèmes qui pourraient survenir: lwn.net/Articles/414467
Zan Lynx

Réponses:

162

Avec memcpy, la destination ne peut pas du tout chevaucher la source. Avec memmoveça peut. Cela signifie que cela memmovepourrait être très légèrement plus lent quememcpy , car il ne peut pas faire les mêmes hypothèses.

Par exemple, memcpypeut toujours copier les adresses de bas en haut. Si la destination chevauche après la source, cela signifie que certaines adresses seront écrasées avant d'être copiées. memmovedétecterait cela et copier dans l'autre sens - de haut en bas - dans ce cas. Cependant, vérifier cela et passer à un autre algorithme (peut-être moins efficace) prend du temps.

bdonlan
la source
1
lors de l'utilisation de memcpy, comment puis-je garantir que les adresses src et dest ne se chevauchent pas? Dois-je m'assurer personnellement que src et dest ne se chevauchent pas?
Alcott le
6
@Alcott, n'utilisez pas memcpy si vous ne savez pas qu'ils ne se chevauchent pas - utilisez plutôt memmove. Lorsqu'il n'y a pas de chevauchement, memmove et memcpy sont équivalents (bien que memcpy puisse être très, très, très légèrement plus rapide).
bdonlan le
Vous pouvez utiliser le mot-clé "restrict" si vous travaillez avec de longs tableaux et que vous souhaitez protéger votre processus de copie. Par exemple, si votre méthode prend comme paramètres des tableaux d'entrée et de sortie et que vous devez vérifier que l'utilisateur ne passe pas la même adresse en entrée et en sortie. Pour en savoir plus, cliquez ici stackoverflow.com/questions/776283/…
DanielHsH
10
@DanielHsH 'restrict' est une promesse que vous faites au compilateur; il n'est pas appliqué par le compilateur. Si vous mettez `` restreindre '' vos arguments et que, en fait, vous avez un chevauchement (ou plus généralement, accédez aux données restreintes à partir d'un pointeur dérivé de plusieurs endroits), le comportement du programme est indéfini, des bogues étranges se produiront, et le compilateur ne vous en avertira généralement pas.
bdonlan
@bdonlan Ce n'est pas seulement une promesse faite au compilateur, c'est une exigence pour votre appelant. C'est une exigence qui n'est pas appliquée mais si vous enfreignez une exigence, vous ne pouvez pas vous plaindre si vous obtenez des résultats inattendus. Le non-respect d'une exigence est un comportement indéfini, tout comme i = i++ + 1n'est pas défini; le compilateur ne vous interdit pas d'écrire exactement ce code mais le résultat de cette instruction peut être n'importe quoi et différents compilateurs ou processeurs afficheront ici des valeurs différentes.
Mecki
33

memmovepeut gérer la mémoire qui se chevauchent, memcpyne peut pas.

Considérer

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

De toute évidence, la source et la destination se chevauchent maintenant, nous remplaçons "-bar" par "bar". C'est un comportement indéfini en utilisant memcpysi la source et la destination se chevauchent, donc dans ce cas, nous en avons besoin memmove.

memmove(&str[3],&str[4],4); //fine
nos
la source
5
pourquoi d'abord exploser?
4
@ultraman: Parce qu'il PEUT avoir été implémenté en utilisant un assemblage de bas niveau qui nécessite que la mémoire ne se chevauche pas. Si tel est le cas, vous pouvez par exemple générer un signal ou une exception matérielle au processeur qui abandonne l'application. La documentation spécifie qu'elle ne gère pas la condition, mais la norme ne spécifie pas ce qui se passera lorsque ces conditions seront vialoées (c'est ce qu'on appelle un comportement indéfini). Un comportement non défini peut tout faire.
Martin York
avec gcc 4.8.2, même memcpy accepte également les pointeurs de source et de destination qui se chevauchent et fonctionne très bien.
GeekyJ
4
@jagsgediya Bien sûr que oui. Mais comme memcpy est documenté pour ne pas prendre en charge cela, vous ne devez pas vous fier à ce comportement spécifique à l'implémentation, c'est pourquoi memmove () existe. Cela peut être différent dans une autre version de gcc. Cela peut être différent si gcc insère le memcpy au lieu d'appeler memcpy () dans la glibc, cela peut être différent sur une version plus ancienne ou plus récente de la glibc et ainsi de suite.
nos
De la pratique, il semble que memcpy et memmove ont fait la même chose. Un tel comportement indéfini profond.
Life
22

Depuis la page de manuel memcpy .

La fonction memcpy () copie n octets de la zone mémoire src vers la zone mémoire dest. Les zones de mémoire ne doivent pas se chevaucher. Utilisez memmove (3) si les zones de mémoire se chevauchent.

John Carter
la source
12

La principale différence entre memmove()et memcpy()est que dans memmove()un tampon - la mémoire temporaire - est utilisé, il n'y a donc aucun risque de chevauchement. D'un autre côté, memcpy()copie directement les données de l'emplacement pointé par la source vers l'emplacement pointé par la destination . ( http://www.cplusplus.com/reference/cstring/memcpy/ )

Considérez les exemples suivants:

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

    Comme vous vous y attendiez, ceci imprimera:

    stackoverflow
    stackstacklow
    stackstacklow
  2. Mais dans cet exemple, les résultats ne seront pas les mêmes:

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

    Production:

    stackoverflow
    stackstackovw
    stackstackstw

C'est parce que "memcpy ()" fait ce qui suit:

1.  stackoverflow
2.  stacksverflow
3.  stacksterflow
4.  stackstarflow
5.  stackstacflow
6.  stackstacklow
7.  stackstacksow
8.  stackstackstw
Mirac Suzgun
la source
2
Mais, il semble que la sortie que vous avez mentionnée soit inversée !!
kumar
1
Lorsque j'exécute le même programme, j'obtiens le résultat suivant: stackoverflow stackstackstw stackstackstw // signifie qu'il n'y a AUCUNE différence de sortie entre memcpy et memmove
kumar
4
"est que dans" memmove () ", un tampon - une mémoire temporaire - est utilisé;" Ce n'est pas vrai. il dit «comme si» donc il doit simplement se comporter ainsi, non pas qu'il doit en être ainsi. C'est en effet pertinent car la plupart des implémentations de memmove font juste un XOR-swap.
dhein
2
Je ne pense pas que l'implémentation de memmove()soit nécessaire pour utiliser un tampon. Il est parfaitement autorisé à se déplacer sur place (tant que chaque lecture se termine avant toute écriture à la même adresse).
Toby Speight
12

En supposant que vous deviez implémenter les deux, l'implémentation pourrait ressembler à ceci:

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

Et cela devrait assez bien expliquer la différence. memmovecopie toujours de manière à ce qu'il soit toujours en sécurité si srcet se dstchevauchent, alors memcpyque ce n'est pas grave comme le dit la documentation lors de l'utilisation memcpy, les deux zones de mémoire ne doivent pas chevaucher.

Par exemple, si les memcpycopies «recto verso» et les blocs de mémoire sont alignés comme ceci

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

la copie du premier octet de srcà dstdétruit déjà le contenu des derniers octets desrc avant qu'ils aient été copiés. Seule la copie «recto verso» donnera des résultats corrects.

Maintenant échangez srcet dst:

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

Dans ce cas, il est prudent de copier uniquement «recto verso» car la copie «recto verso» détruirait src déjà près de son devant lors de la copie du premier octet.

Vous avez peut-être remarqué que l' memmoveimplémentation ci-dessus ne teste même pas si elles se chevauchent réellement, elle vérifie simplement leurs positions relatives, mais cela seul rendra la copie sûre. Comme il memcpyutilise généralement le moyen le plus rapide possible de copier de la mémoire sur n'importe quel système, il memmoveest généralement implémenté comme suit:

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

Parfois, s'il memcpycopie toujours «recto verso» ou «recto verso», il memmovepeut également être utilisé memcpydans l'un des cas de chevauchement, mais memcpypeut même copier d'une manière différente en fonction de la façon dont les données sont alignées et / ou de la quantité de données à être copié, donc même si vous avez testé commentmemcpy copies sur votre système, vous ne pouvez pas compter sur ce résultat de test pour être toujours correct.

Qu'est-ce que cela signifie pour vous lorsque vous décidez lequel appeler?

  1. À moins que vous savez que vous srcet dstne se chevauchent pas, l' appel memmovecomme il sera toujours conduire à des résultats corrects et est généralement aussi vite que cela est possible dans le cas de copie dont vous avez besoin.

  2. Si vous le savez avec certitude srcet que vous dstne vous chevauchez pas, appelez memcpycar peu importe celui que vous appelez pour le résultat, les deux fonctionneront correctement dans ce cas, mais memmovene seront jamais plus rapides memcpyet si vous êtes malchanceux, cela peut même être plus lent, vous ne pouvez donc gagner que l'appel memcpy.

Mecki
la source
+1 parce que vos "dessins ascii" étaient utiles pour comprendre pourquoi il ne peut pas y avoir de chevauchement sans corrompre les données
Scylardor
10

L'un gère les destinations qui se chevauchent, l'autre pas.

KPexEA
la source
6

simplement à partir de la norme ISO / CEI: 9899, ​​il est bien décrit.

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

Et

7.21.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 se chevauchent 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.

Lequel j'utilise habituellement selon la question, dépend de la fonctionnalité dont j'ai besoin.

En texte brut, memcpy()ne permet pas s1et s2de se chevaucher, alors que le memmove()fait.

Dhein
la source
0

Il existe deux façons évidentes d'implémenter mempcpy(void *dest, const void *src, size_t n)(en ignorant la valeur de retour):

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

Dans la première implémentation, la copie procède des adresses basse vers haute, et dans la seconde, de haute vers basse. Si la plage à copier se chevauche (comme c'est le cas lors du défilement d'un framebuffer, par exemple), alors un seul sens de fonctionnement est correct, et l'autre écrasera les emplacements qui seront ensuite lus.

Une memmove()implémentation, dans sa forme la plus simple, testera dest<src(d'une manière dépendante de la plate-forme) et exécutera la direction appropriée de memcpy().

Le code utilisateur ne peut pas faire cela bien sûr, car même après la conversion srcet dstvers un type de pointeur concret, ils ne pointent pas (en général) vers le même objet et ne peuvent donc pas être comparés. Mais la bibliothèque standard peut avoir suffisamment de connaissances sur la plate-forme pour effectuer une telle comparaison sans provoquer de comportement indéfini.


Notez que dans la vraie vie, les implémentations ont tendance à être beaucoup plus complexes, pour obtenir les performances maximales des transferts plus importants (lorsque l'alignement le permet) et / ou une bonne utilisation du cache de données. Le code ci-dessus est juste pour faire le point le plus simplement possible.

Toby Speight
la source
0

memmove peut gérer le chevauchement des régions source et destination, contrairement à memcpy. Parmi les deux, memcpy est beaucoup plus efficace. Donc, mieux vaut utiliser memcpy si vous le pouvez.

Référence: https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr Jerry Cain, (Stanford Intro Systems Lecture - 7) Heure: 36:00

Ehsan
la source
Cette réponse dit "peut-être un peu plus vite" et fournit des données quantitatives indiquant seulement une légère différence. Cette réponse affirme que l'un d'eux est "beaucoup plus efficace". Combien plus efficace avez-vous trouvé le plus rapide? BTW: Je suppose que vous voulez dire memcpy()et non memcopy().
chux - Réintégrer Monica
Le commentaire est basé sur la conférence du Dr Jerry Cain. Je vous demanderais d'écouter sa conférence à 36h00, seulement 2-3 minutes suffiront. Et merci pour la capture. : D
Ehsan