Vérification du pointeur NULL en C / C ++ [fermé]

153

Dans une récente révision de code, un contributeur tente de faire en sorte que toutes les NULLvérifications sur les pointeurs soient effectuées de la manière suivante:

int * some_ptr;
// ...
if (some_ptr == NULL)
{
    // Handle null-pointer error
}
else
{
    // Proceed
}

au lieu de

int * some_ptr;
// ...
if (some_ptr)
{
    // Proceed
}
else
{
    // Handle null-pointer error
}

Je conviens que son chemin est un peu plus clair dans le sens où il dit explicitement "Assurez-vous que ce pointeur n'est pas NULL", mais je le contredirais en disant que quiconque travaille sur ce code comprendrait que l'utilisation d'une variable de pointeur dans un ifinstruction vérifie implicitement NULL. De plus, je pense que la deuxième méthode a moins de chances d'introduire un bogue de ce genre:

if (some_ptr = NULL)

ce qui est juste une douleur absolue à trouver et à déboguer.

Quelle voie préférez-vous et pourquoi?

Marbre Bryan
la source
32
Avoir un style cohérent est parfois plus important que le style que vous choisissez.
Mark Ransom du
15
@Mark: La cohérence est toujours le facteur le plus important de votre style.
sbi
13
"De plus, je pense que la deuxième méthode a moins de chances d'introduire un bogue de ce genre: if (some_ptr = NULL)" C'est pourquoi certaines personnes utilisent if (NULL == some_ptr), ne peuvent pas attribuer à NULL donc vous obtiendriez une erreur de compilation.
user122302
14
la plupart des compilateurs de nos jours avertissent de l'affectation conditionnelle - malheureusement trop de développeurs ignorent les avertissements du compilateur.
Mike Ellery du
10
Je ne suis pas d'accord avec l'affirmation ci-dessus selon laquelle la cohérence est ce qui compte. Cela n'a rien à voir avec la cohérence; pas du bon genre de toute façon. C'est dans ce domaine que nous devrions laisser les programmeurs exprimer leur propre style. S'il y a une personne qui ne comprend pas immédiatement l'une ou l'autre des formes, elle devrait arrêter immédiatement de lire le code et envisager un changement de carrière. Je vais en dire plus: s'il existe une cohérence cosmétique que vous ne pouvez pas appliquer automatiquement avec un script, supprimez-la. Le coût de l'épuisement de la morale humaine est BIEN plus important que ces décisions stupides que les gens prennent lors d'une réunion.
wilhelmtell

Réponses:

203

D'après mon expérience, les tests de la forme if (ptr)ou if (!ptr)sont préférés. Ils ne dépendent pas de la définition du symbole NULL. Ils n'exposent pas la possibilité d'une affectation accidentelle. Et ils sont clairs et succincts.

Edit: Comme le souligne SoapBox dans un commentaire, ils sont compatibles avec les classes C ++ telles que auto_ptrqui sont des objets qui agissent comme des pointeurs et qui fournissent une conversion boolpour activer exactement cet idiome. Pour ces objets, une comparaison explicite à NULLdevrait invoquer une conversion en pointeur qui peut avoir d'autres effets secondaires sémantiques ou être plus coûteuse que la simple vérification d'existence qu'implique la boolconversion.

J'ai une préférence pour le code qui dit ce que cela signifie sans texte inutile. if (ptr != NULL)a la même signification que if (ptr)mais au prix d'une spécificité redondante. La prochaine chose logique est d'écrireif ((ptr != NULL) == TRUE) et c'est ainsi que réside la folie. Le langage C est clair qu'un booléen testé par if, whileou similaire a une signification spécifique de valeur non nulle est vrai et zéro est faux. La redondance ne le rend pas plus clair.

RBerteig
la source
23
De plus, ils sont compatibles avec les classes wrapper de pointeur (shared_ptr, auto_ptr, scoped_tr, etc.) qui remplacent généralement l'opérateur bool (ou safe_bool).
SoapBox
2
Je vote ceci comme "réponse" simplement en raison de la référence à auto_ptr ajoutant à la discussion. Après avoir écrit la question, il est clair que cela est en fait plus subjectif qu'autre chose et ne convient probablement pas pour une "réponse" de stackoverflow puisque n'importe laquelle de ces réponses pourrait être "correcte" selon les circonstances.
Bryan Marble
2
@Bryan, je respecte cela, et si une «meilleure» réponse se présente, n'hésitez pas à déplacer la coche si nécessaire. Quant à la subjectivité, oui elle est subjective. Mais c'est au moins une discussion subjective où il y a des problèmes objectifs qui ne sont pas toujours évidents lorsque la question est posée. La ligne est floue. Et subjectif ... ;-)
RBerteig
1
Une chose importante à noter: jusqu'à récemment, j'étais en faveur d'écrire "if (ptr)" au lieu de "if (ptr! = NULL)" ou "if (ptr! = Nullptr)" car c'est plus concis donc ça fait le code plus lisible. Mais je viens de découvrir que la comparaison soulève (resp.) Un avertissement / une erreur pour la comparaison avec NULL / nullptr sur les compilateurs récents. Donc, si vous voulez vérifier un pointeur, mieux vaut faire "if (ptr! = NULL)" au lieu de "if (ptr)". Si vous déclarez par erreur ptr comme un int, la première vérification lèvera un avertissement / une erreur, tandis que la dernière compilera sans aucun avertissement.
Arnaud
1
@David 天宇 Wong Non, ce n'est pas ce que cela voulait dire. L'exemple de cette page est la séquence de deux lignes int y = *x; if (!x) {...}où l'optimiseur est autorisé à raisonner que puisque *xne serait pas défini si xest NULL, il ne doit pas être NULL et !xdoit donc être FALSE. L'expression !ptrn'est pas un comportement indéfini. Dans l'exemple, si le test a été effectué avant toute utilisation de *x, alors l'optimiseur aurait tort d'éliminer le test.
RBerteig
52

if (foo)est assez clair. Utilise le.

Wilx
la source
25

Je vais commencer par ceci: la cohérence est roi, la décision est moins importante que la cohérence de votre base de code.

En C ++

NULL est défini comme 0 ou 0L en C ++.

Si vous avez lu le langage de programmation C ++ Bjarne Stroustrup suggère d'utiliser 0explicitement pour éviter la NULLmacro lors de l'affectation , je ne sais pas s'il a fait la même chose avec les comparaisons, cela fait un moment que j'ai lu le livre, je pense qu'il vient de le faire if(some_ptr)sans comparaison explicite mais je suis floue là-dessus.

La raison en est que la NULLmacro est trompeuse (comme presque toutes les macros le sont), elle est en fait 0littérale, pas un type unique comme son nom l'indique. Éviter les macros est l'une des directives générales en C ++. D'autre part,0 ressemble à un entier et ce n'est pas le cas lorsqu'il est comparé ou attribué à des pointeurs. Personnellement, je pourrais aller dans les deux sens, mais généralement, je saute la comparaison explicite (bien que certaines personnes n'aiment pas cela, c'est probablement pourquoi vous avez un contributeur suggérant de toute façon un changement).

Indépendamment des sentiments personnels, c'est en grande partie un choix de moindre mal car il n'y a pas une seule bonne méthode.

C'est clair et un idiome courant et je le préfère, il n'y a aucune chance d'attribuer accidentellement une valeur lors de la comparaison et cela se lit clairement:

if(some_ptr){;}

C'est clair si vous savez qu'il some_ptrs'agit d'un type de pointeur, mais cela peut aussi ressembler à une comparaison d'entiers:

if(some_ptr != 0){;}

C'est clair, dans les cas courants, cela a du sens ... Mais c'est une abstraction qui fuit, NULLest en fait 0littérale et pourrait finir par être facilement utilisée à mauvais escient:

if(some_ptr != NULL){;}

C ++ 0x a nullptr qui est maintenant la méthode préférée car elle est explicite et précise, faites juste attention à l'affectation accidentelle:

if(some_ptr != nullptr){;}

Jusqu'à ce que vous soyez en mesure de migrer vers C ++ 0x, je dirais que c'est une perte de temps à vous soucier de laquelle de ces méthodes vous utilisez, elles sont toutes insuffisantes, c'est pourquoi nullptr a été inventé (avec des problèmes de programmation génériques qui ont abouti à une transmission parfaite .) Le plus important est de maintenir la cohérence.

En C

C est une bête différente.

En C NULL peut être défini comme 0 ou comme ((void *) 0), C99 autorise l'implémentation de constantes de pointeur nul définies. Cela revient donc à la définition de NULL par l'implémentation et vous devrez l'inspecter dans votre bibliothèque standard.

Les macros sont très courantes et en général, elles sont beaucoup utilisées pour compenser les lacunes du support de programmation générique dans le langage et d'autres choses. Le langage est beaucoup plus simple et le recours au pré-processeur est plus courant.

De ce point de vue, je recommanderais probablement d'utiliser la NULLdéfinition de macro en C.

M2tM
la source
tl; dr et bien que vous avez raison 0en C ++, il a un sens dans C: (void *)0. 0est préférable à NULLC ++, car les erreurs de type peuvent être un PITA.
Matt Joiner
@Matt: Mais NULLc'est zéro de toute façon.
GManNickG
@GMan: Pas sur mon système: #define NULL ((void *)0)delinux/stddef.h
Matt Joiner
@Matt: en C ++. Vous avez dit que 0 est préférable, mais NULLdoit être égal à zéro.
GManNickG
C'est un bon point, j'ai négligé de le mentionner et j'améliore ma réponse en le suggérant. ANSI C peut avoir NULL défini comme ((void *) 0), C ++ définit NULL comme 0. Je n'ai pas parcouru le standard pour cela directement, mais je crois comprendre qu'en C ++ NULL peut être 0 ou 0L.
M2tM
19

J'utilise if (ptr), mais cela ne vaut absolument pas la peine de discuter.

J'aime mon chemin parce qu'il est concis, bien que d'autres disent == NULLqu'il est plus facile à lire et plus explicite. Je vois d'où ils viennent, je ne suis pas d'accord, les choses supplémentaires facilitent les choses. (Je déteste la macro, donc je suis partial.) À vous de décider.

Je ne suis pas d'accord avec votre argument. Si vous ne recevez pas d'avertissements pour les affectations dans un conditionnel, vous devez augmenter vos niveaux d'avertissement. Aussi simple que cela. (Et pour l'amour de tout ce qui est bon, ne les changez pas.)

Note en C ++ 0x, nous pouvons le faire if (ptr == nullptr), ce qui me fait plus agréable lecture. (Encore une fois, je déteste la macro. Mais nullptrc'est bien.) Je le fais toujours if (ptr), juste parce que c'est ce à quoi je suis habitué.

GManNickG
la source
espérons que 5 années d'expérience supplémentaires vous ont changé d'avis :) si C ++ / C avait un opérateur quelque chose comme if_exist (), alors cela aurait du sens.
magulla
10

Franchement, je ne vois pas pourquoi c'est important. L'un ou l'autre est assez clair et toute personne moyennement expérimentée avec C ou C ++ devrait comprendre les deux. Un commentaire, cependant:

Si vous prévoyez de reconnaître l'erreur et de ne pas continuer à exécuter la fonction (c'est-à-dire que vous allez lancer une exception ou renvoyer un code d'erreur immédiatement), vous devez en faire une clause de garde:

int f(void* p)
{
    if (!p) { return -1; }

    // p is not null
    return 0;
}

De cette façon, vous évitez le "code fléché".

James McNellis
la source
quelqu'un a pointé ici kukuruku.co/hub/programming/i-do-not-know-c qui if(!p)est un comportement indéfini. Cela pourrait fonctionner ou non.
David 天宇 Wong
@David 天宇 Wong si vous parlez de l'exemple n ° 2 sur ce lien, ce if(!p)n'est pas le comportement indéfini, c'est la ligne qui le précède. Et if(p==NULL)aurait exactement le même problème.
Mark Ransom
8

Personnellement, je l'ai toujours utilisé if (ptr == NULL)parce que cela rend mon intention explicite, mais à ce stade, c'est juste une habitude.

L'utilisation =à la place de ==sera interceptée par tout compilateur compétent avec les paramètres d'avertissement corrects.

Le point important est de choisir un style cohérent pour votre groupe et de s'y tenir. Quel que soit votre chemin, vous finirez par vous y habituer, et la perte de friction lorsque vous travaillez dans le code d'autres personnes sera la bienvenue.

Mark Ransom
la source
7

Encore un point en faveur de la foo == NULLpratique: si fooest, disons, un int *ou un bool *, alors le if (foo)contrôle peut accidentellement être interprété par un lecteur comme testant la valeur de la pointee, c'est-à-dire comme if (*foo). La NULLcomparaison ici est un rappel que nous parlons d'un pointeur.

Mais je suppose qu'une bonne convention de dénomination rend cet argument sans objet.

Daniel Hershcovich
la source
4

En fait, j'utilise les deux variantes.

Il y a des situations où vous vérifiez d'abord la validité d'un pointeur, et s'il est NULL, vous retournez / sortez simplement d'une fonction. (Je sais que cela peut conduire à la discussion "si une fonction n'a qu'un seul point de sortie")

La plupart du temps, vous vérifiez le pointeur, puis faites ce que vous voulez et résolvez le cas d'erreur. Le résultat peut être le vilain code indenté x fois avec plusieurs if.

dwo
la source
1
Je déteste personnellement le code fléché qui résulterait en utilisant un point de sortie. Pourquoi ne pas quitter si je connais déjà la réponse?
ruslik
1
@ruslik: en C (ou C-avec-classes de style C ++), avoir plusieurs retours rend le nettoyage plus difficile. Dans le "vrai" C ++, c'est évidemment un non-problème car votre nettoyage est géré par RAII, mais certains programmeurs sont (pour des raisons plus ou moins valables) bloqués dans le passé, et refusent, ou n'y sont pas autorisés, comptent sur RAII .
jalf
@jalf dans de tels cas, un gotopeut être très pratique. De plus, dans de nombreux cas, un malloc()fichier nécessitant un nettoyage pourrait être remplacé par un fichier plus rapide alloca(). Je sais qu'ils ne sont pas recommandés, mais ils existent pour une raison (pour moi, une étiquette bien nommée gotoest beaucoup plus propre qu'un if/elsearbre obscurci ).
ruslik le
1
allocaest non standard et complètement non sûr. En cas d'échec, votre programme explose. Il n'y a aucune chance de récupération, et comme la pile est relativement petite, une panne est probable. En cas d'échec, il est possible que vous écrasiez le tas et introduisiez des vulnérabilités compromettant les privilèges. N'utilisez jamais allocaou vlas à moins que vous n'ayez une petite limite sur la taille qui sera allouée (et alors vous pourriez aussi bien utiliser un tableau normal).
R .. GitHub STOP HELPING ICE
4

Le langage de programmation C (K&R) vous demande de vérifier la valeur null == ptr pour éviter une affectation accidentelle.

Derek
la source
23
K&R demanderait également "qu'est-ce qu'un pointeur intelligent?" Les choses changent dans le monde C ++.
Alex Emelianov du
2
ou plutôt, les choses ont déjà changé dans le monde C ++ ";)
jalf
3
Où K&R déclare-t-il cela (réf)? Ils n'utilisent jamais ce style eux-mêmes. Dans K & R2 chapitre 2, ils recommandent même if (!valid)ci-dessus if (valid == 0).
schot
6
Installez-vous, les amis. Il a dit k & r, pas K&R. Ils sont évidemment moins connus car leurs initiales ne sont même pas en majuscules.
jmucchiello
1
la mise en majuscule vous ralentit
Derek
3

Si le style et le format font partie de vos critiques, il devrait y avoir un guide de style convenu pour mesurer. S'il y en a un, faites ce que dit le guide de style. S'il n'y en a pas, les détails comme celui-ci doivent être conservés tels qu'ils sont écrits. C'est une perte de temps et d'énergie, et distrait de ce que les révisions de code devraient vraiment révéler. Sérieusement, sans un guide de style, je pousserais pour ne PAS changer de code comme celui-ci par principe, même s'il n'utilise pas la convention que je préfère.

Et non pas que cela compte, mais ma préférence personnelle l'est if (ptr). Le sens m'est plus immédiatement évident que même if (ptr == NULL).

Peut-être essaie-t-il de dire qu'il vaut mieux gérer les conditions d'erreur avant le chemin heureux? Dans ce cas, je ne suis toujours pas d'accord avec le critique. Je ne sais pas s'il existe une convention acceptée pour cela, mais à mon avis, la condition la plus «normale» devrait être la première dans toute déclaration if. De cette façon, j'ai moins de recherches à faire pour comprendre en quoi consiste la fonction et comment elle fonctionne.

L'exception à cela est si l'erreur me fait renoncer à la fonction ou si je peux m'en remettre avant de passer à autre chose. Dans ces cas, je gère l'erreur en premier:

if (error_condition)
  bail_or_fix();
  return if not fixed;

// If I'm still here, I'm on the happy path

En traitant la condition inhabituelle dès le départ, je peux m'en occuper et ensuite l'oublier. Mais si je ne peux pas revenir sur le chemin heureux en le traitant d'avance, alors il devrait être traité après le cas principal car cela rend le code plus compréhensible. À mon avis.

Mais si ce n'est pas dans un guide de style, c'est juste mon avis, et votre opinion est tout aussi valable. Soit normaliser, soit ne pas le faire. Ne laissez pas un critique pseudo-standardiser simplement parce qu'il a une opinion.

Darryl
la source
1

C'est l'un des principes fondamentaux des deux langages que les pointeurs évaluent sur un type et une valeur qui peuvent être utilisés comme expression de contrôle, boolen C ++ et inten C. Il suffit de l'utiliser.

Jens Gustedt
la source
1
  • Les pointeurs ne sont pas des booléens
  • Les compilateurs modernes C / C ++ émettent un avertissement lorsque vous écrivez if (foo = bar)par accident.

Par conséquent je préfère

if (foo == NULL)
{
    // null case
}
else
{
    // non null case
}

ou

if (foo != NULL)
{
    // non null case
}
else
{
    // null case
}

Cependant, si j'écrivais un ensemble de règles de style, je ne mettrais pas des choses comme ça dedans, je mettrais des choses comme:

Assurez-vous de faire une vérification nulle sur le pointeur.

JeremyP
la source
2
Il est vrai que les pointeurs ne sont pas des booléens, mais en C, les instructions if ne prennent pas de booléens: elles prennent des expressions entières.
Ken
@Ken: c'est parce que C est cassé à cet égard. Conceptuellement, c'est une expression booléenne et (à mon avis) devrait être traitée comme telle.
JeremyP
1
Certains langages ont des instructions if qui testent uniquement les valeurs nulles / non nulles. Certains langages ont des instructions if qui testent uniquement un entier pour le signe (un test à trois voies). Je ne vois aucune raison de considérer C "cassé" parce qu'ils ont choisi un concept différent que vous aimez. Il y a beaucoup de choses que je déteste à propos de C mais cela signifie simplement que le modèle de programme C n'est pas le même que mon modèle mental, pas que l'un de nous (moi ou C) soit cassé.
Ken
@Ken: Un booléen n'est pas un nombre ou un pointeur conceptuellement , peu importe la langue.
JeremyP
1
Je n'ai pas dit qu'un booléen était un nombre ou un pointeur. J'ai dit qu'il n'y avait aucune raison d'insister sur le fait qu'une instruction if devrait ou ne pouvait prendre qu'une expression booléenne, et j'ai proposé des contre-exemples, en plus de celui qui nous occupe. Beaucoup de langages prennent autre chose qu'un booléen, et pas seulement à la manière «zéro / non nul» de C. En informatique, avoir une instruction if qui accepte (seulement) un booléen est un développement relativement récent.
Ken
1

Je suis un grand fan du fait que C / C ++ ne vérifie pas les types dans les conditions booléennes dans if, foret des whiledéclarations. J'utilise toujours ce qui suit:

if (ptr)

if (!ptr)

même sur les entiers ou tout autre type qui se convertit en booléen:

while(i--)
{
    // Something to do i times
}

while(cin >> a >> b)
{
    // Do something while you've input
}

Le codage dans ce style est plus lisible et plus clair pour moi. Juste mon avis personnel.

Récemment, en travaillant sur le microcontrôleur OKI 431, j'ai remarqué que ce qui suit:

unsigned char chx;

if (chx) // ...

est plus efficace que

if (chx == 1) // ...

car plus tard, le compilateur doit comparer la valeur de chx à 1. Où chx est juste un drapeau vrai / faux.

Donotalo
la source
1

La plupart des compilateurs que j'ai utilisés vont au moins avertir de l' ifaffectation sans autre sucre de syntaxe, donc je n'achète pas cet argument. Cela dit, j'ai utilisé les deux professionnellement et je n'ai aucune préférence pour l'un ou l'autre. Le == NULLest certainement plus clair à mon avis.

Michael Dorgan
la source