Quelle est la différence entre memmove
et memcpy
? Lequel utilisez-vous habituellement et comment?
Avec memcpy
, la destination ne peut pas chevaucher la source du tout. Avec memmove
c'est possible. Cela signifie que memmove
pourrait être très légèrement plus lent que memcpy
, car il ne peut pas faire les mêmes hypothèses.
Par exemple, memcpy
peut toujours copier les adresses de bas en haut. Si la destination se superpose à la source, cela signifie que certaines adresses seront écrasées avant la copie. memmove
détecterait ceci et copierait 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.
memmove
peut gérer le chevauchement de la mémoire, memcpy
ne le peut pas.
Considérer
char[] str = "foo-bar";
memcpy(&str[3],&str[4],4); //might blow up
Évidemment, la source et la destination se chevauchent maintenant, nous écrasons "- bar" avec "bar". Il s'agit d'un comportement non défini utilisant memcpy
si la source Et la destination se chevauchent. Dans ce cas, nous avons besoin de memmove
.
memmove(&str[3],&str[4],4); //fine
La principale différence entre memmove()
et memcpy()
est que, dans memmove()
, un buffer - mémoire temporaire - est utilisé. Il n'y a donc aucun risque de chevauchement. D'autre part, memcpy()
copie directement les données de l'emplacement désigné par source vers l'emplacement désigné par destination. ( http://www.cplusplus.com/reference/cstring/memcpy/ )
Considérez les exemples suivants:
#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 affichera:
stackoverflow
stackstacklow
stackstacklow
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;
}
Sortie:
stackoverflow
stackstackovw
stackstackstw
C'est parce que "memcpy ()" a les effets suivants:
1. stackoverflow
2. stacksverflow
3. stacksterflow
4. stackstarflow
5. stackstacflow
6. stackstacklow
7. stackstacksow
8. stackstackstw
L'une gère les destinations qui se chevauchent, l'autre pas.
simplement de la norme ISO/IEC: 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 le fichier 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 le fichier objet pointé par s1. La copie a lieu 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 overlap 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 le chevauchement de s1
et s2
, contrairement à memmove()
.
En supposant que vous ayez à 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 plutôt bien expliquer la différence. memmove
fait toujours des copies de telle sorte qu'il soit toujours sûr que src
et dst
se chevauchent, alors que memcpy
s'en fiche, comme le dit la documentation, lorsque vous utilisez memcpy
, les deux zones de mémoire ne doivent pas se chevauchent.
Par exemple. si memcpy
copie "d'avant en arrière" et que les blocs de mémoire sont alignés comme ceci
[---- src ----]
[---- dst ---]
Ensuite, la copie du premier octet de src
do dst
détruit déjà le contenu des derniers octets de src
avant que ceux-ci aient été copiés. Donc, ici, il est seulement sûr de copier "de haut en bas".
Maintenant, permutez src
et dst
:
[---- dst ----]
[---- src ---]
Dans ce cas, il est seulement sûr de copier "d'avant en arrière", car copier "d'avant en arrière" détruirait déjà src
près de son devant lors de la copie du premier octet.
Vous avez peut-être remarqué que l'implémentation memmove
ci-dessus ne vérifie même pas si elles se chevauchent réellement, elle vérifie simplement leurs positions relatives, mais cela seul permettra de sécuriser la copie. Comme memcpy
utilise généralement le moyen le plus rapide possible pour copier de la mémoire sur n’importe quel système, memmove
est 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, si memcpy
copie toujours "avant à l'arrière" ou "arrière à l'avant", memmove
peut également utiliser memcpy
dans l'un des cas de chevauchement, mais memcpy
peut même copier d'une manière différente en fonction de l'alignement des données et/ou de l'ampleur les données doivent être copiées. Ainsi, même si vous avez testé la manière dont memcpy
copies sur votre système, vous ne pouvez pas compter sur le résultat de ce test pour qu'il soit toujours correct.
Qu'est-ce que cela signifie pour vous lorsque vous décidez lequel appeler?
Sauf si vous savez avec certitude que src
et dst
ne se chevauchent pas, appelez memmove
car cela donnera toujours des résultats corrects et est généralement aussi rapide que possible pour le casier dont vous avez besoin.
Si vous savez avec certitude que src
et dst
ne se chevauchent pas, appelez memcpy
car peu importe celui que vous appelez pour le résultat, les deux fonctionneront correctement dans ce cas, mais memmove
ne sera jamais plus rapide que memcpy
et si vous êtes malchanceux, il peut même être plus lent, vous ne pouvez donc gagner que si vous appelez memcpy
.
memmove peut traiter les régions source et cible qui se chevauchent, alors que memcpy ne le peut pas. Parmi les deux, memcpy est beaucoup plus efficace. Donc, mieux vaut l’utiliser, si vous le pouvez.
Référence: https://www.youtube.com/watch?v=Yr1YnOVG-4g Dr. Jerry Cain, (Conférence de Stanford Intro Systems - 7) Heure: 36:00
chaîne de caractères [] = "stackoverflow";
char *first, *second;
first = string;
second = string;
puts(string);
o/p - stackoverflow
memcpy(first+5, first,7);
puts(first);
Ici 7 caractères pointés par la seconde, "stackov" collés à la position +5 résultant
o/p - stackstackovw
memcpy(second+5, second, 7);
puts(second);
ici la chaîne d'entrée est "stackstackovw", 7 caractères pointés par une seconde (c'est-à-dire "stackst") sont copiés dans un tampon, puis collés à la place du +5 résultant
o/p - stackstackstw
0 ----- + 5
stack stackst w
Il existe deux manières évidentes d'implémenter mempcpy(void *dest, const void *src, size_t n)
(en ignorant la valeur de retour):
for (char *p=src, *q=dest; n-->0; ++p, ++q)
*q=*p;
char *p=src, *q=dest;
while (n-->0)
q[n]=p[n];
Dans le premier mode de réalisation, la copie passe des adresses basses aux adresses hautes et, dans le second, des adresses hautes à basses. Si la plage à copier se superpose (comme c'est le cas lors du défilement d'un framebuffer, par exemple), une seule direction est correcte et l'autre remplace les emplacements à partir desquels vous lirez.
Une implémentation de memmove()
, 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()
.
Bien entendu, le code utilisateur ne peut pas le faire, car même après avoir jeté src
et dst
dans un type de pointeur concret, ils ne pointent (en général) dans le même objet et ne peuvent donc pas être comparés. Toutefois, la bibliothèque standard peut disposer de suffisamment de connaissances sur la plate-forme pour effectuer une telle comparaison sans provoquer de comportement indéfini.
Notez que dans la vie réelle, les implémentations ont tendance à être considérablement plus complexes, afin de tirer le maximum de performances des transferts plus importants (lorsque l'alignement le permet) et/ou d'une bonne utilisation du cache de données. Le code ci-dessus est juste pour faire le point aussi simplement que possible.
J'ai essayé de lancer le code ci-dessus: Raison principale pour laquelle je souhaite connaître la différence entre memcpy
et memmove
.
#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;
}
donne en dessous de la sortie
stackoverflow
stackstackovw
stackstackstw
Maintenant, j'ai remplacé memmove
par memcpy
et j'ai le même résultat
#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,7);
puts(first);
memcpy(second+5, second, 7);
puts(second);
return 0;
}
Sortie:
stackoverflow
stackstackovw
stackstackstw