Obligement - L'Amiga au maximum

Jeudi 29 juin 2017 - 05:40  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Accueil
 · A Propos
 · Articles
 · Galeries
 · Glossaire
 · Hit Parade
 · Liens
 · Liste jeux Amiga
 · Quizz
 · Téléchargements
 · Trucs et astuces


Articles

 · Actualité (récente)
 · Actualité (archive)
 · Comparatifs
 · Dossiers
 · Entrevues
 · Matériel (tests)
 · Matériel (bidouilles)
 · Points de vue
 · En pratique
 · Programmation
 · Reportages
 · Tests de jeux
 · Tests de logiciels
 · Tests de compilations
 · Articles divers

 · Articles in english
 · Articles in other languages


Twitter

Suivez-nous sur Twitter




Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Moteurs de recherche
 · Pages de liens
 · Constructeurs matériels
 · Matériel
 · Autres sites de matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Développeurs logiciels
 · Logiciels
 · Développeurs de jeux
 · Jeux
 · Autres sites de jeux
 · Scène démo
 · Divers
 · Informatique générale


Jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Partenaires

Annuaire Amiga

Amedia Computer

Relec

Hit Parade


Soutien

N'hésitez pas à soutenir le projet Obligement



Contact

David Brunet

Courriel

 


Programmation : C/C++ - Exec et la programmation orienté objet : les fonctions virtuelles du C++
(Article écrit par Gilles Dridi et extrait d'Amiga News - novembre 1996)


Voici une nouvelle série d'articles sur le système d'exploitation : Exec et la programmation orientée objet. J'ai débuté sur Amiga avec l'environnement Devpac : assembleur et débogueur, et non avec le compilateur C que distribuait SAS, à l'époque. Aussi je vous conseille de vous procurer un compilateur C (du domaine public) si vous commencez à programmer, car vous aurez à la fois un compilateur et un assembleur (mais vous pouvez aussi vous intéresser à d'autres langages comme ceux présentés dans Amiga News). Comme prérequis, il faut déjà connaître la syntaxe du C et savoir faire des éditions de liens et pour cela, je vous renvoie aux articles déjà parus dans Amiga News. Idem, vous devez aussi connaître la signification des mnémoniques du langage machine (de notre Amiga) telles que : JMP, BSR, RTS, etc. et des différents modes d'adressage.

Toutefois, du langage BASIC au Smalltalk ou du µP 6502 au PowerPC, les techniques de base ou concepts des langages ou architectures machines sont récurrents et vous ne perdrez jamais votre temps à apprendre l'un plutôt que l'autre.

Le premier article traite des fonctions virtuelles de C++ v1.0 (sans héritage multiple, ni exceptions, ni templates). Après cette introduction sur un des mécanismes importants de C++, nous verrons comment construire un module-bibliothèque.

Les fonctions virtuelles avec C/C++

Principe

C'est une indirection. L'adresse de la fonction est prise dans un tableau au moment de l'exécution. Dans un autre langage à objets, on appelle ce mécanisme : lien dynamique. Exemple : pour détailler ce mécanisme, j'ai choisi une classe B qui hérite d'une classe A. Voici le schéma d'héritage :

          ___________
         / Classe A  
        ---------------
        |affo()        |
        |virt. aff()   |
        ---------------
              /|
               |
          _____|______
         / Classe B   
        ----------------
        |affo()        |
        |virt. aff()   |
        ---------------

La fonction affo() n'est pas virtuelle, aff() l'est.

Interface

    class A {
    protected:
        char lettre;
    public:
        A(char c='A');
        void affo();
        virtual void toto();
        virtual void aff();
    };

    class B: public A {
    public:
        B();
        void affo();
        void toto();
        void aff();
    };

Implantation

    A::A(char c):lettre(c) { printf("A::A(%c)\n", lettre); }
    void A::affo() { printf("A::affo()\n"); }
    void A::toto() {; }
    void A::aff() { printf("A::aff()\n"); }

    B::B(): ('B') { printf("B::B(%c)\n", lettre); }
    void B::affo() { printf("B::affo()\n"); }
    void B::toto() {; }
    void B::aff() { printf("B::aff()\n"); }

Résultat du programme de test.

    void main() {
        A a, *ptrA;
        B b;
    
        a.aff();
        b.aff();
    
        ptrA= &b;
        ptrA->affo();
        ptrA->aff();
    }

    A::A(A)
    A::A(B)
    B::B(B)

    A::aff()
    B::aff()

    A::affo()
    B::aff()

Alors que le pointeur sur la classe A (ptrA) fait un appel à la fonction affo() de la classe A, l'appel de aff() qui est une fonction virtuelle avec ce pointeur sur la classe A fait un appel à la fonction aff() de la classe B. Que s'est-il passé ?

Explications

Voici le code C généré pour la classe contenant au moins une fonction virtuelle :

    struct A { 
    char _A_lettre ;
    int (**_A__vptr )();
    } ;

    int _A_toto ();
    int _A_aff ();

    static int (*A__vtbl[])() = {
    (int(*)()) _A_toto , 
    (int(*)()) _A_aff , 0};

La classe A du C++ est traduite en C, en structure or en plus de notre caractère, on a un pointeur sur un pointeur de fonction ou un pointeur sur tableau de fonction (_A__vptr). Puis, la déclaration d'un tableau de fonctions "virtuelles" avec son pointeur _A__vtbl.

    struct A *_A__ctor (); // interface de la fonction

    struct A *_A__ctor (_auto_this , _auto_c ) 
                     // c'est proche du langage machine...
    register struct A *_auto_this ;
    char _auto_c ;
    { 
    if (_auto_this == 0 )_auto_this = (struct A *)_new ( (long )6) ;
    ( (_auto_this -> _A__vptr =  A__vtbl), _auto_this ->_A_lettre = _auto_c ) ;

    printf ( "A::A(%c)\n", _auto_this -> _A_lettre ) ;
    return _auto_this ;
    }

En C++, le constructeur, noté __ctor ici, se charge de réserver la mémoire pour la structure que l'on vient de voir et d'initialiser dans notre cas le pointeur _A__vptr sur la table des fonctions virtuelles.

Passons à la classe B.

    struct B {
    char _A_lettre ;
    int (**_A__vptr )();
    } ;

    int _B_toto ();
    int _B_aff ();
    static int (*B__vtbl[])() = {
    (int(*)()) _B_toto , 
    (int(*)()) _B_aff , 0};

La classe B du C++ est traduite en C, en structure qui contient les mêmes items que la structure A (Classe A). La classe B n'a rien ajouté en effet. Par contre, on a une nouvelle déclaration du tableau des fonctions virtuelles. Celui-ci est initialisé avec les fonctions virtuelles de la classe B.

    struct B *_B__ctor ();

    struct B *_B__ctor (_auto_this )
    register struct B *_auto_this ;
    {
    if (_auto_this == 0 )_auto_this = (struct B *)_new ( (long )6) ;
    ( (_auto_this = (struct B *)_A__ctor ( ((struct A *)_ auto_this ), 'B' ) ), _auto
                                            _this ->_A__vptr =  B__vtbl) ;

    printf ( "B::B(%c)\n", _auto_this -> _A_lettre ) ;
    return _auto_this ;
    }

Qu'est-ce qui change dans le constructeur de la classe B ? L'affectation du pointeur _A__vptr par le nouveau tableau.

Conclusion

Rappel du programme de test :

    void main() {
        A a, *ptrA;
        B b;

        a.aff();
        b.aff();

        ptrA= &b;
        ptrA->affo();
        ptrA->aff();
    }

    Résultat:
    A::A(A)
    A::A(B)
    B::B(B)

    A::aff()
    B::aff()

    A::affo()
    B::aff()

Enfin, voyons ce qui s'est passé dans le programme de test lors de l'appel ptrA->aff() ;

    int main (){ _ConStrucT(); { 
    struct A _auto_a ;
    struct A *_auto_ptrA ;
    struct B _auto_b ;

    _A__ctor ( & _auto_a , 'A' ) ;
    _B__ctor ( & _auto_b ) ;

    (*(((int (*)())((& _auto_a )-> _A__vptr [1]))))( & _auto_a ) ;
    (*(((int (*)())((& _auto_b )-> _A__vptr [1]))))( & _auto_b ) ;

    _auto_ptrA = (struct A *)(& _auto_b );
    _A_affo ( _auto_ptrA ) ;
    (*(((int (*)())(_auto_ptrA -> _A__vptr [1]))))( _auto_ptrA ) ;
    }
    }

Déclarations des classes A et B (structures dans ce code C), appels des constructeurs, appels des fonctions virtuelles ; le premier appel _A__vptr est initialisé avec le tableau _A__vtbl[], le deuxième appel _A__vptr est initialisé avec le tableau _B_vtbl[] qui contient B::aff(). Cette initialisation est faite par l'appel du constructeur de la classe B au-dessus : _B__ctor( & _auto_b );

Ensuite, notre pointeur sur la classe A est initialisé avec l'adresse de la structure B (de la classe B). L'appel à affo() via ce pointeur est traduit en C par l'appel à la fonction affo() alors que l'appel à aff() via ce pointeur est traduit par le même appel que b.aff(). En effet, le _auto_ptrA pointe sur & _auto_b. Voir le paragraphe ci-dessus.

On peut donc dire qu'il y a indirection ou lien dynamique pour les fonctions déclarées virtuelles en C++ et on retiendra que l'initialisation de cette indirection se fait au moment de l'appel de son constructeur.

Exemples

Voici deux programmes C et son équivalent C++ qui réalisent la même sortie écran :

Exemple C1 :

    #define MAXNOM  3   // problème garder la table et les types à jour!
    char * Noms[MAXNOM]= { "Fruit", "Pomme", "Abricot" };
    enum Type { TFruit= 0, TPomme, TAbricot };

    struct Fruit { struct Fruit * suivant; enum Type type; };
    char * ident(struct Fruit * p) { return Noms[p->type]; }

    void main() {
        struct Fruit fruit = { 0, TFruit };
        struct Fruit pomme = { 0, TPomme };
        struct Fruit abricot = { 0, TAbricot };
        struct Fruit * ptrF= &fruit;

        fruit.suivant= &pomme; pomme.suivant= &abricot;

        while(ptrF) { printf("%s\n", ident(ptrF)); ptrF= ptrF->suivant; }
    }

Exemple C2 :

    struct Fruit { struct Fruit * suivant; char * (*ident)(); };
    char * identFruit() { return "Fruit"; }

    char * identPomme() { return "Pomme"; }
    char * identAbricot() { return "Abricot"; }

    void main() {
        struct Fruit fruit = { 0, &identFruit };
        struct Fruit pomme = { 0, &identPomme };
        struct Fruit abricot = { 0, &identAbricot };
        struct Fruit * ptrF= &fruit;

        fruit.suivant= &pomme; pomme.suivant= &abricot;

        while(ptrF) { printf("%s\n", (ptrF->ident)()); ptrF= ptrF->suivant; }
    }

Exemple C++:
    class Fruit {
        Fruit * suivant;
    public:
        Fruit(Fruit * sui) { suivant= sui; }
        Fruit * le_suivant() { return suivant; }
        virtual char * ident() { return "Fruit"; }
    };

    class Pomme: public Fruit {
    public:
        Pomme(Fruit * sui): (sui) {}
        char * ident() { return "Pomme"; }
    };

    class Abricot: public Fruit {
    public:
        Abricot(Fruit * sui): (sui) {}
        char * ident() { return "Abricot"; }
    };

    void main() {
        Abricot abricot(0);
        Pomme   pomme(&abricot);
        Fruit   fruit(&pomme), *ptrF= &fruit;

        while(ptrF) { printf("%s\n", ptrF->ident()); ptrF= ptrF->le_suivant(); }
    }

Voilà, c'est fini, à bientôt.


[Retour en haut] / [Retour aux articles]