web-dev-qa-db-fra.com

Comment écrire correctement du code C / C ++ lorsque le pointeur nul n'est pas tous les bits zéro

Comme le dit comp.lang.c FAQ , il existe des architectures où le pointeur nul n'est pas tous les bits zéro. La question est donc de savoir ce qui vérifie réellement la construction suivante:

void* p = get_some_pointer();
if (!p)
    return;

Suis-je en train de comparer p avec un pointeur nul dépendant de la machine ou je compare p avec un zéro arithmétique?

Dois-je écrire

void* p = get_some_pointer();
if (NULL == p)
    return;

au lieu d'être prêt pour de telles architectures ou est-ce juste ma paranoïa?

69
ivaigult

Selon la spécification C:

Une expression de constante entière avec la valeur 0, ou une telle expression transtypée pour taper void *, est appelée une constante de pointeur nulle. 55) Si une constante de pointeur nul est convertie en un type de pointeur, le pointeur résultant, appelé pointeur nul, est garanti de comparer inégal à un pointeur à n'importe quel objet ou fonction.

0 Est donc une constante de pointeur nul. Et si nous le convertissons en un type de pointeur, nous obtiendrons un pointeur nul qui pourrait être différent de zéro pour certaines architectures. Voyons maintenant ce que la spécification dit à propos de la comparaison des pointeurs et d'une constante de pointeur nul:

Si un opérande est un pointeur et que l'autre est une constante de pointeur nulle, la constante de pointeur nulle est convertie en type de pointeur.

Considérons (p == 0): D'abord 0 Est converti en un pointeur nul, puis p est comparé à une constante de pointeur nul dont les valeurs binaires réelles dépendent de l'architecture.

Ensuite, voyez ce que la spécification dit à propos de l'opérateur de négation:

Le résultat de l'opérateur de négation logique! est 0 si la valeur de son opérande est différente de 0, 1 si la valeur de son opérande est égale à 0. Le résultat a le type int. L'expression! E est équivalente à (0 == E).

Cela signifie que (!p) Est équivalent à (p == 0) Qui, selon la spécification, teste p par rapport à la constante de pointeur nul définie par la machine.

Ainsi, vous pouvez écrire en toute sécurité if (!p) même sur des architectures où la constante de pointeur nul n'est pas entièrement composée de zéro.

Comme pour C++, une constante de pointeur nul est définie comme:

Une constante de pointeur nul est une expression constante constante (5.19) prvalue de type entier qui s'évalue à zéro ou une valeur de type std :: nullptr_t. Une constante de pointeur nul peut être convertie en un type de pointeur; le résultat est la valeur de pointeur nulle de ce type et se distingue de toutes les autres valeurs de type pointeur objet ou pointeur fonction.

Ce qui est proche de ce que nous avons pour C, plus le sucre de syntaxe nullptr. Le comportement de l'opérateur == Est défini par:

En outre, les pointeurs vers les membres peuvent être comparés, ou un pointeur vers le membre et une constante de pointeur nulle. Des conversions de pointeur à membre (4.11) et des conversions de qualification (4.4) sont effectuées pour les amener à un type commun. Si un opérande est une constante de pointeur nulle, le type commun est le type de l'autre opérande. Sinon, le type commun est un pointeur vers un type de membre similaire (4.4) au type de l'un des opérandes, avec une signature de qualification cv (4.4) qui est l'union des signatures de qualification cv des types d'opérande. [Remarque: cela implique que tout pointeur sur un membre peut être comparé à une constante de pointeur nulle. - note de fin]

Cela conduit à la conversion de 0 En un type de pointeur (comme pour C). Pour l'opérateur de négation:

L'opérande de l'opérateur de négation logique! est converti contextuellement en bool (article 4); sa valeur est vraie si l'opérande converti est vrai et faux sinon. Le type du résultat est bool.

Cela signifie que le résultat de !p Dépend de la façon dont la conversion du pointeur en bool est effectuée. La norme dit:

Une valeur nulle, une valeur de pointeur nulle ou une valeur de pointeur de membre nul est convertie en faux;

Donc if (p==NULL) et if (!p) fait la même chose en C++.

100
ivaigult

Peu importe si le pointeur nul est zéro sur tous les bits ou non dans la machine réelle. En supposant que p est un pointeur:

if (!p) 

est toujours un moyen légal de tester si p est un pointeur nul, et il est toujours équivalent à:

if (p == NULL)

Vous pouvez être intéressé par un autre article C-FAQ: C'est étrange. NULL est garanti à 0, mais le pointeur nul ne l'est pas?


Ci-dessus est vrai pour C et C++. Notez qu'en C++ (11), il est préférable d'utiliser nullptr pour littéral pointeur nul.

32
Yu Hao

Cette réponse s'applique à C.

Ne confondez pas NULL avec des pointeurs nuls. NULL est juste une macro garantie d'être constante de pointeur nul. Une constante de pointeur nul est garantie être 0 Ou (void*)0.

De C11 6.3.2.3:

Une expression de constante entière avec la valeur 0, ou une telle expression transtypée en type void *, est appelée une constante de pointeur nul 66). Si une constante de pointeur nul est convertie en un type de pointeur, le pointeur résultant, appelé pointeur nul, est garanti pour comparer inégal à un pointeur à n'importe quel objet ou fonction.

66) La macro NULL est définie dans <stddef.h> (et d'autres en-têtes) comme une constante de pointeur nul; voir 7.19.

7.19:

Les macros sont

NUL

qui se développe en une constante de pointeur nul définie par l'implémentation;

Défini par l'implémentation dans le cas de NULL, est soit 0 Soit (void*)0. NULL ne peut pas être autre chose.

Toutefois, lorsqu'une constante de pointeur nul est affectée à un pointeur, vous obtenez un pointeur nul, qui peut ne pas avoir la valeur zéro, même s'il se compare à une constante de pointeur nulle. Le code if (!p) n'a rien à voir avec la macro NULL, vous comparez un pointeur nul avec la valeur arithmétique zéro.

Donc, en théorie, un code comme int* p = NULL Peut entraîner un pointeur nul p qui est différent de zéro.

10
Lundin

À l'époque, les ordinateurs STRATUS avaient des pointeurs nuls comme 1 dans toutes les langues.

Cela a causé des problèmes pour C, donc leur compilateur C permettrait la comparaison du pointeur de 0 et 1 pour retourner vrai

Cela permettrait:

void * ptr=some_func();
if (!ptr)
{
    return;
}

Pour return sur un ptr nul même si vous pouviez voir que ptr avait une valeur de 1 dans le débogueur

if ((void *)0 == (void *)1)
{
    printf("Welcome to STRATUS\n");
}

Imprime en fait "Bienvenue dans STRATUS"

7
Glenn Teitelbaum

Si votre compilateur est bon, il y a deux choses (et seulement deux choses) à surveiller.

1: les pointeurs statiques initialisés par défaut (c'est-à-dire non attribués) ne contiendront pas NULL.

2: memset () sur une structure ou un tableau ou par extension calloc () ne définira pas les pointeurs sur NULL.

1
Joshua