web-dev-qa-db-fra.com

Que signifie faire une "vérification nulle" en C ou C ++?

J'ai appris le C++ et j'ai du mal à comprendre null. En particulier, les tutoriels que j'ai lus mentionnent une "vérification nulle", mais je ne sais pas ce que cela signifie ni pourquoi c'est nécessaire.

  • Qu'est-ce qui est nul exactement?
  • Que signifie "vérifier la nullité"?
  • Dois-je toujours vérifier la null?

Tout exemple de code serait très apprécié.

21
kdi

En C et C++, les pointeurs sont intrinsèquement dangereux, c'est-à-dire que lorsque vous déréférencez un pointeur, il est de votre responsabilité de vous assurer qu'il pointe vers un endroit valide; cela fait partie de la "gestion manuelle de la mémoire" (contrairement aux schémas de gestion automatique de la mémoire implémentés dans des langages comme Java, PHP ou le runtime .NET, qui ne vous permettront pas de créer des références invalides sans effort considérable).

Une solution courante qui détecte de nombreuses erreurs consiste à définir tous les pointeurs qui ne pointent vers rien comme NULL (ou, en C++ correct, 0), Et à vérifier cela avant d'accéder au pointeur. Plus précisément, il est courant d'initialiser tous les pointeurs sur NULL (sauf si vous avez déjà quelque chose vers lequel les pointer lorsque vous les déclarez), et de les définir sur NULL lorsque vous delete ou free() les (sauf s'ils sortent du champ d'application immédiatement après). Exemple (en C, mais aussi en C++ valide):

void fill_foo(int* foo) {
    *foo = 23; // this will crash and burn if foo is NULL
}

Une meilleure version:

void fill_foo(int* foo) {
    if (!foo) { // this is the NULL check
        printf("This is wrong\n");
        return;
    }
    *foo = 23;
}

Sans la vérification nulle, le passage d'un pointeur NULL dans cette fonction entraînera une erreur de segmentation, et il n'y a rien que vous puissiez faire - le système d'exploitation tuera simplement votre processus et peut-être le vidage de mémoire ou affichera une boîte de dialogue de rapport de plantage. Avec la vérification nulle en place, vous pouvez effectuer une gestion des erreurs appropriée et récupérer correctement - corrigez le problème vous-même, abandonnez l'opération en cours, écrivez une entrée de journal, informez l'utilisateur, tout ce qui est approprié.

27
tdammers

Les autres réponses couvraient à peu près votre question exacte. Une vérification nulle est effectuée pour être sûr que le pointeur que vous avez reçu pointe réellement vers une instance valide d'un type (objets, primitives, etc.).

Je vais ajouter ici mon propre conseil. Évitez les contrôles nuls. :) Les contrôles nuls (et d'autres formes de programmation défensive) encombrent le code et le rendent plus sujet aux erreurs que les autres techniques de gestion des erreurs.

Ma technique préférée quand il s'agit de pointeurs d'objets est d'utiliser le Null Object pattern . Cela signifie renvoyer un (pointeur - ou mieux encore, une référence à un) tableau ou liste vide au lieu de null, ou renvoyer une chaîne vide ("") au lieu de null, ou même la chaîne "0" (ou quelque chose d'équivalent à "rien "dans le contexte) où vous vous attendez à ce qu'il soit analysé en un entier.

En bonus, voici un petit quelque chose que vous ne saviez peut-être pas sur le pointeur nul, qui a été (d'abord formellement) implémenté par C.A.R. Hoare pour la langue ALGOL W en 1965.

J'appelle cela mon erreur d'un milliard de dollars. C'était l'invention de la référence nulle en 1965. A cette époque, je concevais le premier système de type complet pour les références dans un langage orienté objet (ALGOL W). Mon objectif était de garantir que toute utilisation des références soit absolument sûre, la vérification étant effectuée automatiquement par le compilateur. Mais je n'ai pas pu résister à la tentation de mettre une référence nulle, tout simplement parce qu'elle était si facile à mettre en œuvre. Cela a conduit à d'innombrables erreurs, vulnérabilités et plantages du système, qui ont probablement causé un milliard de dollars de douleur et de dommages au cours des quarante dernières années.

7
Yam Marcovic

La valeur du pointeur nul représente un "nulle part" bien défini; c'est une valeur de pointeur invalide qui est garantie de comparer inégale à toute autre valeur de pointeur. Tenter de déréférencer un pointeur nul entraîne un comportement indéfini et conduit généralement à une erreur d'exécution, vous devez donc vous assurer qu'un pointeur n'est pas NULL avant de tenter de déréférencer. Un certain nombre de fonctions de bibliothèque C et C++ renverront un pointeur nul pour indiquer une condition d'erreur. Par exemple, la fonction de bibliothèque malloc renverra une valeur de pointeur nulle si elle ne peut pas allouer le nombre d'octets qui ont été demandés, et tenter d'accéder à la mémoire via ce pointeur entraînera (généralement) une erreur d'exécution:

int *p = malloc(sizeof *p * N);
p[0] = ...; // this will (usually) blow up if malloc returned NULL

Nous devons donc nous assurer que l'appel malloc a réussi en vérifiant la valeur de p par rapport à NULL:

int *p = malloc(sizeof *p * N);
if (p != NULL) // or just if (p)
  p[0] = ...;

Maintenant, accrochez-vous à vos chaussettes une minute, cela va devenir un peu cahoteux.

Il y a un pointeur nul valeur et un pointeur nul constant, et les deux ne sont pas nécessairement les mêmes. Le pointeur nul valeur est la valeur que l'architecture sous-jacente utilise pour représenter "nulle part". Cette valeur peut être 0x00000000, ou 0xFFFFFFFF, ou 0xDEADBEEF, ou quelque chose de complètement différent. Ne supposez pas que le pointeur nul valeur est toujours 0.

Le pointeur nul constant, OTOH, est toujours une expression intégrale à valeur 0. En ce qui concerne votre code source, 0 (ou toute expression intégrale évaluée à 0) représente un pointeur nul. C et C++ définissent la macro NULL comme constante de pointeur null. Lorsque votre code est compilé, le pointeur nul constant sera remplacé par le pointeur nul approprié valeur dans le code machine généré.

Sachez également que NULL n'est qu'une des nombreuses valeurs de pointeur invalides; si vous déclarez une variable de pointeur automatique sans l'initialiser explicitement, comme

int *p;

la valeur initialement stockée dans la variable est indéterminée, et peut ne pas correspondre à une adresse mémoire valide ou accessible. Malheureusement, il n'existe aucun moyen (portable) de savoir si une valeur de pointeur non NULL est valide ou non avant de tenter de l'utiliser. Donc, si vous avez affaire à des pointeurs, c'est généralement une bonne idée de les initialiser explicitement sur NULL lorsque vous les déclarez et de les définir sur NULL lorsqu'ils ne pointent activement vers rien.

Notez que c'est plus un problème en C qu'en C++; idiomatique C++ ne devrait pas utiliser autant de pointeurs.

4
John Bode

Il existe plusieurs méthodes, toutes font essentiellement la même chose.

 int * foo = NULL; // parfois défini sur 0x00 ou 0 ou 0L au lieu de NULL 

vérification nulle (vérifier si le pointeur est nul), version A

 if (foo == NULL) 

contrôle nul, version B

 if (! foo) // puisque NULL est défini comme 0,! foo renverra une valeur à partir d'un pointeur nul 

contrôle nul, version C

 si (foo == 0) 

Parmi les trois, je préfère utiliser la première vérification car elle indique explicitement aux futurs développeurs ce que vous essayez de vérifier ET cela indique clairement que vous vous attendiez à ce que foo soit un pointeur.

3
user53019

Non. La seule raison d'utiliser un pointeur en C++ est que vous souhaitez explicitement la présence de pointeurs nuls; sinon, vous pouvez prendre une référence, qui est à la fois plus facile à utiliser sémantiquement et garantit une valeur non nulle.

2
DeadMG