Début 2018, une série d'articles est apparue sur notre blog dédiée à la sixième vérification du code source du projet Chromium. Le cycle comprend 8 articles consacrés aux erreurs et recommandations pour leur prévention. Deux articles ont provoqué une discussion animée et, jusqu'à présent, j'ai rarement reçu de commentaires sur les sujets abordés. Peut-être, quelques explications supplémentaires devraient être données et, comme on dit, parsèment le i.
Une année s'est écoulée depuis la rédaction d'une série d'articles consacrés à la prochaine vérification des codes sources du projet Chromium:
- Chrome: sixième vérification de projet et 250 bogues
- Beau chrome et memset maladroit
- rupture et retombée
- Chrome: fuites de mémoire
- Fautes de frappe au chrome
- Chrome: utilisation de données inexactes
- Pourquoi est-il important de vérifier ce que la fonction malloc a renvoyé
- Chrome: autres erreurs
Les articles sur
memset et
malloc ont provoqué et continuent de provoquer des discussions qui me semblent étranges. Apparemment, il y avait un malentendu dû au fait que je n'ai pas formulé clairement mes pensées. J'ai décidé de revenir sur ces articles et d'apporter quelques précisions.
memset
Commençons par l'article sur
memset , car tout est simple ici. Il y avait une controverse sur la meilleure façon d'initialiser les structures. Beaucoup de programmeurs ont écrit qu'il vaut mieux donner une recommandation de ne pas écrire:
HDHITTESTINFO hhti = {};
Et donc:
HDHITTESTINFO hhti = { 0 };
Arguments:
- La construction {0} est plus facile à remarquer lors de la lecture de code que {}.
- La construction {0} est plus intuitive que {}. Autrement dit, 0 suggère que la structure est remplie de zéros.
En conséquence, on me propose de changer cet exemple d'initialisation dans l'article. Je ne suis pas d'accord avec les arguments et je n'ai pas l'intention de modifier l'article. Maintenant, je justifie ma position.
À propos de la visibilité. Je pense que c'est une question de goût et d'habitude. Je ne pense pas que la présence de 0 à l'intérieur des accolades change fondamentalement la situation.
Mais avec le deuxième argument, je ne suis pas du tout d'accord. Un enregistrement comme {0} donne une raison de mal comprendre le code. Par exemple, vous pouvez calculer que si vous remplacez 0 par 1, tous les champs seront initialisés en unités. Par conséquent, ce style d'écriture est plus nuisible qu'utile.
L'analyseur PVS-Studio a même un diagnostic connexe
V1009 sur ce sujet, dont je vais maintenant donner la description.
V1009. Vérifiez l'initialisation de la baie. Seul le premier élément est initialisé explicitement.L'analyseur a détecté une erreur possible du fait que lors de la déclaration d'un tableau, la valeur est spécifiée pour un seul élément. Ainsi, le reste des éléments sera initialisé implicitement à zéro ou au constructeur par défaut.
Prenons un exemple de code suspect:
int arr[3] = {1};
Le programmeur s'attendait peut-être à ce que
arr soit composé d'une unité, mais ce n'est pas le cas. Le tableau sera composé des valeurs 1, 0, 0.
Le bon code est:
int arr[3] = {1, 1, 1};
Une telle confusion peut se produire en raison de la similitude avec la construction
arr = {0} , qui initialise le tableau entier à zéro.
Si des conceptions similaires sont activement utilisées dans votre projet, vous pouvez désactiver ces diagnostics.
Il est également déconseillé de négliger la visibilité du code.
Par exemple, le code d'encodage des valeurs de couleur est écrit comme suit:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00 }; int Green[3] = { 0x00, 0xff };
Grâce à l'initialisation implicite, toutes les couleurs sont définies correctement, mais il est préférable de réécrire le code plus clairement:
int White[3] = { 0xff, 0xff, 0xff }; int Black[3] = { 0x00, 0x00, 0x00 }; int Green[3] = { 0x00, 0xff, 0x00 };
malloc
Avant de poursuivre la lecture, je vous demande de rafraîchir le contenu de l'article "
Pourquoi est-il important de vérifier ce que la fonction malloc a renvoyé ". Cet article a suscité beaucoup de discussions et de critiques. Voici quelques-unes des discussions:
reddit.com/r/cpp ,
reddit.com/r/C_Programming ,
habr.com . Parfois, ils m'écrivent à propos de cet article par la poste maintenant.
L'article est critiqué par les lecteurs sur les points suivants:
1. Si malloc a retourné NULL , il vaut mieux quitter immédiatement le programme que d'écrire un tas d' if et essayer de gérer en quelque sorte le manque de mémoire, à cause de quoi il est souvent impossible d'exécuter le programme de toute façon.Je n'ai pas du tout appelé jusqu'à la dernière pour faire face aux conséquences de manquer de mémoire, en jetant l'erreur de plus en plus haut. S'il est permis à une application de se fermer sans avertissement, qu'il en soit ainsi. Pour ce faire, une seule vérification est nécessaire immédiatement après
malloc ou en utilisant
xmalloc (voir le paragraphe suivant).
Je me suis opposé et averti de l'absence de contrôles, à cause de laquelle le programme continue de fonctionner "comme si de rien n'était". C'est complètement différent. Ceci est dangereux car il conduit à un comportement indéfini, à une corruption des données, etc.
2. Pas parlé de la solution, qui consiste à écrire des wrappers de fonctions pour allouer de la mémoire avec vérification ultérieure ou à utiliser des fonctions existantes, comme xmalloc .Je suis d'accord, j'ai raté ce moment. Lors de la rédaction d'un article, je ne pensais tout simplement pas à la façon de régler la situation. Il était plus important pour moi de faire part au lecteur du danger d'un manque de vérification. Comment corriger une erreur est déjà une question de goût et de détails de mise en œuvre.
La fonction
xmalloc ne
fait pas partie de la bibliothèque standard C (voir "
Quelle est la différence entre xmalloc et malloc? "). Cependant, cette fonction peut être déclarée dans d'autres bibliothèques, par exemple, dans la bibliothèque GNU utils (
GNU libiberty ).
L'essence de la fonction est que le programme se termine si l'allocation de mémoire échoue. Une implémentation de cette fonction pourrait ressembler à ceci:
void* xmalloc(size_t s) { void* p = malloc(s); if (!p) { fprintf (stderr, "fatal: out of memory (xmalloc(%zu)).\n", s); exit(EXIT_FAILURE); } return p; }
Par conséquent, en appelant la fonction
xmalloc partout au lieu de
malloc, vous pouvez être sûr que le programme n'aura pas de comportement indéfini en raison de toute utilisation du pointeur nul.
Malheureusement,
xmalloc n'est pas non plus une panacée pour tous les maux. Il ne faut pas oublier que l'utilisation de
xmalloc est inacceptable lorsqu'il s'agit d'écrire du code de bibliothèque. J'en parlerai un peu plus tard.
3. La plupart des commentaires étaient les suivants: "dans la pratique, malloc ne renvoie jamais NULL ."Heureusement, je ne comprends pas seul que ce soit la mauvaise approche. J'ai vraiment aimé ce
commentaire dans mon soutien:
De l'expérience de discuter d'un tel sujet, on a le sentiment qu'il y a deux sectes sur Internet. Les adhérents du premier sont fermement convaincus que sous Linux, malloc ne retourne jamais NULL. Les adhérents de la seconde sont fermement convaincus que si la mémoire du programme ne peut pas être allouée, mais que rien ne peut être fait en principe, il suffit de tomber. Ils ne peuvent en aucun cas être convaincus. Surtout quand ces deux sectes se croisent. Vous ne pouvez que le prendre pour acquis. Et peu importe la ressource de profil sur laquelle porte la discussion.J'ai pensé et décidé de suivre les conseils et je n'essaierai pas de persuader :). Espérons que ces équipes de développement n'écrivent que des programmes non critiques. Si, par exemple, certaines données se détériorent dans le jeu ou que le jeu plante, ce n'est pas grave.
La seule chose importante est que les développeurs de bibliothèques, de bases de données, etc. ne le font pas.
Un appel à la bibliothèque et aux développeurs de code responsables
Si vous développez une bibliothèque ou un autre code responsable, vérifiez toujours la valeur du pointeur renvoyé par la fonction
malloc / realloc et renvoyez le code d'erreur à l'extérieur si la mémoire n'a pas pu être allouée.
Dans les bibliothèques, vous ne pouvez pas appeler la fonction de
sortie si vous n'avez pas pu allouer de mémoire. Pour la même raison, vous ne pouvez pas utiliser
xmalloc . Pour de nombreuses applications, il est inacceptable de simplement les prendre et de les planter. Pour cette raison, par exemple, la base de données peut être corrompue. Les données qui ont été comptées pendant plusieurs heures peuvent être perdues. De ce fait, le programme peut être vulnérable aux vulnérabilités de déni de service, alors qu'au lieu de gérer correctement la charge croissante, l'application multithread s'arrête simplement.
On ne peut pas supposer comment et dans quels projets la bibliothèque sera utilisée. Par conséquent, il faut supposer que l'application peut résoudre des tâches très importantes. Et le «tuer» simplement en appelant
exit n'est pas bon. Très probablement, un tel programme est écrit en tenant compte de la possibilité de manquer de mémoire et peut faire quelque chose dans ce cas. Par exemple, certains systèmes de CAO, en raison de la forte fragmentation de la mémoire, ne peuvent pas allouer une mémoire tampon suffisante pour la prochaine opération. Mais ce n'est pas une raison pour qu'elle se termine en mode d'urgence avec perte de données. Un programme peut permettre de sauvegarder un projet et de se redémarrer en mode normal.
En aucun cas, vous ne pouvez compter sur le fait que
malloc peut toujours allouer de la mémoire. On ne sait pas sur quelle plateforme et comment la bibliothèque sera utilisée. Si sur une plate-forme un manque de mémoire est exotique, alors sur une autre, il peut s'agir d'une situation très courante.
On ne peut pas espérer que si
malloc renvoie
NULL , le programme se bloquera. Tout peut arriver. Comme je l'ai décrit dans l'
article , un programme peut tout simplement écrire des données à une adresse non nulle. Par conséquent, certaines données peuvent être corrompues, ce qui entraîne des conséquences imprévisibles. Même le
memset est dangereux. Si les données sont remplies dans l'ordre inverse, alors au début, certaines données sont corrompues, et alors seulement le programme se bloque. Mais la chute peut arriver trop tard. Si des données corrompues sont utilisées dans des threads parallèles au moment de la fonction
memset , les conséquences peuvent être fatales. Vous pouvez obtenir une transaction endommagée dans la base de données ou envoyer une commande pour supprimer les fichiers "inutiles". Tout peut arriver à temps. Je suggère au lecteur d'imaginer indépendamment à quoi peut entraîner l'utilisation des ordures en mémoire.
Ainsi, la bibliothèque n'a qu'une seule option correcte pour travailler avec les fonctions
malloc . Vous devez IMMÉDIATEMENT vérifier que la fonction a renvoyé, et si elle est NULL, puis renvoyer l'état d'erreur.
Liens annexes
- Manipulation de MOO .
- Amusez-vous avec les pointeurs NULL: partie 1 , partie 2 .
- Ce que tout programmeur C devrait savoir sur le comportement non défini: partie 1 , partie 2 , partie 3 .

Si vous souhaitez partager cet article avec un public anglophone, veuillez utiliser le lien vers la traduction: Andrey Karpov.
Sixième vérification du chrome, postface .