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

C++ Discussion :

cast et héritage multiple


Sujet :

C++

  1. #1
    Membre à l'essai
    Inscrit en
    Février 2010
    Messages
    27
    Détails du profil
    Informations forums :
    Inscription : Février 2010
    Messages : 27
    Points : 24
    Points
    24
    Par défaut cast et héritage multiple
    Bonjour,

    En voulant afficher la structure de mes classes, je suis tombé sur une petite "curiosité".
    Je calcule l'offset d'un donnée membre m dans une classe C, avec &((C*)0)->m. Ce qui marche plutôt bien.
    Par contre, pour les classes parentes, je ne peux apparemment pas utiliser un pointeur NULL. Il semblerait que le pointeur NULL reste NULL quelquesoit le cast effectué:

    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
    #include <iostream>
    class UN
    {
    public : 
        UN():un(1){}
        int un;
    };
     
    class DEUX
    {
    public : 
        DEUX():deux(2){}
        int deux;
    };
     
    class C : public UN, public DEUX
    {
    };
     
    int main(int argc, char* argv[])
    {
        C *pc1 = &C();
        C *pc2 = (C*)0; 
        C *pc3 = (C*)1;
     
        DEUX* pdeux = (DEUX*)pc1;
     
        unsigned offset_1 = (unsigned)(DEUX*)pc1 - (unsigned)pc1;
        unsigned offset_2 = (unsigned)(DEUX*)pc2 - (unsigned)pc2;
        unsigned offset_3 = (unsigned)(DEUX*)pc3 - (unsigned)pc3;
     
        std::cout << pdeux->deux << std::endl << std::endl;
     
        std::cout << offset_1 << std::endl;
        std::cout << offset_2 << std::endl;
        std::cout << offset_3 << std::endl << std::endl;
     
    	return 0;
    }
    Ce qui donne :
    J'utilise Visual Studio. Est ce un comportement standard, ou spécifique à mon compilo?
    Au passage, connaissez vous un moyen de connaitre (à la compilation/execution) la présence ou non et la position de la vfptr?

  2. #2
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 32
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Points : 4 732
    Points
    4 732
    Par défaut
    Entre les prise d'adresse d'objets temporaires et les casts à la C, je suis surpris que tu arrives à donner du sens à ton résultat. D'ailleurs ton code compile pas chez moi.

    Sinon l'offset c'est très dépendant du compilateur et des options (padding, tout ca). Je me demande même s'il n'ont pas le droit de réorganiser l'ordre des membres pour que ca colle mieux.

    En en général, des qu'on quitte les POD, je crois qu'on a plus aucune garantie.

    Edit ; Cete QA de SO semble me donner raison.

    Edit 2 ; ya offsetof sinon. Mais ca ne marche que pour les standard_layout
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  3. #3
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Salut !

    Sans offense aucune, tu fais un peu n'importe quoi avec tes pointeurs et tes casts. Voyons ça en plusieurs étapes:

    Erreur 1

    Tu prend un pointeur sur un objet temporaire détruit immédiatement après. GCC ne s'y trompe pas :

    warning: taking address of temporary
    Par conséquent, tu pointes vers un objet détruit. Problème en vue. Un code correct serait

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    C obj1;
    C *pc1 = &obj1;
    Ou bien:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    C *pc1 =  new C;
    // A la fin
    delete pc1;
    Imprécision 2

    Ok c'est un pointeur nul, le cast ne sert à rien.

    Erreur 3

    Celle là est costaud : tu décides de pointer à l'adresse 1. Soit une adresse arbitraire, peut-être (et probablement) déjà utilisée, en tout cas pas une adresse valide vers un objet de type C. Ne fais jamais ceci.

    Imprécision 4

    La plus intéressante !

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    DEUX* pdeux = (DEUX*)pc1;
    On va partir du principe que tu as corrigé l'erreur 1 et que pc1 pointe vers un objet valide. Ici tu veux caster vers une des classes parentes. Tu n'a pas besoin du cast, tu peux écrire.

    Ca fonctionne par chance, mais le cast en C est dangereux, regarde ce que fait ce code:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    C obj1;
    C *pc1 = &obj1;
    UN   *p_un1 = pc1;
    DEUX *p_deux1 = pc1;
    DEUX *p_deux2 = (DEUX*)p_un1;
    std::cout << p_deux1 << std::endl;
    std::cout << p_deux2 << std::endl;
    Uh ho, p_deux1 et p_deux2 n'ont pas la même valeur et sont censé pourtant pointer vers le même objet non ? Non, le cast C-Style a simplement changé le type du pointeur mais n'a utilisé aucune information sur la structure effective qui se trouve en dessous. En d'autres termes, tu as un pointeur de type DEUX* qui pointe vers un objet valide de type UN*. Très mauvais.

    Ceci nous amène à:

    Erreur 5

    Tu as utilisé l'héritage, qui plus est multiple, sans mettre de destructeurs virtuels. Non seulement les objets ne pourront pas être détruits correctement, mais comme il n'y a aucune fonction membre virtuelle, tes classes n'ont pas de table virtuelle (la fameuse vfptr que tu cherches) et par conséquent le RTTI ne pourra pas fonctionner si tu veux l'utiliser.

    Il te faut:

    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
    #include <iostream>
    class UN {
    public :
        UN():un(1){}
        virtual ~UN() {}
        int un;
    };
     
    class DEUX {
    public :
        DEUX():deux(2){}
        virtual ~DEUX() {}
        int deux;
    };
     
    class C : public UN, public DEUX {
     public:
      virtual ~C() {}
    };
     
    int main(int argc, char* argv[]) {
      C obj1;
      C *pc1 = &obj1;
      UN   *p_un1 = pc1;
      DEUX *p_deux1 = pc1;
      DEUX *p_deux2 = dynamic_cast<DEUX*>(p_un1);
      std::cout << p_deux1 << std::endl;
      std::cout << p_deux2 << std::endl;
      return 0;
    }
    Et un magnifique exemple de cross-cast en prime avec le dynamic_cast, la bonne manière de caster autrement que vers une classe parente.

    Erreur 6

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    unsigned offset_1 = (unsigned)(DEUX*)pc1 - (unsigned)pc1;
    Un double cast C-Style, pour convertir un pointeur (potentiellement 8 octets) en un entier non signé (potentiellement 4 octets). Ca ne compile même pas sur GCC, et je suis étonné que Visual le laisse passer ! Ne fais pas ça, ou au moins convertit dans un type entier de la bonne taille.

    Ici, on pourrait écrire:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    size_t offset_1 = static_cast<size_t>(static_cast<DEUX*>(pc1) -pc1);
    Et encore c'est sale.

    Réponse à la question initiale:

    Oui, un pointeur nul reste nul quel que soit le cast effectué. Comment peux-tu t'attendre à un comportement différent ? Ca tombe sous le sens, ce n'est pas une "curiosité".


    Quand tu auras corrigé tous ces problèmes, tu pourras vraiment t'intéresser aux offsets, qui reste un sujet assez complexe, en particulier dès qu'on fait de l'héritage.
    Find me on github

  4. #4
    Membre à l'essai
    Inscrit en
    Février 2010
    Messages
    27
    Détails du profil
    Informations forums :
    Inscription : Février 2010
    Messages : 27
    Points : 24
    Points
    24
    Par défaut
    Ok, désolé d'avoir posté un code aussi sale. Le but était juste d'avoir un exemple concis pour poser le problème.
    Je suis d'ailleurs tout à fait d'accord avec vos remarques.

    Voyez vous donc une méthode propre pour trouver l'offset de la classe DEUX dans la classe C, sachant que j'utilise des addresses arbitraires puisque je ne souhaite construire aucun objet?
    Pour l'instant, je suppose que je n'ai aucun héritage virtuel.

    Edit :
    Ce que je trouve "currieux", mais finalement légitime, c'est la cas particulier du pointeur NULL : En castant un pointeur sur C vers un pointeur sur DEUX, l'addresse est incrémentée de 4 quelquesoit le pointeur, tant qu'il est non nul.

    Edit 2 : Nouveau code corrigé (à part pc3, mais c'est la seule solution à mon problème pour le moment)
    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
    #include <iostream>
    class UN
    {
    public : 
        UN():un(1){}
        virtual ~UN(){}
        int un;
    };
     
    class DEUX
    {
    public : 
        DEUX():deux(2){}
        virtual ~DEUX(){}
        int deux;
    };
     
    class C : public UN, public DEUX
    {
    public:
        virtual ~C(){}
    };
     
    int main(int argc, char* argv[])
    {
        C c;
        C *pc1 = &c;
        C *pc2 = 0; 
        C *pc3 = reinterpret_cast<C*>(1);
     
        DEUX* pdeux = pc1;
     
        size_t offset_1 = reinterpret_cast<size_t>(static_cast<DEUX*>(pc1)) - reinterpret_cast<size_t>(pc1);
        size_t offset_2 = reinterpret_cast<size_t>(static_cast<DEUX*>(pc2)) - reinterpret_cast<size_t>(pc2);
        size_t offset_3 = reinterpret_cast<size_t>(static_cast<DEUX*>(pc3)) - reinterpret_cast<size_t>(pc3);
     
        std::cout << pdeux->deux << std::endl << std::endl;
     
        std::cout << offset_1 << std::endl;
        std::cout << offset_2 << std::endl;
        std::cout << offset_3 << std::endl << std::endl;
     
    	return 0;
    }

  5. #5
    Membre expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Points : 3 156
    Points
    3 156
    Par défaut
    Re

    Citation Envoyé par damiengif Voir le message
    Voyez vous donc une méthode propre pour trouver l'offset de la classe DEUX dans la classe C, sachant que j'utilise des addresses arbitraires puisque je ne souhaite construire aucun objet?
    Pour l'instant, je suppose que je n'ai aucun héritage virtuel.
    Je n'en connais aucune.

    Citation Envoyé par damiengif Voir le message
    Pour l'instant, je suppose que je n'ai aucun héritage virtuel.
    Attention, l'héritage virtuel et la présence de fonctions membres virtuelles (et donc d'une vtable) sont deux choses différentes.

    Citation Envoyé par damiengif Voir le message
    Ce que je trouve "currieux", mais finalement légitime, c'est la cas particulier du pointeur NULL : En castant un pointeur sur C vers un pointeur sur DEUX, l'addresse est incrémentée de 4 quelquesoit le pointeur, tant qu'il est non nul.
    Quel que soit le pointeur en effet, y compris si celui-ci pointe vers n'importe quoi (comme dans le cas de pc3). Cela est dû au fait que le cast effectué par le compilateur lors de l'affectation d'un pointeur C* à un pointeur DEUX* est calculé à la compilation. Si le pointeur n'est pas nul, il va se contenter de le déplacer au bon offset pour pointer vers la bonne instance de la classe parente. S'il est nul, il le laisse nul.
    Find me on github

  6. #6
    Membre à l'essai
    Inscrit en
    Février 2010
    Messages
    27
    Détails du profil
    Informations forums :
    Inscription : Février 2010
    Messages : 27
    Points : 24
    Points
    24
    Par défaut
    Citation Envoyé par jblecanard Voir le message
    Attention, l'héritage virtuel et la présence de fonctions membres virtuelles (et donc d'une vtable) sont deux choses différentes.
    Oui je sais. Ca n'a d'ailleurs pas d'intérêt pour le moment. Je pensais tout haut à la suite de mon dev..

    Citation Envoyé par jblecanard Voir le message
    Quel que soit le pointeur en effet, y compris si celui-ci pointe vers n'importe quoi (comme dans le cas de pc3). Cela est dû au fait que le cast effectué par le compilateur lors de l'affectation d'un pointeur C* à un pointeur DEUX* est calculé à la compilation. Si le pointeur n'est pas nul, il va se contenter de le déplacer au bon offset pour pointer vers la bonne instance de la classe parente. S'il est nul, il le laisse nul.
    Oui. Naïvement, je pensais que ce cast se foutait pas mal de l'addresse de départ et qu'il ajoutait systématiquement l'offset qui va bien. Mais un pointeur NULL se doit de le rester.

  7. #7
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Citation Envoyé par damiengif Voir le message
    Voyez vous donc une méthode propre pour trouver l'offset de la classe DEUX dans la classe C, sachant que j'utilise des addresses arbitraires puisque je ne souhaite construire aucun objet?
    Si tu nous disais à quoi ça peut te servir, on pourrait peut-être trouver des alternatives ?
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  8. #8
    Membre à l'essai
    Inscrit en
    Février 2010
    Messages
    27
    Détails du profil
    Informations forums :
    Inscription : Février 2010
    Messages : 27
    Points : 24
    Points
    24
    Par défaut
    C'est une appli que je développe pour le fun. A terme, elle consistera en une sorte de watch (du style de visual) en temps réel sur une autre des mes applis.
    j'ai donc besoin de connaître la structure des objets de l'appli "debuggee".
    pour l'instant je fais sans le .pdb.
    ma solution consiste donc à partir des headers et des mêmes options de compile a calculer tous les offsets voulus grâce a des macros(TBD):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    BEGIN_CLASS (maClasse)
    BASE (Base)
    MEMBER(int, membre1)
    ...
    END_CLASS (maClasse)

  9. #9
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Points : 16 213
    Points
    16 213
    Par défaut
    Zut, j'allais te proposer les pdb...
    Autrement, tu peux écrire un parseur, et recalculer à la main ces offsets (attention, pas trivial, sauf si tu restreint ce que tu peux mettre là dedans).

    Tu peux aussi utiliser une bibliothèque d'introspection.

    Ou éventuellement initialiser des structures de données avec des pointeurs sur membre (puisque tu imposes déjà à l'observé d'être créé de manière non standard).

    Voire, carrément, modifier l'observé pour qu'il ne contienne que des maps clé/valeur...
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  10. #10
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    La première chose à savoir, c'est que NULL (on préférera utiliser nullptr en C++11) est connu comme étant une adresse invalide.

    Certains compilateurs ont des comportements d'optimisation assez... brutaux (dirons nous) lorsqu'ils rencontre cette valeur.

    La deuxième chose qu'il faut savoir, c'est que 1 correspond à une adresse fixe qui ne sera absolument pas modifiée quel que soit le transtypage que tu pourra envisager. Ce sera, tout simplement l'adresse qui sera représentée en hexadécimale sous la forme de 0x0000000000000001.

    Elle ne te sera d'aucune utilitée

    Si tu veux pouvoir voir la différence d'offset entre les classes de base et tes classes dérivée, il faut vraiment que tu travailles avec des objets corrects.

    La première chose que l'on pourrait faire ressemblerait par exemple à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    int main(){
       C *c = new C;
       UN * un= c;
       DEUX *deux =c;
       std::cout<<c << " "<< un <<" "<< deux<<std::endl
                     <<"sizeof C"<<sizeof(C)
                     <<" | sizeof UN "<<sizeof(UN)
                     <<" | sizeof DEUX "<<sizeof(DEUX);
       delete c;
    }
    et, ho surprise, chez moi j'obtiens l'affichage
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    0xeae010 0xeae010 0xeae20
    sizeof C 32 | sizeof UN 16 | sizeof DEUX 16
    Je tiens à préciser ici que les valeurs sont spécifiques à mon environnement de développement. Certaines valeurs pourraient être légèrement différentes

    Intéressons nous en premier lieu à la deuxième ligne. Elle nous indique la taille des différents élément de l'objet.

    Elle nous dit que la classe UN et la classe DEUX ont toutes les deux une taille de 16 bytes, ce qui est normal. Tiens, c'est bizarre, j'aurais juré qu'un int avait une taille de 4 bytes!!! Mais c'est normal.

    D'abord, vu que UN et DEUX ont des destructeurs virtuels, le compilateur rajoute ce que l'on appelle "une table de fonctions virtuelle" à ces deux classes. On ne va pas trop rentrer dans les détails ici, mais, en gros, c'est un tableau (en fait un pointeur vers un tableau) qui contiendra l'adresse des fonctions qui devront réellement être appelées afin de permettre les comportements polymorphes.

    Ce pointeur, bah, il prend une certaine place en mémoire. Chez moi, avec mon linux, vu que je suis dans un environnement full 64 bits, cela correspond à 8byte (sur les pc 32 bits, cela n'en ferait sans doute que 4 ).

    Note d'ailleurs que si l'on faisait en sorte que le destructeur ne soit pas virtuel, la taille affichée descendrait à 4, qui est, justement, la taille prise en mémoire par un int .

    Oui, mais, eh, ho, STOP : jusqu'à preuve du contraire, 8+4, ca fait 12, ca ne fait pas 16!!! Suis-je sur de ne pas me tromper en parlant des 8bits nécessaires au pointeurs de la vtable

    Oui, j'en suis tout à fait sur. Les quatre bytes manquant, ce sont ce que l'on appelle des bits de padding.

    Le fait est que le processeur (du moins, le mien), manipule habituellement les données sous la forme d'un ensemble de 16 bytes. Il y a parfaitement moyen de faire tenir 8 ou 12 bytes dans un espace qui est prévu pour en contenir 16. Mais, il est difficile de faire tenir 2*12 bytes (24 au total ) dans ce meme espace.

    Du coup, le compilateur va dire, en très gros: "c'est pas grave, tant pis si je perds 8 bits, mais moi, je m'organise de telle manière à ce que le premier byte de ma structure UN corresponde au premier byte que le processeur pourra atteindre. A parti de là, il sera en mesure d'accéder au reste".

    C'est quelque chose qui est fait au niveau de la classe UN et qui est également fait au niveau de la classe DEUX.

    Ensuite, le compilateur rencontre la classe C qui hérite de UN et de DEUX.

    Et là, la norme est particulièrement claire quant à la manière dont les choses doivent se passer:

    De manière générale, les données d'une classe sont toujours placée dans l'ordre de leur déclaration.

    Ainsi, si tu avais une structure (ou une classe, parce que c'est pareil) proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    struct MaStruct{
        int i;
        double d;
        short s;
    };
    les données seraient organisée comme suit
    i serait à l'offset 0 de la classe. Ce serait donc la première donnée de la classe et le code suivant

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    int main(){
        MaStruct s;
        std::cout<<"adresse de s   : "<< & s<<"\n"
                 <<"adresse de s.i : "<< & (s.i)<<"\n"
                 <<"adresse de s.d : "<< &(s.d)<<"\n"
                 <<"adresse de s.c : "<< &(s.s)<<std::endl;
        return 0;
    }
    provoquera un affichage qui te confirme bien que l'adresse de la structure et celle de la première donnée correspondent et que l'adresse de s.d et de s.s sont chaque fois décalées de 8bytes. Pourquoi 8 bytes Toujours à cause du padding.

    Ajoutons maintenant une classe de base (pour laquelle nous ne rendons pas le destructeur virtuel, par facilité et regardons un peu ce que l'on obtient. Nous aurons un code proche de
    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
    struct Base{
        int bi;
        short bs;
    };
    struct MaStruct : public Base{
        int i;
        double d;
        short s;
    };
     
    int main(){
        MaStruct s;
        std::cout<<"sizeof int         : "<<sizeof(int)<<"\n"
                 <<"sizeof double      : "<<sizeof(double)<<"\n"
                 <<"sizeof short       : "<<sizeof(short)<<"\n"
                 <<"total size         : "<<(sizeof(int)+
                                             sizeof(double)+
                                             sizeof(short))*2<<"\n"
                 <<"sizeof Base        : "<<sizeof(Base)<<"\n"
                 <<"sizeof MaStruct    : "<<sizeof(MaStruct)<<"\n"
                 <<"adresse de s       : "<< &s        <<"\n"
                 <<"adresse de bs      : "<< & s.bs    <<"\n"
                 <<"adresse de bd      : "<< & s.bd    <<"\n"
                 <<"adresse de bi      : "<< & s.bi    <<"\n"
                 <<"adresse de s.i     : "<< &(s.i)    <<"\n"
                 <<"adresse de s.d     : "<< &(s.d)    <<"\n"
                 <<"adresse de s.c     : "<< &(s.s)    <<std::endl;
        return 0;
    }
    qui occasionnera une sortie proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    sizeof int         : 4
    sizeof double      : 8
    sizeof short       : 2
    total size         : 28
    sizeof Base        : 24
    sizeof MaStruct    : 48
    adresse de s       : 0x<snip>ba0
    adresse de bi      : 0x<snip>ba0
    adresse de bd      : 0x<snip>ba8
    adresse de s.i     : 0x<snip>bb0
    adresse de s.d     : 0x<snip>bb8
    adresse de s.c     : 0x<snip>bc0
    Que peut-on déduire de cette sortie?

    D'abord, que la classe Base a subit un padding, ce à quoi il fallait s'attendre.

    Ensuite que le premier membre de base prend le premier byte disponible dans MaStruct et que tout les membres spécifiques à MaStruct ne viennent qu'après tous les autres membres de Base.

    Enfin, que le padding imposé à Base se retrouve "en l'état" dans MaStruct.

    Ce comportement est garanti par la norme et pleinement justifié d'un point de vue conceptuel.

    Si je dis qu'il est pleinement justifié du point de vue conceptuel, c'est, tout bonnement, parce que la relation d'héritage implique que tout objet du type dérivé EST-UN objet du type de base. Il doit donc avoir l'ensemble des caractéristiques de la classe de base.

    On y trouvera non seulement les fonctions et les membres publiques (ce qui nous intéressera particulièrement en tant qu'utilisateur), mais aussi tous les membres privés (ou protégés), qui seront de toute manières indispensables pour assurer le bon fonctionnement des fonctions exposées (du moins, de celles qui ne sont pas virtuelles ).

    Et si on a recours à l'héritage multiple, le principe et l'ordre dans lequel les différents membres seront placés dans la classe dérivés seront exactement les mêmes :
    On trouvera le contenu des différentes classes de base dans l'ordre exacte dans lequel les héritages sont définis.

    Autrement dit, si on a deux classes de bases nommées Base1 et Base2, que la classe dérivée hérite d'abord de Base1, puis de Base2, on trouvera, dans la classe dérviée, d'abord les membres de Base1 puis ceux de Base2 et enfin les membres spécifiques à la classe dérivée. Si on inverse l'héritage de Base1 et de Base2, on inverse l'ordre dans lequel ces deux classes sont placées au niveau de la classe dérivée.

    C'est ce qui explique le décalage d'adresse dans le tout premier code de cette intervention
    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

  11. #11
    Membre à l'essai
    Inscrit en
    Février 2010
    Messages
    27
    Détails du profil
    Informations forums :
    Inscription : Février 2010
    Messages : 27
    Points : 24
    Points
    24
    Par défaut
    Tout d'abord merci à tous pour votre disponibilité .
    Citation Envoyé par koala01 Voir le message
    La deuxième chose qu'il faut savoir, c'est que 1 correspond à une adresse fixe qui ne sera absolument pas modifiée quel que soit le transtypage que tu pourra envisager. Ce sera, tout simplement l'adresse qui sera représentée en hexadécimale sous la forme de 0x0000000000000001.

    Elle ne te sera d'aucune utilitée
    d'accord pour 1, mais (je m'excuse encore pour les casts C) avec (C*)1 c'est possible. Ca devient un pointeur quelconque sur un objet de type C. Et du coup, je peux calculer mon offset avec :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    (size_t)(DEUX*)(C*)1 - 1
    Citation Envoyé par koala01 Voir le message
    Si tu veux pouvoir voir la différence d'offset entre les classes de base et tes classes dérivée, il faut vraiment que tu travailles avec des objets corrects.
    Seulement, je ne souhaiterais pas construire d'objets. Sinon, je ne pourrai pas calculer les offsets sur des classes abstraites.

  12. #12
    Membre à l'essai
    Inscrit en
    Février 2010
    Messages
    27
    Détails du profil
    Informations forums :
    Inscription : Février 2010
    Messages : 27
    Points : 24
    Points
    24
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    Zut, j'allais te proposer les pdb...
    J'y viendrai je pense, mais je n'y connais rien et l'api windows m'a pas l'air très simple à utiliser.

  13. #13
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par damiengif Voir le message
    Seulement, je ne souhaiterais pas construire d'objets. Sinon, je ne pourrai pas calculer les offsets sur des classes abstraites.
    La seule différence pour ce qui concerne les classes abstraites, c'est que tu ne peux pas directement les instancier (comprends : sans instancier une classe dérivée concrete).

    Mais tu les retrouves exactement de la meme manière dans les classes dérivées
    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

  14. #14
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    La macro offsetof peut t’intéresser.

    C’est garanti ne marcher que pour les POD, mais apparemment, sous gcc, ça marche aussi pour ton exemple.

  15. #15
    Membre à l'essai
    Inscrit en
    Février 2010
    Messages
    27
    Détails du profil
    Informations forums :
    Inscription : Février 2010
    Messages : 27
    Points : 24
    Points
    24
    Par défaut
    En fait ce que je cherche à faire (méthode sans .pdb) n'est pas vraiment POO spirit.
    D'ailleurs j'accède aux membres privés/protégés de manière assez scandaleuse.
    Je ne suis donc plus à un abus près.

    Merci à tous et bon WE

  16. #16
    Membre averti

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

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Points : 422
    Points
    422
    Par défaut
    moi j'aime bien cette solution (les gens crisent quand on appelle une méthode avec nullptr comme this, sauf que vu que c'est une méthode non virtuelle et qu'on ne fait pas d'indirection sur this dans la méthode ça ne pose pas de problème, et à mon avis le compilateur qui ne sait pas générer ce code est un ******) :

    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
     
    #include <iostream>
    struct A {
    	double d[2];
    	virtual void lalaA() {}
    	void * AgetThis() {
    		return this;
    	}
    };
     
    struct B {
    	double d[3];
    	virtual void lalaB() {}
    	void * BgetThis() {
    		return this;
    	}
    };
     
    struct C {
    	double d[4];
    	virtual void lalaC() {}
    	void * CgetThis() {
    		return this;
    	}
    };
     
    struct D : A,B,C {
    	void * DgetThis() {
    		return this;
    	}
    };
     
     
    int main()
    {
    	void * d = ((D*)0)->DgetThis();
    	void * a = ((D*)0)->AgetThis();
    	void * b = ((D*)0)->BgetThis();
    	void * c = ((D*)0)->CgetThis();
     
    	std::cout << "d : " << d << "\n";
    	std::cout << "a : " << a << "\n";
    	std::cout << "b : " << b << "\n";
    	std::cout << "c : " << c << "\n";
    	return 0;
    }
    ou bien de manière équivalente mais moins intrusive :
    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
     
    #include <iostream>
     
    template <typename T> struct Dummy {
    	void * getThis() {
    		return this;
    	}
    };
     
    struct A : Dummy<A> {
    	double d[2];
    	virtual void lalaA() {}
    };
     
    struct B : Dummy<B> {
    	double d[3];
    	virtual void lalaB() {}
    };
     
    struct C : Dummy<C> {
    	double d[4];
    	virtual void lalaC() {}
    };
     
    struct D : A,B,C,Dummy<D> {
    };
     
     
    int main()
    {
    	void * d = ((D*)0)->Dummy<D>::getThis();
    	void * a = ((D*)0)->Dummy<A>::getThis();
    	void * b = ((D*)0)->Dummy<B>::getThis();
    	void * c = ((D*)0)->Dummy<C>::getThis();
     
    	std::cout << "d : " << d << "\n";
    	std::cout << "a : " << a << "\n";
    	std::cout << "b : " << b << "\n";
    	std::cout << "c : " << c << "\n";
    	return 0;
    }

  17. #17
    Membre averti

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

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Décembre 2004
    Messages : 499
    Points : 422
    Points
    422
    Par défaut
    Ha ! Mais non, il y a 15000 fois plus simple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    	D* d = (D*)1; // et non pas 0
    	A *a = d;
    	B *b = d;
    	C *c = d;
     
    	std::cout << "d : " << (void*)((char*)d-1) << "\n";
    	std::cout << "a : " << (void*)((char*)a-1) << "\n";
    	std::cout << "b : " << (void*)((char*)b-1) << "\n";
    	std::cout << "c : " << (void*)((char*)c-1) << "\n";
    d : 00000000
    a : 00000000
    b : 00000020
    c : 00000048

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Héritage multiple et cast
    Par le-roy_a dans le forum C++
    Réponses: 4
    Dernier message: 01/09/2008, 16h58
  2. [heritage][conception]héritage multiple en java!
    Par soulhouf dans le forum Langage
    Réponses: 9
    Dernier message: 25/08/2005, 20h03
  3. L'héritage multiple est-il possible en Delphi ?
    Par SchpatziBreizh dans le forum Langage
    Réponses: 8
    Dernier message: 30/06/2005, 11h30
  4. utilisez vous l'héritage multiple ?
    Par vodosiossbaas dans le forum C++
    Réponses: 8
    Dernier message: 13/06/2005, 20h25
  5. [XML Schemas]héritage multiple
    Par nicolas_jf dans le forum XML/XSL et SOAP
    Réponses: 2
    Dernier message: 10/06/2003, 12h55

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