L'accès aux membres de la classe sur un pointeur NULL
Je faisais des expériences avec C++ et a trouvé le code ci-dessous que de façon très étrange.
class Foo{
public:
virtual void say_virtual_hi(){
std::cout << "Virtual Hi";
}
void say_hi()
{
std::cout << "Hi";
}
};
int main(int argc, char** argv)
{
Foo* foo = 0;
foo->say_hi(); //works well
foo->say_virtual_hi(); //will crash the app
return 0;
}
Je sais que le virtuel appel de méthode se bloque car il nécessite une vtable de recherche et ne peut travailler qu'avec des objets valides.
J'ai les questions suivantes
- Comment un non méthode virtuelle
say_hi
travailler sur un pointeur NULL? - Où l'objet
foo
sont alloués?
Toutes les pensées?
- Voir this pour ce que le dit la langue à ce sujet. Les deux sont un comportement indéfini.
Vous devez vous connecter pour publier un commentaire.
L'objet
foo
est une variable locale de typeFoo*
. Cette variable susceptible obtient allouée sur la pile pour lamain
fonction, tout comme toute autre variable locale. Mais le valeur stockées dansfoo
est un pointeur null. Il n'est pas n'importe où. Il n'existe aucune instance de typeFoo
représenté partout.À l'appel d'une fonction virtuelle, l'appelant a besoin de savoir quel est l'objet qui est appelée la fonction sur. C'est parce que l'objet lui-même est ce qui indique la fonction qui devrait vraiment être appelé. (Qui est souvent mis en œuvre en donnant l'objet d'un pointeur vers une vtable, une liste de fonctions pointeurs, et l'appelant sait juste qu'il est censé appeler la fonction première sur la liste, sans savoir à l'avance où le pointeur de points.)
Mais pour appeler une fonction non virtuelle, l'appelant n'a pas besoin de savoir tout cela. Le compilateur sait exactement quelle fonction sera appelée, de sorte qu'il peut générer un
CALL
machine d'instruction en code pour accéder directement à la fonction désirée. Il passe simplement un pointeur vers l'objet, la fonction a été appelée sur un paramètre caché à la fonction. En d'autres termes, le compilateur traduit votre appel de fonction dans cette:Maintenant, depuis la mise en œuvre de cette fonction ne fait jamais référence à tous les membres de l'objet pointé par ses
this
argument, vous esquiver les balles de déréférencement d'un pointeur null parce que vous n'avez jamais déréférencer un.Officiellement, l'appel de tout fonction — même un non-virtuel — sur un pointeur null est un comportement indéfini. Un de ces résultats d'un comportement indéfini, c'est que votre code semble fonctionner exactement comme vous le souhaitez. Vous ne doit pas reposer sur que, même s'il vous arrive de trouver des bibliothèques à partir de votre fournisseur de compilateur que ne compter sur cela. Mais le compilateur vendeur a l'avantage d'être en mesure d'ajouter une nouvelle définition de ce qui serait autrement un comportement indéfini. Ne pas le faire vous-même.
[C++11: 9.3.1/2]
: "Si un non-fonction membre statique d'une classeX
est appelée pour un objet qui n'est pas de typeX
, ou d'un type dérivé deX
, le comportement est indéfini." Clairement*foo
n'est pas de typeFoo
(comme il n'existe pas).[C++11: 5.2.5/2]
: "L'expressionE1->E2
est convertie en une forme équivalente(*(E1)).E2
" et puis l'évidence UB de déréférencementE1
quand ce n'est pas un pointeur valide (inc.[C++11: 3.8/2]
).La
say_hi()
fonction membre est généralement mis en œuvre par le compilateur commePuisque vous n'avez pas accès à tout les membres, votre appel réussit (même si vous entrez un comportement indéterminé selon la norme).
Foo
n'obtient pas affecté du tout.Déréférencement d'un pointeur NULL causes "comportement indéfini", Cela signifie que quelque chose pourrait se produire - votre code peut sembler fonctionner correctement. Vous ne devez pas compter sur cela, cependant - si vous exécutez le même code sur une autre plate-forme (ou peut-être même sur la même plate-forme), il sera probablement planter.
Dans votre code, il n'y a pas d'objet Foo, seulement un pointeur qui est initalised avec la valeur NULL.
C'est un comportement indéterminé. Mais la plupart des compilateurs fait des instructions gérer correctement cette situation si vous n'avez pas accès aux variables membres et la table virtuelle.
voyons démontage dans visual studio pour comprendre ce qui se passe
comme vous pouvez le voir Foo:say_hi appelé comme d'habitude fonction, mais avec ce dans le registre ecx. Pour simplifier, on peut supposer que ce passé en tant que paramètre implicite que nous n'avons jamais utiliser dans votre exemple.
Mais dans le second cas, nous faisons le calcul de l'adresse de la fonction en raison virtuel tableau d'échéance de foo adresse et obtient de base.
a) Il travaille parce qu'il n'a pas de déréférencement de quoi que ce soit dans l'implicite pointeur "this". Dès que vous faites cela, boom. Je ne suis pas sûr à 100%, mais je pense pointeur null déréférence sont fait par RW de protéger les premiers 1K de mémoire de l'espace, donc il y a une petite chance de nullreferencing de ne pas se faire prendre si vous ne déréférencement il passé 1K ligne (ie. certains variable d'instance qui serait alloué très loin, comme:
alors (a->je voudrais éventuellement être interceptée lorsque A est null.
b) Nulle part, vous n'déclaré un pointeur, ce qui est alloué sur le main():s de la pile.
L'appel à say_hi est statiquement lié. Donc, l'ordinateur fait tout simplement un appel à une fonction. La fonction n'utilise pas tous les champs, donc il n'y a pas de problème.
L'appel à virtual_say_hi est dynamiquement liée, de sorte que le processeur passe à la table virtuelle, et depuis il n'y a pas de table virtuelle là, il saute quelque part aléatoire et bloque le programme.
Il est important de réaliser que les deux appels produire un comportement indéfini, et ce comportement peut se manifester de manière inattendue. Même si l'appel apparaît de travail, il peut être prévoyant un champ de mines.
Considérer ce petit changement à votre exemple:
Depuis le premier appel à
foo
permet à un comportement indéfini sifoo
est null, le compilateur est maintenant libre de supposer quefoo
est pas null. Que fait leif (foo != 0)
redondant, et le compilateur peut optimiser tout ça! Vous pourriez penser que c'est une très absurde d'optimisation, mais le compilateur écrivains ont été très agressif, et quelque chose comme ce qui s'est passé dans le code réel.À l'origine, les jours de C++, le code C++ a été converti C. méthodes de l'Objet sont convertis à la non-méthodes de l'objet comme ceci (dans votre cas):
De cours, le nom foo_say_hi est simplifiée. Pour plus de détails, consulter C++ name mangling.
Comme vous pouvez le voir, si la thisPtr n'est jamais déréférencé, puis le code est bon et réussit. Dans votre cas, pas de variables d'instance ou tout ce qui dépend de la thisPtr a été utilisé.
Cependant, les fonctions virtuelles sont différents. Il y a beaucoup de recherches dans l'objet pour assurer que le bon pointeur d'objet est transmis comme paramètre à la fonction. Cela permettra de déréférencer le thisPtr et de provoquer l'exception.