Ce morceau de code est-il valide (et comportement défini)?
int &nullReference = *(int*)0;
Les deux g ++ et ++ clang compilation sans aucun avertissement, même lors de l' utilisation -Wall
, -Wextra
, -std=c++98
, -pedantic
, -Weffc++
...
Bien sûr, la référence n'est pas réellement nulle, puisqu'elle n'est pas accessible (cela signifierait déréférencer un pointeur nul), mais nous pourrions vérifier si elle est nulle ou non en vérifiant son adresse:
if( & nullReference == 0 ) // null reference
c++
reference
null
language-lawyer
peoro
la source
la source
if (false)
, éliminant la vérification, précisément parce que les références ne peuvent pas être nulles de toute façon. Une version mieux documentée existait dans le noyau Linux, où une vérification NULL très similaire a été optimisée: isc.sans.edu/diary.html?storyid=6820Réponses:
Les références ne sont pas des pointeurs.
8.3.2 / 1:
1.9 / 4:
Comme Johannes le dit dans une réponse supprimée, il y a un doute sur le fait que «déréférencer un pointeur nul» devrait être catégoriquement déclaré comme étant un comportement indéfini. Mais ce n'est pas l'un des cas qui soulèvent des doutes, puisqu'un pointeur nul ne pointe certainement pas vers un "objet ou une fonction valide", et il n'y a aucun désir au sein du comité des normes d'introduire des références nulles.
la source
&*p
aussip
universellement, cela n'exclut pas un comportement indéfini (qui, de par sa nature, peut «sembler fonctionner»); et je ne suis pas d'accord qu'unetypeid
expression qui cherche à déterminer le type d'un "pointeur nul déréférencé" déréférence en fait le pointeur nul. J'ai vu des gens argumenter sérieusement sur le fait que l'&a[size_of_array]
on ne peut et ne devrait pas se fier, et de toute façon, il est plus facile et sûr d'écrire simplementa + size_of_array
.*p
réfère la valeur l , quandp
est un pointeur nul. Le C ++ n'a actuellement pas la notion de lvalue vide, que le problème 232 voulait introduire.typeid
œuvres basées sur la syntaxe, au lieu de basées sur la sémantique. C'est, si voustypeid(0, *(ostream*)0)
vous faites avoir un comportement non défini -bad_typeid
n'est garanti d'être jeté, même si vous passez un lvalue résultant d'un déréférence de pointeur NULL sémantiquement. Mais syntaxiquement au niveau supérieur, ce n'est pas une déréférence, mais une expression d'opérateur virgule.La réponse dépend de votre point de vue:
Si vous jugez par le standard C ++, vous ne pouvez pas obtenir une référence nulle car vous obtenez d'abord un comportement non défini. Après cette première incidence de comportement indéfini, la norme permet que tout se passe. Donc, si vous écrivez
*(int*)0
, vous avez déjà un comportement indéfini car vous êtes, d'un point de vue standard du langage, en déréférençant un pointeur nul. Le reste du programme n'est pas pertinent, une fois cette expression exécutée, vous êtes hors du jeu.Cependant, en pratique, les références nulles peuvent facilement être créées à partir de pointeurs nulles et vous ne le remarquerez pas tant que vous n'aurez pas réellement essayé d'accéder à la valeur derrière la référence null. Votre exemple est peut-être un peu trop simple, car tout bon compilateur d'optimisation verra le comportement indéfini et optimisera simplement tout ce qui en dépend (la référence nulle ne sera même pas créée, elle sera optimisée).
Pourtant, cette optimisation dépend du compilateur pour prouver le comportement non défini, ce qui peut ne pas être possible. Considérez cette fonction simple dans un fichier
converter.cpp
:Lorsque le compilateur voit cette fonction, il ne sait pas si le pointeur est un pointeur nul ou non. Il génère donc simplement du code qui transforme n'importe quel pointeur en référence correspondante. (Btw: c'est un noop puisque les pointeurs et les références sont exactement la même bête dans l'assembleur.) Maintenant, si vous avez un autre fichier
user.cpp
avec le codele compilateur ne sait pas que
toReference()
va déréférencer le pointeur passé et suppose qu'il retourne une référence valide, qui se trouvera être une référence nulle en pratique. L'appel réussit, mais lorsque vous essayez d'utiliser la référence, le programme se bloque. J'espère. La norme permet que tout se passe, y compris l'apparition d'éléphants roses.Vous pouvez vous demander pourquoi c'est pertinent, après tout, le comportement indéfini a déjà été déclenché à l'intérieur
toReference()
. La réponse est le débogage: les références nulles peuvent se propager et proliférer comme le font les pointeurs nuls. Si vous n'êtes pas conscient que des références nulles peuvent exister et que vous apprenez à éviter de les créer, vous pouvez passer un certain temps à essayer de comprendre pourquoi votre fonction membre semble planter quand elle essaie simplement de lire un ancienint
membre (réponse: l'instance dans l'appel du membre était une référence nulle, de mêmethis
qu'un pointeur nul, et votre membre est calculé pour être localisé en tant qu'adresse 8).Alors, que diriez-vous de vérifier les références nulles? Vous avez donné la ligne
dans votre question. Eh bien, cela ne fonctionnera pas: selon la norme, vous avez un comportement non défini si vous déréférencer un pointeur nul, et vous ne pouvez pas créer une référence null sans déréférencer un pointeur nul, donc les références nulles n'existent que dans le domaine du comportement non défini. Puisque votre compilateur peut supposer que vous ne déclenchez pas de comportement indéfini, il peut supposer qu'il n'y a pas de référence nulle (même s'il émettra facilement du code qui génère des références nulles!). En tant que tel, il voit la
if()
condition, conclut que cela ne peut pas être vrai et jette simplement laif()
déclaration entière . Avec l'introduction des optimisations de temps de liaison, il est devenu tout à fait impossible de vérifier les références nulles de manière robuste.TL; DR:
Les références nulles sont en quelque sorte une existence horrible:
Leur existence semble impossible (= par le standard),
mais elles existent (= par le code machine généré),
mais vous ne pouvez pas les voir si elles existent (= vos tentatives seront optimisées),
mais elles peuvent vous tuer de toute façon inconscient (= votre programme plante à des moments bizarres, ou pire).
Votre seul espoir est qu'ils n'existent pas (= écrivez votre programme pour ne pas les créer).
J'espère que cela ne vous hantera pas!
la source
Si votre intention était de trouver un moyen de représenter null dans une énumération d'objets singleton, alors c'est une mauvaise idée de (dé) référencer null (it C ++ 11, nullptr).
Pourquoi ne pas déclarer un objet singleton statique qui représente NULL dans la classe comme suit et ajouter un opérateur cast-to-pointer qui retourne nullptr?
Edit: Correction de plusieurs erreurs de typage et ajout d'une instruction if dans main () pour tester l'opérateur cast-to-pointer fonctionnant réellement (ce que j'ai oublié de ... mon mauvais) - 10 mars 2015 -
la source
clang ++ 3.5 y met même en garde:
la source