IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

trouver le rang dans la VMT d'une méthode virtuelle


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre très actif

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2004
    Messages
    499
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Par défaut trouver le rang dans la VMT d'une méthode virtuelle
    Bonjour,

    je cherchais un code pour obtenir dynamiquement le rang dans la VMT d'une méthode virtuelle, voila la solution que j'ai trouvée qui devrait marcher avec tous les compilateurs/processeurs :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
     
    // CPPVmtRank.cpp : dynamically obtain VMTRank from a pointer to a virtual method
     
    #define __Meth(N) virtual int DummyMethod##N() { return 0x##N; }
    #define __MethX(N) __Meth(N##0) __Meth(N##1) __Meth(N##2) __Meth(N##3) \
    				   __Meth(N##4) __Meth(N##5) __Meth(N##6) __Meth(N##7) \
    				   __Meth(N##8) __Meth(N##9) __Meth(N##A) __Meth(N##B) \
    				   __Meth(N##C) __Meth(N##D) __Meth(N##E) __Meth(N##F)
     
    #define __MethXX(N) __MethX(N##0) __MethX(N##1) __MethX(N##2) __MethX(N##3) \
    				    __MethX(N##4) __MethX(N##5) __MethX(N##6) __MethX(N##7) \
    				    __MethX(N##8) __MethX(N##9) __MethX(N##A) __MethX(N##B) \
    				    __MethX(N##C) __MethX(N##D) __MethX(N##E) __MethX(N##F)
     
    class DummyClass {
    public:
    	__MethXX(0) __MethXX(1) __MethXX(2) __MethXX(3)
    	__MethXX(4) __MethXX(5) __MethXX(6) __MethXX(7)
    	__MethXX(8) __MethXX(9) __MethXX(a) __MethXX(b)
    	__MethXX(c) __MethXX(d) __MethXX(e) __MethXX(f)
            ///4096 DummyMethods
    };
     
    DummyClass dummyObj;
     
    class A {
    public:
    	virtual void Meth0();
    	virtual void Meth1();
    	virtual void Meth2();
    	virtual void Meth3();
    	virtual void Meth4();
    	virtual void Meth5();
    	virtual void Meth6();
    };
     
    template <typename T> int GetVMTRank(T t) {
    	int (DummyClass::*methPtr)();
    	*(void**)&methPtr = *(void**)&t;
    	return (dummyObj.*methPtr)();
    }
     
    int main() {
    	int Meth0_VMTRank = GetVMTRank(&A::Meth0);
    	int Meth6_VMTRank = GetVMTRank(&A::Meth6);
    }
    je pense que le seul truc qui peut faire crasher c'est si le compilateur s'amuse à ne pas mettre dans la VMT les méthodes dans l'ordre de leur déclaration,
    dans ce cas il est possible de créer une DummyClass par DummyMethod (DummyClass9 héritant de DummyClass8) pour obliger le compilateur à mettre les DummyMethod dans l'ordre.

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 146
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 146
    Billets dans le blog
    4
    Par défaut
    Bonjour,

    une seule question : pourquoi ?
    Un &mymethod - this serait pas une info suffisante ? Si tenté qu'elle soit d'une quelconque utilité.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Membre très actif

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2004
    Messages
    499
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Par défaut
    salut,

    c'est gentil de t'intéresser à mon message

    C'est peu intuitif mais en fait il existe deux types de pointeurs de méthode :
    - les pointeurs de méthode non virtuelle qui sont l'adresse du début de la méthode
    - les pointeurs de méthode virtuelle qui sont l'adresse d'un bout de code qui lit la VMT de l'objet (sur lequel on fait l'appel) et redirige vers la "vraie méthode"

    ainsi, si mymethodPtr est un pointeur de méthode virtuelle, alors
    n'enverra pas forcément vers le même code que (si obj2 surcharge la méthode virtuelle correspondante), c'est cette subtilité que j'exploite à fond dans mon code, où j'appelle mymethodPtr sur un objet qui n'implémente même pas la méthode vers laquelle pointe mymethodPtr..

    pour finir, avec des &maClasse::mymethod on n'a aucune chance d'obtenir l'adresse de la case de la VMT de la classe qui pointe vers mymethod. Cependant je suis d'accord que le compilateur n'a aucune excuse pour ne pas proposer de syntaxe simple le permettant.

  4. #4
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Citation Envoyé par acx01b Voir le message
    salut,

    c'est gentil de t'intéresser à mon message

    C'est peu intuitif mais en fait il existe deux types de pointeurs de méthode :
    - les pointeurs de méthode non virtuelle qui sont l'adresse du début de la méthode
    - les pointeurs de méthode virtuelle qui sont l'adresse d'un bout de code qui lit la VMT de l'objet (sur lequel on fait l'appel) et redirige vers la "vraie méthode"
    Si ce n'est que la table de fonctions virutelles se trouve bel et bien dans ta classe, mais qu'il n'y a que ca dans ta classes.

    Qu'elles soient virtuelles ou non, les fonctions membres sont gérées exactement comme des fonctions libres au niveau du binaire exécutable, à ceci près que leur premier argument est this (le pointeur sur l'objet au départ duquel la fonction est appelée) et du "mangling" (la décoration qui permet de retirer tout ambiguité quant à la fonction appelée).

    Les fonctions membres virtuelles passent effectivement par une indirection supplémentaire (la table de fonctions virutelles qui contient l'adresse de la fonction à exécuter) mais il n'y a que les fonctions virutelles qui le fassent

    ainsi, si mymethodPtr est un pointeur de méthode virtuelle, alors
    n'enverra pas forcément vers le même code que (si obj2 surcharge la méthode virtuelle correspondante), c'est cette subtilité que j'exploite à fond dans mon code, où j'appelle mymethodPtr sur un objet qui ne n'implémente même pas !
    Que veux tu dire par "n'enverra pas forcément le même code

    Au niveau du compilateur, une fonction est exécutée ou non et renvoie une valeur ou non. Il n'y a rien d'autre que cela

    Au niveau des pointeurs de fonctions, une fonction virtuelle est déclarée dans la classe de base ou non.

    A partir de là, tu peux (ou non) l'utiliser comme un pointeur de fonction membre de l'objet de base, ou non. Point barre

    Il n'y a que les fonctions qui ne sont pas définies dans la classe de base qui ne pourront pas être utilisées comme un pointeur de fonction membre l'objet de base mais qui devront être utilisées comme des pointeur de fonctions membres du type dérivé
    pour finir, avec des &maClasse::mymethod on n'a aucune chance d'obtenir l'adresse de la case de la VMT de la classe qui pointe vers mymethod. Cependant je suis d'accord que le compilateur n'a aucune excuse pour ne pas proposer de syntaxe simple le permettant.
    Mais, comme Bousk, j'ai envie de dire "pourquoi"

    Tu n'as jamais besoin d'accéder directement à la table des fonctions virtuelles.

    Ce n'est qu'un système de "popote interne" qui permet de mettre en place un besoin particulier qui est l'adaptation potentielle d'un comportement en fonction du type réel de l'objet au départ duquel on appelle le dit comportement.

    Tu peux savoir que l'indirection supplémentaire fera qu'il faut "un peu plus de temps" (on le compte en cycles d'horloges, là ) pour appeler la fonction, mais ca s'arrête là

    Essayer de savoir quel est le rang de ta fonction virtuelle dans la table revient à essayer de voir dans le cylindre d'un moteur ce qui se passe lorsque l’étincelle vient enflammer l'essence: il vaut mieux le faire par simulation dont on peut ralentir l'exécution
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Membre très actif

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2004
    Messages
    499
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Citation Envoyé par acx01b Voir le message
    ainsi, si mymethodPtr est un pointeur de méthode virtuelle, alors
    n'enverra pas forcément vers le même code que (si obj2 surcharge la méthode virtuelle correspondante)
    Que veux tu dire par "n'enverra pas forcément le même code
    je veux dire que si mymethodPtr pointe vers la première méthode virtuelle d'une classe, alors mymethodPtr sera l'adresse du code assembleur suivant (en win32) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    B::`vcall'{0}':
    00B5D5E0  mov         eax,dword ptr [ecx]  
    00B5D5E2  jmp         dword ptr [eax]
    ainsi, suivant l'objet lequel on appelle le pointeur de méthode, la vrai méthode exécutée ne sera pas forcément la même !

    la sémantique d'un pointeur de méthode virtuelle ou non n'est pas donc pas exactement la même : dans un cas c'est "appeler la méthode à l'adresse X sur l'objet Y", et dans l'autre c'est "appeler la méthode au rang N de la VMT de l'objet Y sur l'objet Y"

    Pour finir, voici un screenshot qui montre que regarder la VMT de ses objets est loin d'être idiot quand on cherche à résoudre un bug dû à un objet invalide :



    En regardant la VMT de l'objet passé en paramètre de "uneFonction" on voit tout de suite que c'est un A et non un B.

  6. #6
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    A moins que tu ne t'y prennes très mal

    N'aurais tu, par hasard, pas oublié que qui dit fonction virtuel dit héritage public et que qui dit héritage public dit sémantique d'entité, donc, objet non copiables et non affectables par nature

    Si tu veux appeler une fonction membre par callback, et que cette fonction n'est pas statique, il te faut forcément un objet au départ duquel invoquer la fonction en question.

    Si tu transmets l'objet de manière "classique" par copie, tu ne peux pas profiter du polymorphisme car le paramètre transmis lorsque tu passes un objet du type dérivé correspond au "sous ensemble" représenté par le type de base, et il est donc logique que tu observes le comportement propre au type de base

    Mais, si tu prends la précaution de rendre tes classes de base non copiables et non affectables (*), le compilateur te préviendra tout de suite que tu fais une erreur, t'obligeant à transmettre ton argument sous la forme d'un pointeur ou d'une référence.

    Et, justement, si tu transmets ton paramètre sous la forme d'un pointeur ou d'une référence, tu peux profiter du polymorphisme, et tu n'as donc pas besoin d'accéder à la table de fonctions virtuelles

    (*) Pour rendre les classes non copiables et non affectables, il y a trois solutions, par préférence:

    - La solution C++11 qui consiste à déclarer l'opérateur d'affectation et le constructeur de copie comme delete:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    class MyBase{
        public:
            MyBase(MyBase const &) = delete;
            MyBase& operator=(MyBase const &) = delete;
    };
    - La solution boost::noncopyable qui consiste à faire hériter de manière privée ta classe de base de cette classe fournie par boost
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class MyBase : private boost::noncopyable{
        public:
    };
    - La solution utilisée depuis la nuit des temps qui consiste à déclarer sans les définir les opérateur d'affectation et constructeur par copie de la classe de base dans l'accessibilité privée:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class MyBase{
        public:
            /* ... */
        private:
            /*!!! NE SUROUT PAS FOURNIR D'IMPLEMENTATION !!! */
            MyBase(MyBase const &) ;
            MyBase& operator=(MyBase const &) ;
    };
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Citation Envoyé par acx01b Voir le message
    j
    En regardant la VMT de l'objet passé en paramètre de "uneFonction" on voit tout de suite que c'est un A et non un B.
    Parce que tu le caste explicitement en B!!!

    La règle suivie par C et par C++ est la même ici :
    trust the developer, even if it is bullshit
    "

    Tu dis explicitement que ton pointeur sur A doit être considéré comme un pointeur sur B.

    Or, A et B sont deux classes totalement différentes entre lesquelles il n'y a strictement aucune relation (et surtout pas de relation d'héritage)

    Comment veux tu que le compilateur sache que tu n'as pas une excellente raison de vouloir faire passer ton A pour un B

    Nous ne sommes pas en java, ici, il n'y a pas d'héritage caché avec une quelconque classe Object (ou quel que soit son nom réel) : nous sommes en C++ ou la règle est "vous ne payez que pour ce que vous utilisez"

    Si tu veux pouvoir faire passer ton objet de type A (ou, dans le cas présent, un pointeur vers ton objet de type A) comme étant (un pointeur vers) un objet de type B, il faut que A hérite publiquement de B, et, à ce moment là, le transtypage devient inutile tout simplement parce que ton A... EST un B .

    Mais, encore faut-il que cela ait du sens au niveau du LSP (Liskov Substitution Principle, ou, si tu préfères, le principe de substitution de Liskov)... Mais c'est un autre problème

    Si cela n'a pas de sens de faire hériter A de B, tu dois te tourner vers une alternative :

    Soit tu surcharge simplement la fonction pour qu'une des version prenne un A* et que l'autre prenne un B*, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    void uneFonction(A* a){
     
    }
    void uneFonction(B* b){
     
    }
    soit, si tu es certain que tes deux classes exposent des fonctions publiques identiques, tu utilise une fonction template sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template <typename T>
    void uneFonction(T * t){
       /* !!! Tout type transmis à cette fonction DOIT exposer une fonction
        * membre publique Meth !!!
        */
       t->Meth();
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  8. #8
    Membre très actif

    Profil pro
    Étudiant
    Inscrit en
    Décembre 2004
    Messages
    499
    Détails du profil
    Informations personnelles :
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Par défaut
    Citation Envoyé par acx01b Voir le message
    je pense que le seul truc qui peut faire crasher c'est si le compilateur s'amuse à ne pas mettre dans la VMT les méthodes dans l'ordre de leur déclaration,
    dans ce cas il est possible de créer une DummyClass par DummyMethod (DummyClass9 héritant de DummyClass8) pour obliger le compilateur à mettre les DummyMethod dans l'ordre.
    Je suis un peu vexé, car j'ai découvert que si celui qui a déjà regardé du code assembleur sait que sur les processeurs "dits normaux" il existe au moins 3 principaux callstyle pour les méthodes : __thiscall, __stdcall, __clrcall (et d'autres peu utilisés),
    qu'il sait également que ces trois principaux callstyle ont le bon goût de placer l'argument 'this' dans ecx (resp. rcx sur x64), qu'il en déduit que pour les méthodes à 0 arguments, ces callstyle sont similaires,

    il y a une petite subtilité que le premier clampin venu (dont je fais partie) n'a pas remarquée : le protocole d'appel des pointeurs de méthode !
    Hè non, vous l'aurez compris, on ne passe pas toujours 'this' dans ecx quand on appelle un pointeur de méthode.. Parfois on le passe dans la pile ! Satané C++ et ses milliards de subtilités...
    Donc mon code ne marche que si la méthode virtuelle dont on souhaite connaître le VMTrank a le même callstyle que les méthodes de DummyClass.
    Si on veut être généraliste, on doit faire une DummyClass par callstyle, et récupérer le callstyle avec un template, de préférence C++11 :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    enum TheCallStyles {
       CallStyle_thiscall, CallStyle_stdcall, CallStyle_clrcall
    };
    template <class U, typename T, typename... Args> TheCallStyles GetCallStyle(T (__thiscall U::*methPtr)(Args)) { return CallStyle_thiscall; }
     
    template <class U, typename T, typename... Args> TheCallStyles GetCallStyle(T (__stdcall U::*methPtr)(Args)) { return CallStyle_stdcall; }
     
    template <class U, typename T, typename... Args> TheCallStyles GetCallStyle(T (__clrcall U::*methPtr)(Args)) { return CallStyle_clrcall; }
    (non testé je n'ai pas de compilateur C++11..)

    Au pire, si on n'aime pas le C++11, on peut faire un template pour les méthodes à 0 argument, un autre pour les méthodes à 1 argument, à 2, 3,4, etc.

Discussions similaires

  1. [XL-2007] Trouver la colonne dans laquelle se trouve une valeur donnée
    Par Accessifiante dans le forum Excel
    Réponses: 7
    Dernier message: 04/10/2014, 08h45
  2. Réponses: 2
    Dernier message: 01/02/2013, 13h25
  3. Trouver un nombre dans un tableau avec une fonction
    Par neufrdb dans le forum Collection et Stream
    Réponses: 6
    Dernier message: 27/03/2011, 16h33
  4. Réponses: 3
    Dernier message: 02/03/2009, 12h31
  5. Réponses: 2
    Dernier message: 21/07/2006, 06h55

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo