129 votes

Dans quels cas l'appel d'une fonction membre sur une instance nulle entraîne-t-il un comportement indéfini?

Considérons le code suivant:

#include <iostream>

struct foo
{
    void bar() { std::cout << "gman was here" << std::endl; }
    void baz() { x = 5; }

    int x;
};

int main()
{
    foo* f = 0;

    f->bar(); // (a)
    f->baz(); // (b)
}

Nous nous attendons (b) de crash, car il n'y a pas de membre correspondant de la x pour le pointeur null. Dans la pratique, (a) ne tombe pas en panne parce que l' this pointeur n'est jamais utilisé.

Parce qu' (b) dereferences this pointeur ((*this).x = 5;), et this est null, le programme entre dans un comportement indéfini, comme référence à la valeur null est toujours dit d'être un comportement indéfini.

N' (a) suite à un comportement indéfini? Si les deux fonctions ( x) sont statiques?

122voto

GManNickG Points 155079

Les deux (a) et (b) suite à un comportement indéfini. C'est toujours un comportement indéfini d'appeler une fonction membre par l'intermédiaire d'un pointeur null. Si la fonction est statique, c'est techniquement pas défini en tant que bien, mais il y a du litige.


La première chose est de comprendre pourquoi c'est un comportement indéfini déréférencer un pointeur null. En C++03, il y a effectivement un peu d'ambiguïté ici.

Bien que le "déréférencement d'un pointeur null résultats dans un comportement indéfini" est mentionné dans les notes dans les deux §1.9/4 et §8.3.2/4, il n'est jamais explicitement mentionnée. (Les Notes sont non-normatif.)

Cependant, on peut essayer de déduire à partir de §3.10/2:

Une lvalue se réfère à un objet ou une fonction.

Lors de la déférence, le résultat est une lvalue. Un pointeur null ne fait pas référence à un objet, par conséquent, lorsque nous utilisons la lvalue nous avoir un comportement indéfini. Le problème est que la phrase précédente, il n'est jamais dit, alors, que signifie "utiliser" la lvalue? Tout de même générer, à tous, ou à utiliser dans le cadre plus formel sentiment d'effectuer des lvalue-à-rvalue de conversion?

De même, il ne peut absolument pas être convertie en une rvalue (§4.1/1):

Si l'objet pour lequel la lvalue se réfère n'est pas un objet de type T et n'est pas un objet d'un type dérivé de T, ou si l'objet est non initialisée, un programme qui nécessite cette conversion a un comportement indéfini.

Ici, il est certainement un comportement indéfini.

L'ambiguïté vient de si oui ou non c'est un comportement indéfini de déférence , mais pas utiliser la valeur d'un pointeur non valide (c'est, obtenir une lvalue, mais pas de le convertir à une rvalue). Si non, alors int *i = 0; *i; &(*i); est bien définie. C'est un actif en question.

Nous avons donc une stricte "déréférencer un pointeur null, obtenir un comportement indéfini" de la vue et de la faiblesse de l'utilisation d'un déréférencé pointeur null, obtenir un comportement indéfini".

Maintenant nous pencher sur la question.


Oui, (a) résultats dans un comportement indéfini. En fait, si l' this est nul, quel que soit le contenu de la fonction , le résultat est indéfini.

Cela résulte du §5.2.5/3:

Si E1 a le type "pointeur vers la classe X", alors l'expression E1->E2 est convertie en une forme équivalente (*(E1)).E2;

*(E1) entraînera un comportement indéfini avec une interprétation stricte, et .E2 le convertit en une rvalue, faisant d'elle un comportement indéfini de la faiblesse de l'interprétation.

Il en résulte également que c'est un comportement indéfini directement à partir de (§9.3.1/1):

Si une fonction membre non statique d'une classe de X est appelée pour un objet qui n'est pas de type X, ou d'un type dérivé de X, le comportement est indéfini.


Avec des fonctions statiques, la stricte contre la faiblesse de l'interprétation qui fait la différence. Strictement parlant, il n'est pas défini:

Un membre statique peut être appelée à l'aide de la classe membre de la syntaxe d'accès, auquel cas l'objet-l'expression est évaluée.

C'est, il est évalué comme si c'étaient des non-statique, et nous avons encore une fois de déréférencer un pointeur null avec (*(E1)).E2.

Cependant, parce qu' E1 n'est pas utilisé dans un membre statique-appel de la fonction, si nous utilisons la faiblesse de l'interprétation que l'appel est bien définie. *(E1) résultats dans une lvalue, la fonction statique est résolu, *(E1) est éliminé, et la fonction est appelée. Il n'y a pas de lvalue-à-rvalue de conversion, donc il n'y a pas de comportement indéfini.

Dans C++0x, comme de n3126, l'ambiguïté demeure. Pour l'instant, d'être en sécurité: utilisation de l'interprétation stricte.

35voto

Mark Ransom Points 132545

Évidemment indéfini signifie qu'il est pas défini, mais parfois, il peut être prévisible. Les informations que je suis sur le point de fournir ne doivent jamais être invoqué pour code de travail, car il n'est pas garanti, mais il pourrait s'avérer utile lors du débogage.

Vous pourriez penser que l'appel d'une fonction sur un objet pointeur de déréférencer le pointeur et causer de l'UB. Dans la pratique, si la fonction n'est pas virtuel, le compilateur ne l'avez converti à un simple appel de fonction en passant le pointeur en tant que premier paramètre ce, en contournant le déréférencement et la création d'une bombe à retardement pour les appelés de la fonction membre. Si la fonction de membre ne faisant pas référence à un membre quelconque de variables ou de fonctions virtuelles, il ne pourrait réussir sans erreur. Rappelez-vous que pour réussir relève de l'univers de "undefined"!

Microsoft MFC fonction GetSafeHwnd réellement s'appuie sur ce problème. Je ne sais pas ce qu'ils ont de fumer.

Si vous êtes à l'appel d'une fonction virtuelle, le pointeur doit être déréférencé pour obtenir à la vtable, et pour sûr que vous allez obtenir UB (probablement un crash, mais rappelez-vous qu'il n'y a pas de garanties).

-1voto

antitrust Points 4853

Vous devez utiliser des pointeurs et non des références si vous souhaitez réaffecter. Les références sont initialisées une fois pour toutes et ne peuvent pas être réaffectées. Les pointeurs peuvent être créés sans être initialisés, mais ils peuvent également être réaffectés.

8.3.2 / 1:

 A reference shall be initialized to refer to a valid object or function. 
[Note: in particular, a null reference 
cannot exist in a well-defined program, because the only way to create such 
a reference would be to bind it to the    
"object" obtained by dereferencing a null pointer, which causes undefined 
behavior. As described in 9.6, a reference cannot be bound directly to a bit-field]
 

1.9 / 4:

 Certain other operations are described in this International Standard as undefined 
(for example, the effect of dereferencing the null pointer)
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X