Obligement - L'Amiga au maximum

Vendredi 19 avril 2024 - 00:15  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

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

Articles in english


Réseaux sociaux

Suivez-nous sur X




Liste des 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,
ALL


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


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


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]


Soutenez le travail de l'auteur