Je remarque parfois des programmes qui plantent sur mon ordinateur avec l'erreur: "appel de fonction virtuelle pure".
Comment ces programmes se compilent-ils même lorsqu'un objet ne peut pas être créé à partir d'une classe abstraite?
c++
polymorphism
virtual-functions
pure-virtual
Brian R. Bondy
la source
la source
doIt()
appel dans le constructeur est facilement dévirtualisé et distribué deBase::doIt()
manière statique, ce qui provoque simplement une erreur de l'éditeur de liens. Ce dont nous avons vraiment besoin, c'est d'une situation dans laquelle le type dynamique lors d'une distribution dynamique est le type de base abstrait.Base::Base
appelé un non-virtuelf()
qui à son tour appelle ladoIt
méthode virtuelle (pure) .En plus du cas standard de l'appel d'une fonction virtuelle depuis le constructeur ou le destructeur d'un objet avec des fonctions virtuelles pures, vous pouvez également obtenir un appel de fonction virtuelle pure (au moins sur MSVC) si vous appelez une fonction virtuelle après que l'objet a été détruit . De toute évidence, c'est une très mauvaise chose à essayer, mais si vous travaillez avec des classes abstraites comme interfaces et que vous vous trompez, c'est quelque chose que vous pourriez voir. C'est peut-être plus probable si vous utilisez des interfaces comptées référencées et que vous avez un bogue de comptage de références ou si vous avez une condition de concurrence d'utilisation / destruction d'objet dans un programme multithread ... Le problème avec ces types d'appels purs est que c'est il est souvent moins facile de comprendre ce qui se passe car une vérification des «suspects habituels» des appels virtuels dans ctor et dtor sera claire.
Pour vous aider à déboguer ces types de problèmes, vous pouvez, dans différentes versions de MSVC, remplacer le gestionnaire d'appels purecall de la bibliothèque d'exécution. Vous faites cela en fournissant votre propre fonction avec cette signature:
et le lier avant de lier la bibliothèque d'exécution. Cela VOUS donne le contrôle de ce qui se passe lorsqu'un appel purecall est détecté. Une fois que vous avez le contrôle, vous pouvez faire quelque chose de plus utile que le gestionnaire standard. J'ai un gestionnaire qui peut fournir une trace de pile de l'endroit où le purecall s'est produit; voir ici: http://www.lenholgate.com/blog/2006/01/purecall.html pour plus de détails.
(Notez que vous pouvez également appeler _set_purecall_handler () pour installer votre gestionnaire dans certaines versions de MSVC).
la source
_purecall()
invocation qui se produit normalement lors de l'appel d'une méthode d'une instance supprimée ne se produira pas si la classe de base a été déclarée avec l'__declspec(novtable)
optimisation (spécifique à Microsoft). Avec cela, il est tout à fait possible d'appeler une méthode virtuelle remplacée après la suppression de l'objet, ce qui pourrait masquer le problème jusqu'à ce qu'il vous mord sous une autre forme. Le_purecall()
piège est votre ami!Habituellement, lorsque vous appelez une fonction virtuelle via un pointeur suspendu, l'instance a probablement déjà été détruite.
Il peut y avoir aussi des raisons plus «créatives»: peut-être avez-vous réussi à découper la partie de votre objet où la fonction virtuelle a été implémentée. Mais généralement, c'est simplement que l'instance a déjà été détruite.
la source
Je suis tombé sur le scénario selon lequel les fonctions virtuelles pures sont appelées à cause d'objets détruits,
Len Holgate
j'ai déjà une très bonne réponse , je voudrais ajouter de la couleur avec un exemple:Le destructeur de classe dérivée réinitialise les points vptr vers la classe de base vtable, qui a la fonction virtuelle pure, donc quand nous appelons la fonction virtuelle, il appelle en fait les fonctions virutales pures.
Cela peut se produire en raison d'un bogue de code évident ou d'un scénario compliqué de condition de concurrence dans les environnements multi-threading.
Voici un exemple simple (compile g ++ avec l'optimisation désactivée - un programme simple pourrait être facilement optimisé):
Et la trace de la pile ressemble à ceci:
Surligner:
si l'objet est complètement supprimé, ce qui signifie que le destructeur est appelé et que memroy est récupéré, nous pouvons simplement obtenir un
Segmentation fault
comme la mémoire est revenue au système d'exploitation, et le programme ne peut tout simplement pas y accéder. Donc, ce scénario "pur appel de fonction virtuelle" se produit généralement lorsque l'objet est alloué sur le pool de mémoire, alors qu'un objet est supprimé, la mémoire sous-jacente n'est en fait pas récupérée par le système d'exploitation, elle est toujours là accessible par le processus.la source
Je suppose qu'il y a un vtbl créé pour la classe abstraite pour une raison interne (il peut être nécessaire pour une sorte d'informations de type d'exécution) et que quelque chose ne va pas et qu'un objet réel l'obtient. C'est un bug. Cela seul devrait dire que quelque chose ne peut pas arriver.
Spéculation pure
edit: on dirait que je me trompe dans le cas en question. OTOH IIRC certains langages autorisent les appels vtbl hors du constructeur destructeur.
la source
J'utilise VS2010 et chaque fois que j'essaye d'appeler destructor directement à partir de la méthode publique, j'obtiens une erreur "appel de fonction virtuelle pure" pendant l'exécution.
J'ai donc déplacé ce qu'il y a à l'intérieur de ~ Foo () pour séparer la méthode privée, puis cela a fonctionné comme un charme.
la source
Si vous utilisez Borland / CodeGear / Embarcadero / Idera C ++ Builder, vous pouvez simplement implémenter
Pendant le débogage, placez un point d'arrêt dans le code et consultez la pile d'appels dans l'EDI, sinon enregistrez la pile d'appels dans votre gestionnaire d'exceptions (ou cette fonction) si vous disposez des outils appropriés pour cela. J'utilise personnellement MadExcept pour cela.
PS. L'appel de fonction d'origine se trouve dans [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp
la source
Voici une façon sournoise pour que cela se produise. Cela m'est arrivé essentiellement aujourd'hui.
la source
I had this essentially happen to me today
évidemment pas vrai, parce que tout simplement faux: une fonction virtuelle pure n'est appelée que lorsqu'ellecallFoo()
est appelée dans un constructeur (ou destructeur), car à ce moment l'objet est encore (ou déjà) au stade A. Voici une version en cours d'exécution de votre code sans l'erreur de syntaxeB b();
- les parenthèses en font une déclaration de fonction, vous voulez un objet.