J'interviewais hier un type pour un poste d'ingénieur en logiciel de niveau intermédiaire. Il a mentionné qu'en C, NULL n'était pas toujours nul et qu'il avait vu des implémentations de C où NULL n'était pas nul. Je trouve cela très suspect, mais je veux être sûr. Quelqu'un sait s'il a raison?
(Les réponses n'affecteront pas mon jugement sur ce candidat, j'ai déjà soumis ma décision à mon manager.)
Je suppose que vous voulez dire le pointeur nul. Il est garanti que la comparaison est égale à 0
.1 Mais il n'est pas nécessaire qu'il soit représenté avec des bits nuls.2
Voir aussi le comp.lang.c FAQ sur les pointeurs nuls.
Le pointeur null constant est toujours égal à 0. La macro NULL
peut être définie par l'implémentation comme un 0
nu, ou une expression de conversion comme (void *) 0
, ou une autre expression entière à valeur zéro le standard).
Le pointeur null value peut être différent de 0. Lorsqu'une constante de pointeur null est rencontrée, elle est convertie en la valeur de pointeur null appropriée.
Le § 6.3.2.3 de la norme C99 dit
Une expression constante entière avec la valeur 0, ou une telle expression convertie en un type void *, est appelée une constante de pointeur nulle) Si une constante de pointeur nulle est convertie en un type de pointeur, le pointeur résultant, appelé pointeur null, garantit une comparaison inégale à un pointeur sur un objet ou une fonction.
Le § 7.17 dit aussi
[...] NULL qui se développe en une constante de pointeur null définie par l'implémentation [...]
L'adresse du pointeur NULL peut être différente de 0, alors qu'il se comportera comme dans la plupart des cas.
(Cela devrait être le même que dans les anciennes normes C, que je n'ai pas sous la main pour le moment)
En C, il existe un et un seul contexte dans lequel il est nécessaire de convertir explicitement une constante de pointeur null en un type de pointeur spécifique pour que le programme fonctionne correctement. Ce contexte passe un pointeur null dans une liste d'arguments de fonctions non typées. Dans modern C, cela ne se produit que lorsque vous devez passer un pointeur null à une fonction prenant un nombre variable d'arguments. (Dans l’héritage C, cela se produit avec toute fonction non déclarée avec un prototype.) L’exemple paradigmatique est execl
, où le tout dernier argument doit être un pointeur null explicitement converti en (char *)
:
execl("/bin/ls", "ls", "-l", (char *)0); // correct
execl("/bin/ls", "ls", "-l", (char *)NULL); // correct, but unnecessarily verbose
execl("/bin/ls", "ls", "-l", 0); // undefined behavior
execl("/bin/ls", "ls", "-l", NULL); // ALSO undefined behavior
Oui, ce dernier exemple a un comportement non défini même siNULL
est défini par ((void *)0)
, car void *
et char *
sont non implicitement interconvertibles lorsqu'ils sont passés à travers une liste d'arguments non typés, même s'ils sont partout ailleurs.
"Sous le capot", le problème ici est pas avec le modèle de bits utilisé pour un pointeur null, mais le compilateur peut avoir besoin de connaître le type concret exact de chaque argument pour configurer correctement une trame d'appel . (Considérez le MC68000, avec ses registres d'adresse et de données distincts; certains ABI spécifiaient les arguments de pointeur à transmettre dans les registres d'adresse mais les arguments entiers dans les registres de données. Pensez également à tout ABI où int
et void *
ne sont pas de la même taille. , mais C prévoit toujours explicitement que void *
et char *
ne soient pas de la même taille.) S'il existe un prototype de fonction, le compilateur peut l'utiliser, mais les fonctions non prototypées et les arguments variadiques n'offrent aucune assistance de ce type.
C++ est plus compliqué et je ne me sens pas qualifié pour expliquer comment.
Il a raison, sur certaines implémentations, la taille du pointeur n’est pas identique à la taille de l’entier. NULL dans le contexte entier est 0, mais la disposition binaire réelle ne doit pas nécessairement être composée de 0.