La présentation est en train de télécharger. S'il vous plaît, attendez

La présentation est en train de télécharger. S'il vous plaît, attendez

Page de garde Inside C++ Maîtrise dinformatique Février 2002.

Présentations similaires


Présentation au sujet: "Page de garde Inside C++ Maîtrise dinformatique Février 2002."— Transcription de la présentation:

1 Page de garde Inside C++ Maîtrise dinformatique Février 2002

2 Quest-ce quun ordinateur ? Fondamentalement, un ordinateur cest : Un (ou des) processeur(s) De la mémoire vive (non persistante) Des périphériques de stockage persistants (disque dur, graveurs, bandes, …) Des périphériques dentrées-sorties (clavier, souris, carte vidéo, carte son, …) … La mémoire vive a trois utilisations majeures : Stockage des instructions (le code) des processus Pile dexécution des processus Espace de tas des processus (là où ( m | c ) alloc et new allouent les blocs demandés) Le processeur contient des registres, dans lesquels sont mémorisés, pour le processus courant : Le pointeur sur linstruction en cours Le pointeur courant de la pile Les adresses de base des segments de code, de pile, de tas, … Les premiers arguments des fonctions appelées, la valeur de retour, … … Les registres servent aussi de "variables intermédiaires" dans les calculs.

3 Quest-ce quun ordinateur ? La mémoire de pile sert à : Préserver (empiler) les valeurs des registres pour pouvoir les restaurer (dépiler) ultérieurement, par exemple avant et après un appel de fonction Passer les arguments des fonctions qui ne sont pas passés par des registres Allouer de lespace pour les variables locales et les objets locaux (+ alloca ) … Par la suite, nous considèrerons que nous travaillons sur une architecture 32 bits Intel, donc avec un support de pile dexécution fourni par le processeur (ce qui nest pas le cas de tous les processeurs). Petit rappel : C/C++ garantissent des relations dordre entre les tailles des types primitifs…

4 Quest-ce quun ordinateur ? processeur PC : Program Counter (pointeur dinstruction) SP : Stack Pointer (pointeur de pile) pc... mov reg1, [a] add reg1, [b] cmp reg1, [c] jge +5Fh mov [d], 69h jmp main+43h... code segment reg1reg2 reg3reg4 …… stack segment sp

5 Appel de fonction simple XXX f( … ) { … } Ladresse de f est connue à la compilation. stack Schéma dun appel à f : sp Préservation des registres caller-save registres caller-save Empilement des arguments arguments Saut à la fonction après empilement de ladresse de retour return address Réservation despace de pile pour les variables/objets locaux variables locales Préservation des registres callee-save registres callee-save f fait son boulot… Restauration des registres callee-save utilisés par la fonction Dépilement des variables locales Retour à linstruction suivant lappel de f Dépilement des arguments Restauration des registres caller-save

6 Appel de fonction simple Les registres caller-save sont les registres utilisés par la fonction appelante et que les fonctions appelées ont le droit de modifier. Les registres caller-save sont donc préservés par la fonction appelante même si la fonction appelée ne les modifie pas… Les registres callee-save sont les registres utilisés par la fonction appelée, qui sont donc préservés par la fonction appelée même si les fonctions appelantes ne les utilisent pas… Le stockage des variables locales dans la pile est le mécanisme permettant, par exemple, aux différentes "instances" dune fonction récursive davoir chacune leurs propres variables locales. Les variables locales static ne sont que des variables globales à visibilité réduite. Les conventions dappels (passage des arguments par registre ou dans la pile, ordre de passage des arguments, responsabilité des dépilements darguments, registres callee-save ou caller-save, …) dépendent à la fois de larchitecture matérielle (cf. doc constructeur) et du language ( extern "C", extern "Pascal", …).

7 Appel de méthode non virtuelle Le mécanisme dappel de méthodes static est strictement identique à celui des appels de fonctions. Les méthodes non- static ont un paramètre semi-caché supplémentaire : le pointeur constant this.

8 Fonctions et méthodes inline Le mécanisme dinlining vise à réduire le coût des appels de fonctions/méthodes. Si, au moment de la génération de code pour un appel de fonction ou de méthode : Sa "version" / son adresse est connue (cest une fonction ou, dans le cas dune méthode, on connaît le type dynamique de lobjet). Son implémentation est accessible. Alors cet appel de fonction est candidat à linlining, cest-à-dire au remplacement de tout le mécanisme dappel par limplémentation de la fonction elle-même. Le compilateur vérifie, pour chaque inlining potentiel, que la taille du code expansé ne soit pas trop supérieure à celle du code avec un appel de fonction, ce qui est généralement le cas avec les accesseurs et les modifieurs. Linlining augmente généralement la portée des optimisation du compilateur (propagation de constantes dans le corps de la fonction, …).

9 Memory layout des objets simples class|struct { bool b; char c; double d; float f; int i; long l; void *ptr; char c2; bool bb[2]; int ii[3]; } a; &a +0 &a +4 &a +8 &a +12 &a +16 &a +20 &a +24 &a +28 &a +32 &a +36 &a +40 &a +44 bc d f i l ptr bb ii c2 Dans les union s, tous les membres commencent à la même adresse et la taille dune union est celle du plus grand de ses membres.

10 Memory layout des objets simples Les données sont disposées dans lordre de déclaration. Par défaut, les données sont alignées en fonction de leur taille en octets : multiple de 1 pour les bool et les char, multiple de 4 pour les int, les long, les float, les pointeurs, …, multiple de 8 pour les double. Il peut donc y avoir de lespace inutilisé dans les structures… On peut forcer le compilateur à aligner les données différemment (packing) mais, généralement, un processeur travaillant sur un certain nombre de bits, 32 par exemple, naime guère les adresses qui ne sont pas des multiples de 4 octets, ou les données dont la taille ne correspond pas à 4 octets…

11 Héritage simple En cas dhéritage simple, les données sont simplement accolées (dans un ordre dépendant de limplémentation) : class A { AAA a; }; class B: public A { BBB b; }; class C: public B { CCC c; }; C x; C::B::A::a b c B b= c; B::A::a b Ici, linitialisation de b par copie de c entraîne le slicing des données de c provenant de la dérivation de la classe B. A& a= c; a étant une référence (un "pointeur constant") vers c, aucune donnée nest dupliquée.

12 Héritage simple et fonctions non virtuelles Lorsque les fonctions ne sont pas virtuelles, leur adresse est résolue lors de la compilation en fonction du type statique. class A { public: void f(); }; B b; b.f(); // invoque B::f() A a= b; // slicing de b a.f(); // invoque A::f() A& c= b; c.f(); // invoque A::f() bien que c soit en fait un B class B: public A { public: void f(); };

13 Fonctions virtuelles Dès lors quune classe a au moins une méthode virtuelle, ses instances ont un pointeur caché (" vptr ") vers la table des méthodes virtuelles (" vtbl "). Les tables de méthodes virtuelles sont partagées par toutes les instances dune même classe. class A { XXX a; public: virtual ~A(); virtual void f(); void g(); }; A::vtbl ~() f() class B: public A { YYY b; public: virtual ~B(); virtual void f(); void g(); virtual void h(); }; B::vtbl ~() f() h() B b; vptr A::a b B:: b.f(); b.g(); A& a= b; a.f(); a.g();

14 Casts de pointeur polymorphe en pointeur non-polymorphe Dans les implémentations de compilateurs C++ où le vptr est placé au début des objets, le cast dun pointeur vers une classe ayant au moins une méthode virtuelle en un pointeur vers une classe nayant pas la moindre fonction virtuelle nécessite un décalage. class A { AAA a; }; class B: public A { BBB b; public: virtual void f(); }; B * b= new B(); vptr A::a b B:: A * a= b; Le compilateur doit tout dabord vérifier que b ne soit pas 0 / NULL, puis lui ajouter sizeof( vptr ). De même, pour pouvoir comparer a et b ( ==, != ), le compilateur sait quil faut normaliser les pointeurs. Par contre, delete a génère du code faux : a ne pointe plus sur un début de bloc alloué par new ! Ainsi, les casts (implicites ou non) peuvent nécessiter de générer du code…

15 Héritage multiple sans fonctions virtuelles class A { AAA a; }; class B: A { BBB b; }; class C: A { CCC c; }; class D: B, C { DDD d; }; D::B::A::a b C::A::a c d D * d= new D(); C * c= d; (c == d) true ! delete c;

16 Héritage multiple avec fonctions virtuelles class A { AAA a; public: virtual ~A(); }; class B: A { BBB b; }; class C: A { CCC c; }; class D: B, C { DDD d; }; D * d= new D(); D::B::A:: vptr a b C::A:: vptr a c d B * d_b= d; A * d_b_a= d_b; C * d_c= d; A * d_c_a= d_c; Avec, toujours : d == d_c true ! Mais : delete d_c;

17 Héritage virtuel Les données des classes héritées virtuellement ne sont pas "dupliquées". Chaque classe héritant vituellement dune classe de base contient un pointeur vers les données de cette classe. class A { AAA a; }; class B: virtual A { BBB b; }; class C: virtual A { CCC c; }; class D: B, C { DDD d; }; D::B:: A* b C:: A* c d A:: a

18 Le RTTI..... Lorsque le RTTI est activé, le compilateur initialise une instance de la classe type_info par classe, pour les références, plus ce quil faut pour tous les pointeurs rencontrés ainsi que les types primitifs. Ce sont des références vers ces instances (constantes) qui seront renvoyées par typeid. Le résultat de typeid peut-être connu lors de la compilation : Pour les types primitifs : int i; const type_info& i_ti= typeid( i ); Pour les pointeurs (seul le type statique de lobjet pointé intervient) : A * ptr= new C(); // C dérive de A const type_info& ptr_ti= typeid( ptr ); Pour les références (non triviales), le résultat de typeid est déduit du vptr.

19 typeid et références non triviales Pour les références non triviales, le résultat de typeid est déduit du vptr. La référence vers linstance de type_info correspondant à la vtbl peut, par exemple, être stockée dans la cellule -1: Avant daccéder au vptr, le compilateur insère du code visant à vérifier que la référence ne soit pas 0 / NULL … X& x= *(new Y()); Y:: vptr … … Y::vtbl type_info Tout cela est fait par une fonction dont le coût nest pas négligeable !

20 Construction dobjets.....La construction dun objet se fait en deux étapes distinctes : 1Allocation de lespace mémoire : Si lobjet est alloué en pile, par simple décalage du pointeur de pile. Si lobjet est alloué dans le tas (par appel à new ), par appel de l operator new de la classe, sil a été défini ou hérité, ou de l operator new global. 2Le constructeur adéquat est invoqué, ce qui peut générer une cascade dappels aux constructeurs des classes de base. Les données seront construites dans lordre des déclarations (au besoin, le compilateur les réordonne avec avertissement selon le niveau de warning). Chaque entrée dans un constructeur implique la mise à jour du vptr (si vptr il y a) : attention aux méthodes virtuelles pures ! Pour les tableaux dobjets : 1Allocation de lespace mémoire : similaire si lobjet est alloué en pile ; dans le tas, lallocation est faite par l operator new[] adéquat (de classe ou global). 2Le constructeur par défaut est invoqué de manière itérative pour initialiser chaque cellule (ce qui peut encore générer une cascade dappels aux constructeurs des classes de base, etc.)

21 Destruction dobjets.....Inversement, la destruction dun objet se fait en deux étapes inverses : 1Invocation ascendante des destructeurs (le vtpr, si vptr il y a, est mis à jour à chaque étage). 2Libération de la mémoire : Si lobjet est alloué en pile, par simple décalage du pointeur de pile. Si lobjet est alloué dans le tas (par appel à new ), par appel de l operator delete de la classe, sil a été défini ou hérité, ou de l operator delete global. Pour les tableaux dobjets, cest ric-rac. Rappel : attention aux classes polymorphes dont le destructeur nest pas virtuel !!!!!

22 operator new et operator delete..... Les operator new et delete globaux ou dune classe peuvent être redéfinis, dans le but de fournir une gestion spécifique de la mémoire : #include // à voir … void * operator new( size_t size ); void operator delete( void * ptr ); void operator delete( void * ptr, size_t size ); void * operator new[]( size_t size ); void operator delete[]( void * ptr ); void operator delete[]( void * ptr, size_t size ); La définition des versions avec taille des operator delete sont selon votre bon vouloir… En règle générale, si vous redéfinissez operator new, redéfinissez operator delete ! Exemple dapplication : allocateur mémoire rétenseur (qui ne libère jamais vraiment la mémoire), qui chaîne les blocs libérés pour les retourner à la prochaine demande dallocation.

23 Placement operator new..... Les operator new (scalaires ou de tableaux) peuvent être surchargés, le premier argument devant être de type size_t. class A { public: A( XXX, YYY ); void * operator new( size_t, WhatEver1, WhatEver2 ); void operator delete( void *, WhatEver1, WhatEver2 ); … } (idem pour les constructeurs de tableaux) De tels constructeurs sont invoqués par : new ( whatever1, whatever2 ) A( xxx, yyy ); L operator delete correspondant, sil a été défini, nest appelé automatiquement que si le constructeur lance une exception.


Télécharger ppt "Page de garde Inside C++ Maîtrise dinformatique Février 2002."

Présentations similaires


Annonces Google