mise à zéro de la mémoire

gcc 4.4.4 C89

Je me demande simplement ce que font la plupart des programmeurs C lorsqu’ils veulent mettre à zéro la mémoire.

Par exemple, j’ai un tampon de 1024 octets. Parfois je fais ça:

char buffer[1024] = {0}; 

Ce qui mettra à zéro tous les octets.

Cependant, devrais-je le déclarer comme ceci et utiliser memset?

 char buffer[1024]; . . memset(buffer, 0, sizeof(buffer)); 

Y a-t-il une raison réelle pour laquelle vous devez mettre à zéro la mémoire? Quel est le pire qui puisse arriver en ne le faisant pas?

Le pire qui puisse arriver? Vous vous retrouvez (sans le vouloir) avec une chaîne qui n’est pas terminée par la valeur NULL ou par un entier qui hérite de ce qui se trouve à droite de celle-ci après avoir imprimé une partie du tampon. Cependant, les chaînes non terminées peuvent également se produire de différentes manières, même si vous avez initialisé le tampon.

Éditer (à partir de commentaires) La fin du monde est également une possibilité lointaine, en fonction de ce que vous faites.

L’un ou l’autre est indésirable. Cependant, à moins d’éviter complètement la mémoire allouée dynamicment, la plupart des tampons alloués de manière statique sont généralement plutôt petits, ce qui rend memset() relativement bon marché. En fait, beaucoup moins cher que la plupart des appels à calloc() pour les blocs dynamics, qui ont tendance à être plus grands que ~ 2k.

c99 contient un langage concernant les valeurs d’initialisation par défaut. Je ne peux toutefois pas donner l’ gcc -std=c99 , avec n’importe quel type de stockage.

Néanmoins, avec beaucoup de compilateurs plus anciens (et des compilateurs qui ne sont pas tout à fait c99) toujours utilisés, je préfère simplement utiliser memset()

Je préfère grandement

 char buffer[1024] = { 0 }; 

Il est plus court, plus facile à lire et moins sujet aux erreurs. memset sur les mémoires tampons allouées dynamicment, puis préférez calloc .

Lorsque vous définissez le char buffer[1024] sans initialiser, vous obtiendrez des données non définies. Par exemple, Visual C ++ en mode débogage sera initialisé avec 0xcd. En mode Release, il va simplement allouer la mémoire sans se soucier de ce qui se trouve dans ce bloc par rapport à une utilisation précédente.

De plus, vos exemples illustrent l’initialisation du temps d’exécution par rapport à la compilation. Si votre char buffer[1024] = { 0 } est une déclaration globale ou statique, il sera stocké dans le segment de données du binary avec ses données initialisées, augmentant ainsi la taille de votre binary d’environ 1024 octets (dans ce cas). Si la définition est dans une fonction, elle est stockée dans la stack et est allouée à l’exécution et non stockée dans le binary. Si vous fournissez un initialiseur dans ce cas, l’initialiseur est stocké dans le binary et un équivalent de memcpy() est créé pour initialiser le buffer au moment de l’exécution.

J’espère que cela vous aidera à choisir la méthode qui vous convient le mieux.

Dans ce cas particulier, il n’y a pas beaucoup de différence. Je préfère = { 0 } à memset car memset est plus sujet aux erreurs:

  • C’est une occasion de se tromper.
  • Il offre la possibilité de mélanger les arguments de memset (par exemple, memset(buf, sizeof buf, 0) au lieu de memset(buf, 0, sizeof buf) .

En général, = { 0 } est également préférable pour initialiser la struct . Il initialise effectivement tous les membres comme si vous aviez écrit = 0 pour les initialiser. Cela signifie que les membres du pointeur sont assurés d’être initialisés avec le pointeur null ( qui peut ne pas correspondre à bits nuls tous, et tous bits nuls correspond à ce que vous obtiendriez si vous aviez utilisé memset ).

D’autre part, = { 0 } peut laisser les bits de remplissage d’une struct comme des ordures. Par conséquent, il se peut que cela ne soit pas approprié si vous envisagez d’utiliser memcmp pour les comparer ultérieurement.

Le pire qui puisse arriver en ne le faisant pas est que vous écrivez certaines données caractère par caractère et que vous les interprétiez ensuite comme une chaîne (et que vous n’avez pas écrit de terminateur nul). Ou vous finissez par échouer à réaliser qu’une partie de celle-ci était non initialisée et à la lire comme s’il s’agissait de données valides. Fondamentalement: toutes sortes de méchanceté.

Memset devrait bien se passer (à condition de corriger la faute de frappe typo :-)). Je préfère cela à votre premier exemple parce que je pense que c’est plus clair.

Pour la mémoire allouée dynamicment, j’utilise calloc plutôt que malloc et memset.

Je préfère utiliser memset pour effacer une partie de la mémoire, en particulier lorsque vous travaillez avec des chaînes. Je veux savoir sans aucun doute qu’il y aura un délimiteur nul après ma chaîne. Oui, je sais que vous pouvez append un \0 à la fin de chaque chaîne et certaines fonctions le font pour vous, mais je ne veux pas de doute que cela a eu lieu.

Une fonction peut échouer lors de l’utilisation de votre tampon et celui-ci rest inchangé. Préféreriez-vous avoir un tampon de déchets inconnus, ou rien?

Une des choses qui peut arriver si vous n’initialisez pas, c’est que vous courez le risque de fuir des informations sensibles.

La mémoire non initialisée peut contenir quelque chose de sensible provenant d’une utilisation antérieure de cette mémoire. Peut-être un mot de passe ou une clé de cryptage ou une partie d’un email privé. Votre code peut ultérieurement transmettre ce tampon ou cette structure quelque part, ou l’écrire sur le disque. Si vous ne l’avez que partiellement rempli, le rest de celui-ci contient toujours le contenu précédent. Certains systèmes sécurisés nécessitent la mise à zéro des mémoires tampons lorsqu’un espace adresse peut contenir des informations sensibles.

Ce message a été fortement édité pour le rendre correct. Merci beaucoup à Tyler McHenery pour avoir souligné ce que j’ai manqué.

 char buffer[1024] = {0}; 

Définira le premier caractère du tampon dans la valeur null et le compilateur développera ensuite tous les caractères non initialisés à 0 également. Dans un tel cas, il semble que les différences entre les deux techniques se résument à savoir si le compilateur génère un code plus optimisé pour l’initialisation du tableau ou si memset est optimisé plus rapidement que le code compilé généré.

Auparavant j’ai déclaré:

tampon de char [1024] = {0};

Définira le premier caractère du tampon sur null. Cette technique est couramment utilisée pour les chaînes à terminaison null, car toutes les données situées après la première valeur NULL sont ignorées par les fonctions suivantes (non boguées) qui gèrent les chaînes à terminaison NULL.

Ce qui n’est pas tout à fait vrai. Désolé pour la mauvaise communication, et merci encore pour les corrections.

Cela dépend de la façon dont vous le remplissez: si vous avez l’intention d’écrire avant même de lire quoi que ce soit, alors pourquoi vous en donner? Cela dépend aussi de l’utilisation que vous allez faire du tampon: s’il doit être traité comme une chaîne, il vous suffit de définir le premier octet sur \0 :

 char buffer[1024]; buffer[0] = '\0'; 

Cependant, si vous l’utilisez en tant que stream d’octets, le contenu de l’ensemble du tableau sera probablement pertinent. Par conséquent, le fait de configurer l’intégralité de l’élément ou de le définir sur { 0 } comme dans votre exemple, est un choix judicieux.

J’utilise aussi memset (tampon, 0, sizeof (tampon));

Le risque de ne pas l’utiliser est qu’il n’y a aucune garantie que la mémoire tampon que vous utilisez soit complètement vide, il pourrait y avoir des ordures pouvant entraîner un comportement imprévisible.

Toujours memset-ing à 0 après malloc, est une très bonne pratique.

yup, la méthode calloc () définie dans stdlib.h alloue de la mémoire initialisée avec des zéros.

Je ne connais pas le:

 char buffer[1024] = {0}; 

technique. Mais en supposant que cela fasse ce que je pense, il y a une différence (potentielle) entre les deux techniques.

Le premier est fait au moment de la compilation, et le tampon fera partie de l’image statique de l’exécutable, et sera donc 0 quand vous le chargez.

Ce dernier sera fait au moment de l’exécution.

Le premier peut entraîner un comportement de temps de chargement. Si vous avez juste:

 char buffer[1024]; 

les chargeurs modernes peuvent très bien “virtuellement” charger cela … c’est-à-dire qu’il ne faudra pas réellement d’espace dans le fichier, il s’agira simplement d’une instruction au chargeur de découper un bloc lorsque le programme sera chargé. Je ne suis pas assez à l’aise avec les chargeurs modernes dire si c’est vrai ou pas.

Mais si vous le pré-initialisez, il faudra certainement le charger à partir de l’exécutable.

Remarquez, ni l’un ni l’autre n’a d’impacts «réels» sur les performances dans le petit. Ils peuvent ne pas en avoir dans le “grand”. Juste en disant qu’il y a un potentiel ici, et que les deux techniques font en fait quelque chose de très différent.