53 votes

Quelle est la première entrée de (int (*)(...))0 vtable dans la sortie de g++ -fdump-class-hierarchy ?

Pour ce code :

class B1{
public:  
  virtual void f1() {}  
};

class D : public B1 {
public:
  void f1() {}
};

int main () {
    B1 *b1 = new B1();
    D  *d  = new D();

    return 0;
}

Après la compilation, la vtable que j'obtiens avec g++ -fdump-class-hierarchy est :

Vtable for B1
B1::_ZTV2B1: 3u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI2B1)
16    B1::f1

Vtable for D
D::_ZTV1D: 3u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1D)
16    D::f1

Je n'ai pas compris ce que font les entrées comme (int ( )(...))0* correspondent. Bien sûr, cela signifie quelque chose comme, c'est une fonction qui renvoie un int et prend un nombre illimité d'arguments, je ne comprends rien de plus. A quelle fonction correspond ce pointeur de fonction ? et comment le savez-vous ? Ma machine est une machine 64 bits.

Le deuxième pointeur de fonction a une adresse associée à la fin ? ?? A qui cela correspond-il ?

EDITAR

Le compilateur que j'utilise est g++ :

g++ -v
Using built-in specs.
Target: x86_64-suse-linux
Configured with: ../configure --prefix=/usr --infodir=/usr/share/info --mandir=/usr/share/man --libdir=/usr/lib64 --libexecdir=/usr/lib64 --enable-languages=c,c++,objc,fortran,obj-c++,java,ada --enable-checking=release --with-gxx-include-dir=/usr/include/c++/4.4 --enable-ssp --disable-libssp --with-bugurl=http://bugs.opensuse.org/ --with-pkgversion='SUSE Linux' --disable-libgcj --disable-libmudflap --with-slibdir=/lib64 --with-system-zlib --enable-__cxa_atexit --enable-libstdcxx-allocator=new --disable-libstdcxx-pch --enable-version-specific-runtime-libs --program-suffix=-4.4 --enable-linux-futex --without-system-libunwind --with-arch-32=i586 --with-tune=generic --build=x86_64-suse-linux
Thread model: posix
*gcc version 4.4.1 [gcc-4_4-branch revision 150839] (SUSE Linux)*

0 votes

Je pense qu'il s'agit des constructeurs/destructeurs... Avez-vous essayé d'en ajouter pour voir si cela fait une différence ?

1 votes

@forsvarir , @Pixie : J'ai ajouté les constructeurs, cela n'a fait aucune différence, BTW, il est dit que seules les fonctions qui ont un mot clé virtuel attaché, obtiennent une entrée dans la vtable.

0 votes

Cela dépend fortement de la plate-forme. Quel compilateur utilisez-vous ?

56voto

Ce sont les pointeurs offset-to-top (nécessaires pour l'héritage multiple) et typeinfo (RTTI).

De la Itanium ABI (vous n'utilisez pas le compilateur Itanium, mais leur description est vraiment bonne) :

El décalage vers le haut contient le déplacement vers le haut de l'objet à partir de l'emplacement dans l'objet du pointeur de table virtuelle qui adresse cette table virtuelle, sous la forme d'un ptrdiff_t. Il est toujours présent. Le décalage fournit un moyen de trouver le sommet de l'objet à partir de n'importe quel sous-objet de base avec un pointeur de table virtuelle. Ceci est nécessaire pour dynamic_cast en particulier.
(Dans une table virtuelle d'objets complète, et donc dans toutes ses tables virtuelles de base primaires, la valeur de ce décalage sera nulle. [...])

El pointeur de typeinfo pointe vers l'objet typeinfo utilisé pour le RTTI. Il est toujours présent. Toutes les entrées de chacune des tables virtuelles d'une classe donnée doivent pointer vers le même objet typeinfo. Une implémentation correcte de l'égalité de typeinfo consiste à vérifier l'égalité des pointeurs, sauf pour les pointeurs (directement ou indirectement) vers des types incomplets. Le pointeur de typeinfo est un pointeur valide pour les classes polymorphes, c'est-à-dire celles qui ont des fonctions virtuelles, et est nul pour les classes non polymorphes.


Le décalage par rapport au sommet plus en détail (sur demande)

Disons que vous avez une classe dérivée D qui dérive d'une classe de base, B1 . Que se passe-t-il lorsque vous essayez de lancer un D instance au type B1 ? Puisque les fonctions qui prennent un B1 l'objet ne sait rien sur D qui fait partie de la D vtable doit également être un B1 vtable. C'est assez facile - il suffit de faire en sorte que le début de l'élément D vtable ressemble à une B1 vtable, et ajouter toutes les entrées supplémentaires dont nous avons besoin après cela. Les fonctions qui attendent un B1 seront heureux, car ils n'utiliseront aucune partie de la vtable au-delà de ce qu'ils attendent d'une B1 .

Cependant, que se passe-t-il si D maintenant également provient de B2 ? Le pointeur sur le D vtable ne peut pas être les deux un valide B1 vtable y un valide B2 vtable ! Le compilateur résout ce problème en ajoutant un fichier séparé B2 vtable à la fin de notre combinaison D/B1 et ajuste manuellement le pointeur de table virtuelle lorsque nous essayons d'effectuer un transfert à partir d'une table virtuelle. D à un B2 .

Cependant, cela conduit à un nouveau problème - que se passe-t-il lorsque nous essayons de lancer arrière d'un B2 à un D ? Le compilateur ne peut pas simplement ajuster le pointeur de la table virtuelle en arrière de la même quantité qu'il a ajusté le pointeur précédemment, parce qu'il n'a pas réellement connaître pour être sûr que le B2 que nous lui donnons est de type D ! En particulier, dynamic_cast<D>() doit être capable de dire si notre objet est ou n'est pas de type D . Pour cela, il doit accéder à la RTTI de l'objet, et pour que il a besoin de savoir où se trouve le début de la vtable de l'objet original. C'est le but de la valeur offset-to-top - elle nous donne le décalage vers le début de la vtable de l'objet original, nous obtenons le RTTI de notre objet, et le dieu vengeur du C++ permet à nos cultures de pousser pour une autre saison.

Cette page propose quelques bons exemples de mises en page vtable (sous la rubrique Tableau 1c ). Notez qu'ils sont légèrement plus compliqués en raison de l'utilisation de héritage virtuel qui ajoute un décalage supplémentaire à la table virtuelle de chaque classe enfant.

0 votes

Je n'ai pas compris cette citation "Le décalage vers le haut indique le déplacement vers le haut de l'objet à partir de l'emplacement dans l'objet du pointeur de table virtuelle qui adresse cette table virtuelle".

1 votes

J'ai appuyé sur entrée par erreur ^, je voulais dire, où dans cette vtable se trouve un pointeur vers la fonction f1 () ? L'explication que vous avez donnée n'est toujours pas claire pour moi, voyez si vous pouvez l'expliquer dans un langage plus simple.

3 votes

@Anisha Kaul : Eh bien, c'est à peu près ce qui est dit dans votre question : le pointeur vers f1 est stocké comme le troisième élément de la vtable dans cette implémentation particulière : 16 B1::f1 .

4voto

Michael Points 16659

Peut-être que la première entrée est pour un destructeur virtuel et la seconde pour le support RTTI ? Mais ce n'est qu'une supposition.

1 votes

La deuxième entrée ressemble effectivement à un support RTTI, mais le destructeur n'est pas virtuel pour ces classes.

1voto

daniglezad Points 69

Je pense que les réponses citant l'ABI de l'Itanium sont trop lourdes à comprendre.

Je pense que cet article de la Ruhr-Universität Bochum et d'autres ( https://www.syssec.ruhr-uni-bochum.de/media/emma/veroeffentlichungen/2019/10/02/ACSAC19-VPS.pdf ) décrit RTTI et Offset-To-Top d'une manière plus conviviale.

Extrait du document lui-même :

enter image description here

RTTI contient un pointeur sur les informations de type de la classe. Entre autres choses, ces informations de type contiennent le nom de la classe et de ses classes de base. Cependant, le RTTI est facultatif et souvent omis par le compilateur. Il n'est nécessaire que lorsque le programmeur utilise, par exemple, dynamic_cast ou type_info. Par conséquent, une analyse statique fiable ne peut pas s'appuyer sur cette information. Les classes qui ne contiennent pas de RTTI ont le champ RTTI mis à zéro.

Offset-to-Top est nécessaire quand une classe utilise l'héritage multiple (donc a une vtable de base et une ou plusieurs sous-vtables) comme la classe C le fait. Offset-to-Top spécifie la distance entre la vtblptr d'une sous-table et la vtblptr de base au début de l'objet. Dans notre exemple, la vtblptr de la sous-table de la classe C réside à l'offset 0x10 dans l'objet, tandis que la vtblptr de la vtable de base réside à l'offset 0x0. Par conséquent, la distance entre les deux, telle que stockée dans le champ Offset-to-Top de la sous-table C, est de 0x10. Offset-to-Top est 0 si la vtable est la vtable de base de la classe ou si aucun héritage multiple n'est utilisé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X