Puis-je appeler memcpy () et memmove () avec «nombre d'octets» mis à zéro?

103

Dois-je traiter les cas quand je n'ai rien à déplacer / copier avec memmove()/ memcpy()comme cas de bord

int numberOfBytes = ...
if( numberOfBytes != 0 ) {
    memmove( dest, source, numberOfBytes );
}

ou devrais-je simplement appeler la fonction sans vérifier

int numberOfBytes = ...
memmove( dest, source, numberOfBytes );

La vérification de l'ancien extrait de code est-elle nécessaire?

dents acérées
la source
6
question me rappelle un peu la vérification des pointeurs nuls sur des fonctions comme free. Pas nécessaire, mais je mettrais un commentaire là-dessus pour montrer que vous y avez pensé.
Toad
12
@Toad: À quoi cela sert-il, autre que d'encombrer le code? En lisant le code de quelqu'un, je n'ai pas besoin de savoir que le programmeur d'origine "a pensé à faire cette opération qui n'est pas vraiment nécessaire, mais parce que c'est inutile, je ne l'ai pas fait". Si je vois un pointeur en train d'être libéré, je sais qu'il est autorisé à être nul, donc je n'ai pas besoin de connaître les pensées du programmeur original sur le sujet de "devrais-je vérifier la valeur nulle". Et il en va de même pour la copie de 0 octet avecmemcpy
jalf
9
@jalf: le fait que ce soit une question sur le stackoverflow en fait un doute. Donc, ajouter un commentaire peut ne pas vous aider, mais pourrait aider quelqu'un avec moins de connaissances
Toad
2
@Toad Ouais, des commentaires expliquant explicitement pourquoi une vérification qui semble vraiment nécessaire ne l'est pas peut être utile en principe. L' autre côté de la médaille est que cet exemple est un cas commun impliquant une fonction de bibliothèque standard que chaque programmeur n'a besoin que d'apprendre la réponse à la fois; alors ils peuvent reconnaître dans n'importe quel programme qu'ils lisent que ces vérifications ne sont pas nécessaires. Pour cette raison, j'omettreais les commentaires. Une base de code avec plusieurs appels comme celui-ci devra soit copier et coller les commentaires dans chacun, soit les utiliser arbitrairement sur certains appels seulement, qui sont tous les deux laids.
Mark Amery

Réponses:

145

À partir de la norme C99 (7.21.1 / 2):

Où un argument déclaré comme size_t nspécifie la longueur du tableau pour une fonction,n peut avoir la valeur zéro lors d'un appel à cette fonction. Sauf indication contraire explicite dans la description d'une fonction particulière dans ce sous-paragraphe, les arguments de pointeur sur un tel appel doivent toujours avoir des valeurs valides, comme décrit au 7.1.4. Lors d'un tel appel, une fonction qui localise un caractère ne trouve aucune occurrence, une fonction qui compare deux séquences de caractères renvoie zéro et une fonction qui copie des caractères copie zéro caractère.

Donc la réponse est non; la vérification n'est pas nécessaire (ou oui; vous pouvez passer zéro).

Mike Seymour
la source
1
Un pointeur serait-il considéré comme "valide" aux fins d'une telle fonction s'il pointait vers l'emplacement suivant le dernier élément d'un tableau? Un tel pointeur ne pouvait pas être légitimement déféré, mais on pourrait faire d'autres choses en toute sécurité, comme en soustraire un.
supercat
1
@supercat: oui, un pointeur qui pointe un au-delà de la fin d'un tableau est valide pour l'arithmétique du pointeur avec d'autres pointeurs dans (ou un après la fin de) ce tableau, mais n'est pas déréférencable.
Mike Seymour
@MikeSeymour: La citation ne devrait-elle pas impliquer une réponse opposée: la vérification est nécessaire et vous ne pouvez pas passer zéro avec des pointeurs nuls?
neverhoodboy
7
@neverhoodboy: Non, la citation dit clairement " npeut avoir la valeur zéro". Vous avez raison de dire que vous ne pouvez pas passer de pointeurs nuls, mais ce n'est pas le sujet de la question.
Mike Seymour
1
@MikeSeymour: Ma faute. Désolé. La question concerne la taille et non le pointeur.
neverhoodboy
5

Comme le dit @You, le standard spécifie que memcpy et memmove doivent gérer ce cas sans problème; car ils sont généralement mis en œuvre comme

void *memcpy(void *_dst, const void *_src, size_t len)
{
    unsigned char *dst = _dst;
    const unsigned char *src = _src;
    while(len-- > 0)
        *dst++ = *src++;
    return _dst;
}

vous ne devriez même pas avoir de pénalité de performance autre que l'appel de fonction; si le compilateur prend en charge intrinsèques / inlining pour de telles fonctions, la vérification supplémentaire peut même rendre le code un micro-petit-bit plus lent, puisque la vérification est déjà effectuée à un moment donné.

Matteo Italia
la source
1
Je pense que cette fonction est probablement faite en assemblage où vous pouvez optimiser les transferts de mémoire beaucoup mieux que dans c
Toad
"en quelque sorte comme" :) En fait, presque toutes les implémentations que j'ai vues sont en assemblage, et essayez de copier la plupart des bits en utilisant la taille du mot natif (par exemple uint32_t sur x86), mais cela ne change pas la substance de la réponse : il est un tout en boucle qui n'a pas besoin de grands calculs avant de commencer, de sorte que le chèque est déjà fait.
Matteo Italia
9
-1, l'implémentation typique n'est pas pertinente pour savoir si c'est valide C pour appeler ces fonctions (qui peuvent même ne pas être implémentées en tant que fonctions C) avec un argument zéro.
R .. GitHub STOP HELPING ICE
4
Le fait qu'il soit valide C a déjà été couvert par les autres réponses, comme je l'ai dit au tout début de ma réponse: "Comme dit par @You, le standard spécifie que memcpy et memmove doivent gérer ce cas sans problème". Je viens d'ajouter mon avis sur le fait qu'il ne faut même pas avoir peur d'appeler memcpy avec len = 0 pour des raisons de performances, car dans ce cas c'est un appel avec un coût quasi nul.
Matteo Italia