113 votes

D'où viennent les crashs de "pur appel de fonction virtuelle" ?

Je remarque parfois des programmes qui plantent sur mon ordinateur avec l'erreur : "appel de fonction virtuelle pure".

Comment ces programmes peuvent-ils même compiler lorsqu'il est impossible de créer un objet d'une classe abstraite ?

109voto

Adam Rosenfield Points 176408

Ils peuvent se produire si vous essayez de faire un appel de fonction virtuelle à partir d'un constructeur ou d'un destructeur. Puisque vous ne pouvez pas faire un appel de fonction virtuelle à partir d'un constructeur ou d'un destructeur (l'objet de la classe dérivée n'a pas été construit ou a déjà été détruit), il appelle la version de la classe de base, qui dans le cas d'une fonction virtuelle pure, n'existe pas.

(Voir la démo en direct aquí )

class Base
{
public:
    Base() { doIt(); }  // DON'T DO THIS
    virtual void doIt() = 0;
};

void Base::doIt()
{
    std::cout<<"Is it fine to call pure virtual function from constructor?";
}

class Derived : public Base
{
    void doIt() {}
};

int main(void)
{
    Derived d;  // This will cause "pure virtual function call" error
}

6 votes

Y a-t-il une raison pour laquelle le compilateur ne pourrait pas le faire, en général ?

0 votes

Je ne vois pas de raison technique pour laquelle le compilateur ne pourrait pas le faire.

2 votes

GCC me donne un avertissement seulement : test.cpp : In constructor 'Base::Base()' : test.cpp:4 : warning : abstract virtual 'virtual void Base::doIt()' called from constructor Mais il échoue au moment de la liaison.

66voto

Len Holgate Points 12579

En plus du cas standard d'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 (sur MSVC au moins) si vous appelez une fonction virtuelle après que l'objet ait été détruit. Évidemment, c'est une très mauvaise chose à essayer de faire, mais si vous travaillez avec des classes abstraites comme des interfaces et que vous vous trompez, c'est quelque chose que vous pourriez voir. C'est probablement plus probable si vous utilisez des interfaces référencées et comptées et que vous avez un bug de comptage des références ou si vous avez une condition de course entre l'utilisation et la destruction d'un objet dans un programme multithread... Le problème avec ces types d'appels purs est qu'il est souvent moins facile de comprendre ce qui se passe car une vérification des "suspects habituels" d'appels virtuels dans le ctor et le dtor ne donnera rien.

Pour faciliter le débogage de ce type de problèmes, vous pouvez, dans diverses versions de MSVC, remplacer le gestionnaire d'appel pur de la bibliothèque d'exécution. Pour ce faire, vous devez fournir votre propre fonction avec cette signature :

int __cdecl _purecall(void)

et de le lier avant de lier la bibliothèque d'exécution. Cela VOUS permet de contrôler ce qui se passe lorsqu'un appel pur 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 handler qui peut fournir une trace de la pile 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).

1 votes

Merci pour l'indication concernant l'obtention d'une invocation _purecall() sur une instance supprimée ; je n'étais pas au courant de cela, mais je viens de le prouver à moi-même avec un petit code de test. En regardant un dump post-mortem dans WinDbg, j'ai pensé que j'avais affaire à une course où un autre thread essayait d'utiliser un objet dérivé avant qu'il n'ait été entièrement construit, mais cela éclaire d'un jour nouveau le problème, et semble mieux correspondre aux preuves.

1 votes

Une autre chose que j'ajouterai : le _purecall() l'invocation qui se produit normalement lors de l'appel d'une méthode d'une instance supprimée sera no se produit si la classe de base a été déclarée avec l'attribut __declspec(novtable) optimisation (spécifique à Microsoft). Avec cela, il est tout à fait possible d'appeler une méthode virtuelle surchargée après que l'objet ait été supprimé, ce qui pourrait masquer le problème jusqu'à ce qu'il vous morde sous une autre forme. Le site _purecall() le piège est votre ami !

0 votes

C'est utile de le savoir Dave, j'ai vu quelques situations récemment où je ne recevais pas de purecalls alors que je pensais que je devais en recevoir. Peut-être que je ne respectais pas cette optimisation.

9voto

Jeff Hillman Points 3333

Voici une excellente explication des causes possibles de ce phénomène :

http://www.artima.com/cppsource/pure_virtual.html

7voto

Braden Points 785

En général, lorsque vous appelez une fonction virtuelle par le biais d'un pointeur suspendu, il est fort probable que l'instance ait déjà été détruite.

Il peut aussi y avoir des raisons plus "créatives" : peut-être avez-vous réussi à couper la partie de votre objet où la fonction virtuelle était implémentée. Mais généralement, c'est simplement que l'instance a déjà été détruite.

0voto

BCS Points 18500

Je suppose qu'une vtbl a été créée pour la classe abstraite pour une raison interne (elle pourrait être nécessaire pour une sorte d'information sur le type d'exécution) et quelque chose se passe mal et un objet réel l'obtient. C'est un bogue. Ce seul fait devrait indiquer que quelque chose qui ne peut pas se produire l'est.

Pure spéculation

éditer : Il semble que j'aie tort dans le cas en question. Par contre, certains langages autorisent les appels de vtbl en dehors du constructeur et du destructeur.

0 votes

Il ne s'agit pas d'un bogue dans le compilateur, si c'est ce que vous voulez dire.

0 votes

Vous vous doutez bien que C# et Java le permettent. Dans ces langages, les objets en construction ont leur type final. En C++, les objets changent de type pendant la construction et c'est pourquoi et quand vous pouvez avoir des objets avec un type abstrait.

0 votes

TOUTES Les classes abstraites, et les objets réels créés dérivés d'elles, ont besoin d'une vtbl (virtual function table), listant les fonctions virtuelles qui doivent être appelées sur elle. En C++, un objet est responsable de la création de ses propres membres, y compris la table des fonctions virtuelles. Les constructeurs sont appelés de la classe de base à la classe dérivée, et les destructeurs sont appelés de la classe dérivée à la classe de base, donc dans une classe de base abstraite, la table de fonctions virtuelles n'est pas encore disponible.

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