[Kos-dev] Galere

d2 kos-dev@enix.org
17 Jan 2003 17:45:27 +0100


--=-=-=
Content-Type: text/plain; charset=iso-8859-15
Content-Transfer-Encoding: 8bit


Hello,

>>>>> "Thomas" == Thomas Petazzoni <thomas.petazzoni@enix.org> writes:
    Thomas> En fait, j'ai regarde le code de libsupc++, et ca
    Thomas> ressemble pas du tout a ce qu'on a fait jusqu'a maintenant
    Thomas> au niveau de libcxxrt, donc je me posais des
    Thomas> questions. Quelle est la difference entre les operator new
    Thomas> (de libsupc++) et les __builtin_new que nous on definit ?

Ah ok, c'est ca votre "probleme" ! Ah ben alors tout va bien, car il
n'y en a plus, alors, de probleme ! Magique ? Non non...

Bon, alors reprenons. Avec gcc 2.x (en particulier 2.9x), le runtime
c++ est defini un peu partout et n'importe comment : les operators
new/delete/new[]/delete[] et tout le reste s'appellent
__builtin_new/... et donc ne correspondent pas aux noms "operator new"
d'un name mangling des familles. Bref, avec gcc 2.x, y'a pas de
runtime C++ bien propre bien isole, et les noms sont foireux. Ceci
dit, __builtin_new/... correspondent aux operateurs new/... (je pense
qu'il doit y avoir possibilite de definir des operator
new/delete/... ; mais dans ce cas la, je sais pas comment gcc reagit
vis a vis des __builtin_* ; mais honnetement nous on s'en fout un peu
de ca : __builtin_new/delete/... ca nous suffit point final).

Est arrive gcc 3.x. Avec lui, le runtime C++ est mieux isole, et se
trouve dans libsupc++ (sauf pour les parties liees aux exceptions, mais
on a -fno-exceptions). La, cette fois-ci, l'operator new s'appelle
vraiment (vrai mangling) "operator new". Meme chose pour delete et
cie.

Bref, pour gcc 2.9x, le runtime C++ utilise les fonctions
__builtin_*. Pour gcc 3.x, le runtime C++ utilise un equivalent de
libsupc++ ("operator new"...). Ce sont deux choses differentes, et on
utilise soit l'une, soit l'autre, suivant la version du compilo (mais
pas les 2 a la fois, ca sert a rien).

    Thomas> Enfin si d'apres toi, les __builtin_new, __builtin_delete
    Thomas> & co ca suffit, bin allons y ;-)

Pour gcc 2.x oui, pour gcc 3.x non. C'est pour ca que :

  ls perso/devel/kos/kos/modules/libcxxrt                        1217
  CVS/  Makefile  gcc-2.95.cc  gcc-2.96.cc  gcc-2.96.o  gcc-3.cc  libcxxrt.ro

Et a l'avenir je mettrai tout dans un seul .cc avec les
  #if __GNUC__ == ...
qui vont bien.

    Thomas> reinterpret_cast <std::size_t *> (base)[-1] =
    THOMAS> tableau d'objets. Bon par contre le reinterpret_cast,
    Thomas> j'avoue que j'ai un peu du mal ... Mais enfin bon.

Tu consideres que c'est un cast comme en C (ie, il s'agit de faire
comme ((size_t*)base)[-1] = ...). La difference entre static_cast et
reinterpret_cast est que static_cast sur un pointeur est un cast
controlé par le compilo qui tient compte des relations d'heritage,
avec recalcul d'adresse possible ; reinterprete_cast fait 1000000%
confiance au programmeur et n'effectue aucun recalcul d'adresse.

En gros, naturellement en C++ on peut faire du upcast sans precaution
(si class B : public class A, on peut passer un B *b a une fonction
qui prend en parametre A*). Mais pour faire du downcast (dire que
l'objet A* a qu'on a sous les yeux est en realite un objet de type B*)
on a le choix entre 2 mecanismes :
  - dynamic_cast (RTTI necessaire) qui verifie la validite du
    (down)cast a la compilation (si possible) ou en run-time (sinon),
  - static_cast (pas besoin de RTTI) : qui ne verifie que la validite
    semantique du cast (on verifie qu'on *sait* caster a en B* ; on ne
    peut pas garantir que a sera forcement un objet de type B* valide)

Dans ces 2 cas, le static/dynamic_cast *peut* generer un objet qui a
une adresse differente (simple translation) de l'objet en parametre :
c'est le cas si le sous-objet de type A dans B est decale par rapport
a l'adresse de debut de B (inevitable dans le cas de l'heritage
multiple). Ces 2 mecanismes ne s'appliquent que la ou un cast
"implicite" est possible (relation d'heritage up/down, ou regles
internes au compilo [genre double/int...]). Bref, ce qu'il faut
retenir, c'est que dans les cas ou le compilo les accepte (ie cast
implicite possible), ces 2 mecanismes *peuvent* induire une
translation (au sens maths, ce n'est pas un anglicisme) d'adresse.

Or dans ton exemple il ne doit pas y avoir recalcul d'adresse (on veut
y aller bourrin a la C) => reinterpret_cast est la pour ca. Ca veut
dire : a l'adresse precise que j'indique, il y'a le debut d'un objet
de tel type, merci de ne pas refaire de calcul d'adresse avant de
pointer dessus.

Exemple :

--=-=-=
Content-Disposition: attachment; filename=a.cc

#include "stdio.h"

class A {
  public:
    char a[128];
};

class B {
  public:
    char b[256];
};

class C : public A, public B {
  public:
    char c[512];
};

int main()
{
  B b, *pb;
  C c, *pc;

  printf("&c : %p\n", &c);
  pb = dynamic_cast<B*>(&c);
  printf("dyn<B>(&c) : %p\n", pb);
  pb = static_cast<B*>(&c);
  printf("static<B>(&c) : %p\n", pb);
  pb = reinterpret_cast<B*>(&c);
  printf("reint<B>(&c) : %p\n", pb);

  return 0;
}


--=-=-=


La classe A est la juste pour decaler le debut du sous objet B dans C
(heritage multiple). On voit qu'effectivement, le debut de l'objet de
type B contenu dans c est decale de la taille de la classe A quand on
utilise les mecanismes propres de downcast :
  &c : ffbee238
  dyn<B>(&c) : ffbee2b8
  static<B>(&c) : ffbee2b8
Par contre, pour le mecanisme de cast brutos (reinterpret_cast), on
force l'adresse de l'objet de type B considere a etre le debut de c
(ie on ecrase le sous-objet A de c) :
  reint<B>(&c) : ffbee238

Pour finir, le compilo c++ interdira qu'on fasse un static_cast (et
meme un dynamic_cast quand il sait statiquement les types exacts de
tous les objets) d'un objet de type A vers un objet de type B (pas de
cast implicite puisqu'aucune relation d'heritage up/down). Il ne
ralera pas si on le force via reinterpret_cast.

Est-ce qu'on comprend qqch ?...

Bonne soiree,

-- 
d2

--=-=-=--