Pourquoi utiliser bzero sur memset?

156

Dans un cours de programmation système que j'ai suivi ce semestre précédent, nous avons dû implémenter un client / serveur de base en C.Lors de l'initialisation des structs, like sock_addr_in, ou char buffers (que nous utilisions pour envoyer des données entre le client et le serveur) le professeur nous a demandé de les utiliser uniquement bzeroet de ne memsetpas les initialiser. Il n'a jamais expliqué pourquoi, et je suis curieux de savoir s'il y a une raison valable à cela?

Je vois ici: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown qui bzeroest plus efficace en raison du fait que la mémoire ne sera jamais remise à zéro, donc ce n'est pas doivent faire toute vérification supplémentaire qui memsetpourrait le faire. Cela ne semble pas nécessairement être une raison pour ne pas l'utiliser memsetpour la réinitialisation de la mémoire.

bzeroest considérée comme obsolète et n'est en outre pas une fonction C standard. Selon le manuel, il memsetest préférable bzeropour cette raison. Alors, pourquoi voudriez-vous encore utiliser bzeroplus memset? Juste pour les gains d'efficacité, ou est-ce quelque chose de plus? De même, quels sont les avantages de memsetsur bzeroqui en font l'option préférée de facto pour les programmes plus récents?

PseudoPsyché
la source
28
"Pourquoi utiliser bzero sur memset?" - Non. Memset est standard, bzero ne l'est pas.
30
bzero est un BSDism (). memset () est ansi-c. de nos jours, bzero () sera probablement implémenté sous forme de macro. Demandez à votre professeur de se raser et de lire des livres. l'efficacité est un faux argument. Un appel système ou un changement de contexte peut facilement coûter des dizaines de milliers de tics d'horloge, un passage sur un tampon fonctionne à la vitesse du bus. Si vous voulez optimiser les programmes réseau: minimisez le nombre d'appels système (en lisant / écrivant des morceaux plus gros)
wildplasser
7
L'idée qui memsetpeut être légèrement moins efficace en raison de "un peu plus de vérification" est certainement un cas d'optimisation prématurée: quels que soient les gains que vous pourriez voir en omettant une instruction CPU ou deux ne valent pas la peine lorsque vous pouvez compromettre la portabilité de votre code. bzeroest obsolète, et c'est une raison suffisante pour ne pas l'utiliser.
dasblinkenlight
4
Souvent, vous pouvez ajouter un initialiseur `= {0}` à la place et ne pas appeler de fonction du tout. Cela est devenu plus facile quand, au tournant du siècle, C a cessé d'exiger la déclaration préalable des variables locales. Cependant, certains articles en papier vraiment anciens sont encore profondément ancrés au siècle précédent.
MSalters
1
@SSAnne non, mais il est probablement issu d'un livre recommandé pour le cours sur lequel il a été influencé, comme mentionné dans l'une des réponses ci-dessous: stackoverflow.com/a/17097072/1428743
PseudoPsyche

Réponses:

152

Je ne vois aucune raison de préférer bzeroplus memset.

memsetest une fonction C standard alors qu'elle bzeron'a jamais été une fonction standard C. La raison est probablement que vous pouvez obtenir exactement la même fonctionnalité en utilisant la memsetfonction.

En ce qui concerne maintenant l'efficacité, les compilateurs comme gccutilisent des implémentations intégrées pour memsetlesquelles basculent vers une implémentation particulière lorsqu'une constante 0est détectée. Idem glibclorsque les fonctions intégrées sont désactivées.

ouah
la source
Merci. C'est logique. J'étais à peu près sûr que cela memsetdevrait toujours être utilisé dans ce cas, mais je ne savais pas pourquoi nous ne l'utilisions pas. Merci d'avoir clarifié et réaffirmé mes pensées.
PseudoPsyche
1
J'ai eu de nombreux problèmes avec des bzeroimplémentations cassées . Sur les tableaux non alignés, il dépassait la longueur fournie et mettait à zéro un peu plus d'octets. Jamais eu un tel problème après le passage à memset.
rustyx
N'oubliez pas memset_sce qui doit être utilisé si vous voulez vous assurer que le compilateur n'optimise pas silencieusement un appel à «nettoyer» la mémoire à des fins liées à la sécurité (comme la suppression d'une région de mémoire contenant un une information comme un mot de passe en clair).
Christopher Schultz
69

Je suppose que vous avez utilisé (ou que votre professeur a été influencé par) la programmation réseau UNIX de W. Richard Stevens. Il utilise bzerofréquemment à la place de memset, même dans l'édition la plus récente. Le livre est si populaire, je pense que c'est devenu un idiome dans la programmation réseau, c'est pourquoi vous le voyez toujours utilisé.

Je m'en tiendrai memsetsimplement parce qu'il bzeroest obsolète et réduit la portabilité. Je doute que vous voyiez des gains réels à utiliser l'un par rapport à l'autre.

Austin
la source
4
Vous auriez raison. Nous n'avions pas de manuels obligatoires pour ce cours, mais je viens de vérifier à nouveau le programme et la programmation réseau UNIX est en effet répertoriée comme une ressource facultative. Merci.
PseudoPsyche
9
C'est en fait pire que ça. Il était obsolète dans POSIX.1-2001 et supprimé dans POSIX.1-2008.
paxdiablo
9
Citant la page 8 de la troisième édition de UNIX Network Programming par W. Richard Stevens - En effet, l'auteur de TCPv3 a fait l'erreur de permuter les deuxième et troisième arguments en memset en 10 occurrences de la première impression. Le compilateur AC ne peut pas attraper cette erreur car les deux occurrences sont identiques ... c'était une erreur, et pourrait être évitée en utilisant bzero, car l'échange des deux arguments en bzero sera toujours intercepté par le compilateur C si des prototypes de fonction sont utilisés. Cependant, comme l'a souligné paxdiablo, bzero est obsolète.
Aaron Newton
@AaronNewton, vous devriez ajouter cela à la réponse de Michael car cela confirme ce qu'il a dit.
Synetech du
52

Le seul avantage que je pense bzero()avoir sur la memset()mise à zéro de la mémoire est qu'il y a un risque réduit d'erreur.

Plus d'une fois, j'ai rencontré un bug qui ressemblait à:

memset(someobject, size_of_object, 0);    // clear object

Le compilateur ne se plaindra pas (bien que peut-être augmenter certains niveaux d'avertissement sur certains compilateurs) et l'effet sera que la mémoire n'est pas effacée. Parce que cela ne détruit pas l'objet - cela le laisse tranquille - il y a de bonnes chances que le bogue ne se manifeste pas en quoi que ce soit d'évident.

Le fait que ce bzero()ne soit pas standard est un irritant mineur. (FWIW, je ne serais pas surpris si la plupart des appels de fonction dans mes programmes ne sont pas standard; en fait, écrire de telles fonctions est un peu mon travail).

Dans un commentaire à une autre réponse ici, Aaron Newton a cité ce qui suit de Unix Network Programming, Volume 1, 3rd Edition par Stevens, et al., Section 1.2 (italiques ajoutés):

bzeron'est pas une fonction ANSI C. Il est dérivé du premier code de réseau Berkely. Néanmoins, nous l'utilisons dans tout le texte, au lieu de la memsetfonction ANSI C , car il bzeroest plus facile à retenir (avec seulement deux arguments) que memset(avec trois arguments). Presque tous les fournisseurs qui prennent en charge l'API sockets fournissent également bzero, et sinon, nous fournissons une définition de macro dans notre en- unp.htête.

En effet, l'auteur de TCPv3 [TCP / IP Illustrated, Volume 3 - Stevens 1996] a commis l'erreur de permuter les deuxième et troisième arguments memseten 10 occurrences lors de la première impression . Le compilateur AC ne peut pas intercepter cette erreur car les deux arguments sont du même type. (En fait, le deuxième argument est un intet le troisième est size_t, qui est généralement un unsigned int, mais les valeurs spécifiées, respectivement 0 et 16, sont toujours acceptables pour l'autre type d'argument.) L'appel à memsettoujours fonctionnait, car seul un peu de fonctions de socket exigent en fait que les 8 derniers octets d'une structure d'adresse de socket Internet soient mis à 0. Néanmoins, c'était une erreur, et une qui pourrait être évitée en utilisant bzero, car l'échange des deux arguments en bzerosera toujours intercepté par le compilateur C si des prototypes de fonction sont utilisés.

Je pense également que la grande majorité des appels à memset()sont à zéro mémoire, alors pourquoi ne pas utiliser une API adaptée à ce cas d'utilisation?

Un inconvénient possible bzero()est que les compilateurs pourraient être plus susceptibles d'optimiser memcpy()parce que c'est standard et qu'ils pourraient donc être écrits pour le reconnaître. Cependant, gardez à l'esprit qu'un code correct est toujours meilleur qu'un code incorrect optimisé. Dans la plupart des cas, l'utilisation bzero()n'entraînera pas un impact notable sur les performances de votre programme, et cela bzero()peut être une macro ou une fonction en ligne qui se développe en memcpy().

Michael Burr
la source
Oui, je suppose que cela pourrait être un raisonnement lorsque vous travaillez dans une salle de classe comme celle-ci, de manière à rendre les choses potentiellement moins déroutantes pour les étudiants. Cependant, je ne pense pas que ce soit le cas de mon professeur. C'était un très grand professeur de RTFM. Si vous aviez une question à laquelle le manuel pouvait répondre, il afficherait les pages de manuel sur le projecteur en classe et vous les montrerait. Il voulait vraiment insinuer dans l'esprit de chacun que le manuel est là pour être lu et répond à la plupart de vos questions. J'en suis reconnaissant, contrairement à certains autres professeurs.
PseudoPsyche
5
Je pense que c'est un argument qui peut être fait même en dehors de la salle de classe - j'ai vu ce bogue dans le code de production. Cela me semble une erreur facile à commettre. Je suppose également que la grande majorité des memset()appels consistent simplement à supprimer un bloc de mémoire, ce qui, je pense, est un autre argument pour bzero(). Que signifie le «b» bzero()de toute façon?
Michael Burr
7
+1. Cela memsetviole un ordre de paramètre commun de "buffer, buffer_size" le rend particulièrement sujet aux erreurs.
jamesdlin
En Pascal, ils évitent cela en l'appelant "fillchar" et il prend un caractère. La plupart des compilateurs C / C ++ choisiraient celui-là. Ce qui me fait me demander pourquoi les compilateurs ne disent pas "vous passez un pointeur 32/64 bits là où un octet est attendu" et vous lancent fermement dans les erreurs du compilateur.
Lundi
1
@Gewure deuxième et troisième arguments sont dans le mauvais ordre; l'appel de fonction cité ne fait exactement rien
Ichthyo
4

Je voulais parler de l'argument bzero vs memset. Installez ltrace puis comparez ce qu'il fait sous le capot. Sous Linux avec libc6 (2.19-0ubuntu6.6), les appels effectués sont exactement les mêmes (via ltrace ./test123):

long m[] = {0}; // generates a call to memset(0x7fffefa28238, '\0', 8)
int* p;
bzero(&p, 4);   // generates a call to memset(0x7fffefa28230, '\0', 4)

On m'a dit qu'à moins de travailler dans les entrailles profondes de la libc ou de n'importe quel nombre d'interfaces noyau / syscall, je n'ai pas à m'inquiéter pour eux. Tout ce dont je dois m'inquiéter, c'est que l'appel satisfasse à l'exigence de remise à zéro du tampon. D'autres ont mentionné lequel est préférable à l'autre, je vais donc m'arrêter ici.

chewing-gum
la source
Cela se produit parce que certaines versions de GCC émettront du code memset(ptr, 0, n)quand elles voient bzero(ptr, n)et qu'elles ne peuvent pas le convertir en code en ligne.
zwol
@zwol C'est en fait une macro.
SS Anne le
1
@SSAnne gcc 9.3 sur mon ordinateur effectue cette transformation lui-même, sans aucune aide des macros dans les en-têtes du système. extern void bzero(void *, size_t); void clear(void *p, size_t n) { bzero(p, n); }produit un appel à memset. (Inclure stddef.hpour size_tsans rien d'autre qui pourrait interférer.)
zwol
4

Vous ne devriez probablement pas utiliser bzero, ce n'est pas vraiment du C standard, c'était une chose POSIX.

Et notez que le mot «était» - il était obsolète dans POSIX.1-2001 et supprimé dans POSIX.1-2008 par respect pour memset, il est donc préférable d'utiliser la fonction C standard.

paxdiablo
la source
Qu'entendez-vous par standard C? Vous voulez dire qu'il ne se trouve pas dans la bibliothèque C standard?
Koray Tugay
@Koray, la norme C signifie la norme ISO et, oui, bzeron'en fait pas partie.
paxdiablo
Non, je veux dire, je ne sais pas ce que vous entendez par aucune norme. La norme ISO signifie-t-elle la bibliothèque C standard? Cela vient avec la langue? La bibliothèque minimale dont on sait qu'elle sera là?
Koray Tugay
2
@Koray, l'ISO est l'organisation de normalisation responsable de la norme C, l'actuelle étant C11, et les précédentes C99 et C89. Ils établissent les règles qu'une implémentation doit suivre pour être considérée C. Donc oui, si la norme dit qu'une implémentation doit fournir memset, elle sera là pour vous. Sinon, ce n'est pas C.
paxdiablo
2

Pour la fonction memset, le deuxième argument est an intet le troisième argument est size_t,

void *memset(void *s, int c, size_t n);

qui est typiquement un unsigned int, mais si les valeurs comme, 0 and 16pour le deuxième et le troisième argument respectivement sont entrées dans le mauvais ordre comme 16 et 0 alors, un tel appel à memset peut toujours fonctionner, mais ne fera rien. Parce que le nombre d'octets à initialiser est spécifié comme 0.

void bzero(void *s, size_t n)

Une telle erreur peut être évitée en utilisant bzero, car la permutation des deux arguments en bzero sera toujours interceptée par le compilateur C si des prototypes de fonction sont utilisés.

haïr
la source
1
Une telle erreur peut également être évitée avec memset si vous pensez simplement à l'appel comme "définissez cette mémoire à cette valeur pour cette taille", ou si vous avez un IDE qui vous donne le prototype ou même si vous savez simplement ce que vous faire :-)
paxdiablo
D'accord, mais cette fonction a été créée au moment où ces IDE intelligents n'étaient pas disponibles pour le support.
havish le
2

En bref: memset nécessitent alors plus d'opérations d'assemblage bzero.

Voici la source: http://fdiv.net/2009/01/14/memset-vs-bzero-ultimate-showdown

Bar Tal
la source
Oui, c'est une chose que j'ai mentionnée dans le PO. J'ai même lié à cette page exacte. Il s'avère que cela ne semble pas vraiment faire de différence en raison de certaines optimisations du compilateur. Pour plus de détails, voir la réponse acceptée par ouah.
PseudoPsyche
6
Cela montre seulement qu'une implémentation de mauvaise qualité de memset est lente. Sur MacOS X et certains autres systèmes, memset utilise du code qui est configuré au moment du démarrage en fonction du processeur que vous utilisez, utilise pleinement les registres vectoriels et pour les grandes tailles, il utilise des instructions de prélecture de manière intelligente pour obtenir le dernier bit de vitesse.
gnasher729
moins d'instructions ne signifie pas une exécution plus rapide. En fait, les optimisations augmentent souvent la taille binaire et le nombre d'instructions en raison du déroulement de la boucle, de l'insertion de fonctions, de l'alignement de la boucle ... Regardez n'importe quel code optimisé décent et vous verrez qu'il a souvent beaucoup plus d'instructions que d'implémentations de merde
phuclv
2

Ayez-le comme vous le souhaitez. :-)

#ifndef bzero
#define bzero(d,n) memset((d),0,(n))
#endif

Notez que:

  1. L'original bzerone renvoie rien, memsetretourne le pointeur void ( d). Cela peut être résolu en ajoutant le transtypage à void dans la définition.
  2. #ifndef bzerone vous empêche pas de masquer la fonction d'origine même si elle existe. Il teste l'existence d'une macro. Cela peut causer beaucoup de confusion.
  3. Il est impossible de créer un pointeur de fonction vers une macro. Lorsque vous utilisez bzerovia des pointeurs de fonction, cela ne fonctionnera pas.
Bruce
la source
1
Quel est le problème avec ça, @Leeor? Antipathie générale pour les macros? Ou vous n'aimez pas le fait que cette macro puisse être confondue avec la fonction (et peut-être même la cacher)?
Palec
1
@Palec, ce dernier. Cacher une redéfinition en tant que macro peut conduire à tant de confusion. Un autre programmeur utilisant ce code pense qu'il utilise une chose et est forcé sans le savoir à utiliser l'autre. C'est une bombe à retardement.
Leeor
1
Après avoir réfléchi à nouveau, je conviens que c'est en effet une mauvaise solution. Entre autres, j'ai trouvé une raison technique: lors de l'utilisation bzerovia des pointeurs de fonction, cela ne fonctionnera pas.
Palec
Vous devriez vraiment avoir appelé votre macro autre chose que bzero. C'est une atrocité.
Dan Bechard
-2

memset prend 3 paramètres, bzero prend 2 en mémoire contraint ce paramètre supplémentaire prendrait 4 octets de plus et la plupart du temps, il sera utilisé pour tout mettre à 0

Skynight
la source