web-dev-qa-db-fra.com

'\ 0' et NULL peuvent-ils être utilisés de manière interchangeable?

NULL est souvent utilisé dans le contexte des pointeurs et est défini via des macros dans plusieurs bibliothèques standard (telles que <iostream>) pour être l'entier 0. '\0' est le caractère nul et correspond à 8 bits de zéros. Par ailleurs, 8 bits de zéros est équivalent à l'entier 0.

Dans certains cas, bien qu'il soit considéré comme un style horrible, ces deux peuvent être échangés:

int *p='\0';
if (p==NULL) //evaluates to true
    cout << "equal\n";

Ou

char a=NULL;
char b='\0';
if (a==b) //evaluates to true
    cout << "equal again\n";

Il existe déjà de nombreuses questions similaires sur SO seul; par exemple, la meilleure réponse à cette question ( Quelle est la différence entre NULL, '\ 0' et ) dit "ce n'est pas vraiment la même chose".

Quelqu'un pourrait-il fournir un exemple indiquant que NULL et \0 ne peut pas être échangé (de préférence une application réelle et non un cas pathologique)?

55
cwest

Quelqu'un pourrait-il fournir un exemple indiquant que NULL et\0 ne peuvent pas être échangés?

La différence entre NULL et '\0' Peut affecter la résolution de surcharge.

Exemple ( cochez-le sur Colir ):

#include <iostream>

// The overloaded function under question can be a constructor or 
// an overloaded operator, which would make this example less silly
void foo(char)   { std::cout << "foo(char)"  << std::endl; }
void foo(int)    { std::cout << "foo(int)"   << std::endl; }
void foo(long)   { std::cout << "foo(long)"  << std::endl; }
void foo(void*)  { std::cout << "foo(void*)" << std::endl; }

int main()
{
    foo('\0'); // this will definitely call foo(char)
    foo(NULL); // this, most probably, will not call foo(char)
}

Notez que le compilateur gcc utilisé chez Coliru définit NULL comme 0L, Ce qui pour cet exemple signifie que foo(NULL) se résout en foo(long) plutôt qu'en foo(void*). Cette réponse traite de cet aspect en détail.

51
Leon

Définition de la macro NULL en C++

Leon a raison que lorsqu'il y a plusieurs surcharges pour la même fonction, \0 Préférerait celle qui prend le paramètre de type char. Cependant, il est important de noter que sur un compilateur typique, NULL préférerait la surcharge qui prend le paramètre de type int, pas de type void*!

Ce qui cause probablement cette confusion, c'est que le langage C permet de définir NULL comme (void*)0. La norme C++ indique explicitement (projet N3936, page 444):

Les définitions possibles [de la macro NULL] incluent 0 Et 0L, Mais pas (void*)0.

Cette restriction est nécessaire, car par ex. char *p = (void*)0 est C valide mais C++ non valide, tandis que char *p = 0 est valide dans les deux.

Dans C++ 11 et versions ultérieures, vous devez utiliser nullptr, si vous avez besoin d'une constante nulle qui se comporte comme un pointeur.

Comment la suggestion de Léon fonctionne dans la pratique

Ce code définit plusieurs surcharges d'une même fonction. Chaque surcharge génère le type du paramètre:

#include <iostream>

void f(int) {
    std::cout << "int" << std::endl;
}

void f(long) {
    std::cout << "long" << std::endl;
}

void f(char) {
    std::cout << "char" << std::endl;
}

void f(void*) {
    std::cout << "void*" << std::endl;
}

int main() {
    f(0);
    f(NULL);
    f('\0');
    f(nullptr);
}

On Ideone cette sortie

int
int
char
void*

Je dirais donc que le problème des surcharges n'est pas une application réelle mais un cas pathologique. La constante NULL se comportera de toute façon mal et devrait être remplacée par nullptr en C++ 11.

Et si NULL n'est pas nul?

Un autre cas pathologique suggéré par Andrew Keeton à une autre question:

Notez que ce qui est un pointeur nul dans le langage C. Peu importe l'architecture sous-jacente. Si l'architecture sous-jacente a une valeur de pointeur nulle définie comme adresse 0xDEADBEEF, alors c'est au compilateur de trier ce gâchis.

En tant que tel, même sur cette architecture amusante, les moyens suivants sont toujours des moyens valides de rechercher un pointeur nul:

if (!pointer)
if (pointer == NULL)
if (pointer == 0)

Les méthodes suivantes sont INVALIDES pour rechercher un pointeur nul:

#define MYNULL (void *) 0xDEADBEEF
if (pointer == MYNULL)
if (pointer == 0xDEADBEEF)

car ceux-ci sont vus par un compilateur comme des comparaisons normales.

Sommaire

Dans l'ensemble, je dirais que les différences sont principalement stylistiques. Si vous avez une fonction qui prend int et une surcharge qui prend char, et qu'elles fonctionnent différemment, vous remarquerez une différence lorsque vous les appelez avec \0 Et NULL constantes. Mais dès que vous placez ces constantes dans des variables, la différence disparaît, car la fonction appelée est déduite du type de la variable.

L'utilisation de constantes correctes rend le code plus facile à gérer et donne une meilleure signification. Vous devez utiliser 0 Lorsque vous voulez dire un nombre, \0 Lorsque vous voulez dire un caractère et nullptr quand vous voulez dire un pointeur. Matthieu M. souligne dans les commentaires que GCC avait un bug , dans lequel un char* Était comparé à \0, Alors que l'intention était de déréférencer le pointeur et de comparer un char à \0. De telles erreurs sont plus faciles à détecter si un style approprié est utilisé dans la base de code.

Pour répondre à votre question, il n'y a pas vraiment de cas d'utilisation réel qui vous empêcherait d'utiliser \0 Et NULL de manière interchangeable. Juste des raisons stylistiques et quelques cas Edge.

36
Ville-Valtteri

Veuillez ne pas faire ça. C'est un anti-modèle, et c'est en fait faux. NULL est pour les pointeurs NULL, '\0' est le caractère nul. Ce sont logiquement des choses différentes.

Je ne pense pas avoir jamais vu ça:

int* pVal='\0';

Mais cela est assez courant:

char a=NULL;

Mais ce n'est pas une bonne forme. Cela rend le code moins portable et à mon avis moins lisible. Il est également susceptible de provoquer des problèmes dans des environnements mixtes C/C++.

Il repose sur des hypothèses concernant la façon dont une implémentation particulière définit NULL. Par exemple, certaines implémentations utilisent un simple

#define NULL 0

D'autres pourraient utiliser:

#define NULL ((void*) 0)

Et j'en ai vu d'autres se définir comme un entier, et toutes sortes de traitements bizarres.

NULL devrait, à mon avis, être utilisé uniquement pour indiquer une adresse invalide. Si vous voulez un caractère nul, utilisez '\0'. Ou définissez ceci comme NULLCHR. Mais ce n'est pas aussi propre.

Cela rendra votre code plus portable - vous ne commencerez pas à recevoir des avertissements concernant les types, etc. si vous modifiez les paramètres du compilateur/environnement/compilateur. Cela peut être plus important dans un environnement C ou mixte C/C++.

Un exemple d'avertissements pouvant survenir: Considérez ce code:

#define NULL 0
char str[8];
str[0]=NULL;

Cela équivaut à:

#define NULL 0
char str[8];
str[0]=0;

Et nous attribuons une valeur entière à un caractère. Cela peut provoquer un avertissement du compilateur, et s'il y a suffisamment d'occurrences de cela, vous ne verrez bientôt aucun avertissement important. Et pour moi, c'est le vrai problème. Avoir des avertissements dans le code a deux effets secondaires:

  1. Étant donné suffisamment d'avertissements, vous n'en repérez pas de nouveaux.
  2. Il donne le signal que les avertissements sont acceptables.

Dans les deux cas, les bogues réels peuvent alors passer, qui seraient détectés par le compilateur si nous avions pris la peine de lire les avertissements (ou d'activer -Werror)

8
mjs

Oui, ils peuvent présenter un comportement différent lors de la résolution des fonctions surchargées.

func('\0') invoque func(char),

tandis que

func(NULL) invoque func(integer_type).


Vous pouvez supprimer la confusion en utilisant nullptr , qui est toujours un type de pointeur, n'affiche aucune ambiguïté lorsque attribue/compare la valeur ou la résolution de surcharge de fonction .

char a = nullptr; //error : cannot convert 'std::nullptr_t' to 'char' in initialization
int x = nullptr;  //error : nullptr is a pointer not an integer

Notez que, il est toujours compatible avec NULL:

int *p=nullptr;
if (p==NULL) //evaluates to true

Extrait du livre C++ Programming Stroustrup 4th Edition:

Dans un code plus ancien, 0 ou NULL est généralement utilisé à la place de nullptr (§7.2.2). Cependant, l'utilisation de nullptr élimine la confusion potentielle entre les entiers (tels que 0 ou NULL) et les pointeurs (tels que nullptr).


8
Saurav Sahu

Les programmes informatiques ont deux types de lecteurs.

Le premier type sont des programmes informatiques, comme le compilateur.

Le deuxième type sont les humains, comme vous et vos collègues.

Les programmes sont généralement bien avec l'obtention d'un type de zéro à la place d'un autre. Il y a des exceptions, comme l'ont souligné les autres réponses, mais ce n'est pas vraiment important.

Ce qui est important, c'est que vous jouez avec les lecteurs humains .

Les lecteurs humains sont très sensibles au contexte. En utilisant le mauvais zéro, vous êtes mentir pour vous lecteurs humains. Ils vont te maudire.

Un être humain menti peut ignorer plus facilement les bogues.
Un humain à qui on a menti peut voir des "bogues" qui ne sont pas là. Lors de la "correction" de ces bogues phanthom, ils introduisent de vrais bogues.

Ne mentez pas à vos humains. L'un des humains auxquels vous mentez est votre futur moi. Tu vas aussi te maudire.

7
Stig Hemmer

Extraits du projet N3936 en C++ 14:

18.2 Types [support.types]

3 La macro NULL est une constante de pointeur nul C++ définie par l'implémentation dans la présente Norme internationale (4.10).

4.10 Conversions de pointeur [conv.ptr]

1 A constante de pointeur nul est un littéral entier (2.14.2) de valeur 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 le valeur du pointeur nul de ce type et se distingue de toutes les autres valeurs du type pointeur objet ou pointeur fonction.

Ainsi, NULL peut être n'importe quel littéral entier de valeur zéro, ou une valeur de type std::nullptr_t comme nullptr, tandis que '\0' est toujours le littéral zéro à caractère étroit.

Donc, pas en général interchangeables, même si dans un contexte de pointeur, vous ne pouvez voir qu'une différence stylistique.

Un exemple serait:

#include <iostream>
#include <typeinfo>

int main() {
    std::cout << typeid('\0').name << '\n'
        << typeid(NULL).name << '\n'
        << typeid(nullptr).name << '\n';
}
4
Deduplicator

Définissons ce qui est NULL en C/C++.

Selon la référence C/C++, NULL est défini comme une macro qui se développe en une constante de pointeur null. Ensuite, nous pouvons lire qu'une constante de pointeur nul peut être convertie en n'importe quel type de pointeur (ou type pointeur sur membre), qui acquiert une valeur de pointeur nulle. Il s'agit d'une valeur spéciale qui indique que le pointeur ne pointe vers aucun objet.

Définition se référant à C:

Une constante pointeur nul est une expression constante intégrale qui est évaluée à zéro (comme 0 ou 0L), ou le transtypage de cette valeur pour taper void * (comme (void *) 0).

Définition faisant référence à C++ 98:

Une constante pointeur nul est une expression constante intégrale qui est évaluée à zéro (telle que 0 ou 0L).

Définition faisant référence à C++ 11:

Une constante de pointeur nul est soit une expression constante intégrale évaluée à zéro (telle que 0 ou 0L), soit une valeur de type nullptr_t (telle que nullptr).

Exemple de méthodes de surcharge.

Supposons que nous avons les méthodes suivantes:

class Test {
public:
    method1(char arg0);
    method1(int arg0);
    method1(void* arg0);
    method1(bool arg0);
}

L'appel de method1 avec l'argument NULL ou nullptr doit appeler method1(void* arg0);. Cependant, si nous appelons method1 avec l'argument '\0' Ou 0, Il faut exécuter method1(char arg0); et method1(int arg0);.

1
java-devel