Cognitive polymorphes Vtable

Et polymorphisme vtable

table de fonction virtuelle et multi-état, le problème est les développeurs C + auront éventuellement à face.
Bien que depuis longtemps je n'ai pas écrit C ++, et ici il est encore enregistré bien rangé.
Informations du compilateur:

  • gcc: gcc (Debian 7.3.0-19) 7.3.0;
  • clang: 7.0.1-8 (tags / RELEASE_701 / finale).

espace de classe 1

class Empty {
public:
    Empty() = default;
    ~Empty() = default;
    void hello() { std::cout << "hello world" << std::endl; }
};
// sizeof(Empty) = 1

Nous avons d' abord besoin d' une classe claire, vide (y compris les fonctions non virtuelles), ayant une taille de 1.
Afin de pouvoir les instances de classe dans un tableau, vous devez avoir une taille de classe vide, tableau sizeof serait par ailleurs une catastrophe.
Cependant, comme la classe de base des classes vides, pour aligner chacun peut occuper 4 octets ou plus, la classe de base libre d'optimisation du compilateur.

Vide optimisation de base: Laissez le membre de données non statique, la classe de base fonction virtuelle sans réellement occupé 0 octets.

Maintenant, nous commençons l'ajout d'une fonction virtuelle, voir la taille des classes à nouveau.

class Empty {
public:
    Empty() = default;
    ~Empty() = default;
    void hello() { std::cout << "hello world" << std::endl; }
    virtual void virtual_test() {}
};
// sizeof(Empty) = 8

Après addition de fonctions virtuelles de classe, la taille augmente de 1 octet à 8 octets.
En effet , le compilateur inséré implicitement pointeur de la table de fonction virtuelle classe ( void *vptr), la taille du pointeur est de 8 octets.

A propos des choses du compilateur à faire dans le dos, il est recommandé de voir la profondeur de l'exploration << >> modèle objet C de (bien que l'air d'oublier, mais regardez un peu mieux que pas).

2 pointeur de table de fonctions virtuelles (le VPTR) et la table de fonctions virtuelles (le vtbl)

Pour la classe contient une fonction virtuelle, le compilateur crée une table de fonction virtuelle correspondante (la vtbl) de classe.
Vtable, la principale classe de stockage fonction virtuelle correspondant à l'adresse.
Lors de la compilation, le compilateur dans le constructeur, l' attribution de VPTR, valeur vtbl de l'adresse.
code de pseudo-suit en tant que:

class Empty {
public:
    Empty() {
        vtpr = (void*)&Empty::vtbl;
    }
}

Une certaine amélioration, nous modifions les classes vides sont les suivantes:

class Empty {
public:
    Empty() = default;
    virtual ~Empty() {}
    virtual void virtual_func1() {}
    virtual void virtual_func2() {}
public:
    int m1 = 0x01020304, m2 = 0x04030201;
};

int main() {
    Empty empty;
    std::cout << empty.m1 << std::endl;
    return 0;
}

L'amélioration principale est d'ajouter des variables membres m1, m2, et un certain nombre de fonctions supplémentaires (y compris des fonctions virtuelles).
Vider le gdb Voir par exemple la mise en page de mémoire, comme indiqué ci - dessous:

Vider la mémoire de mise en page par exemple

On voit sur la figure, comme un exemple de la mémoire layout vide:

  1. la VPTR (ligne rouge, pointer Vide la table virtuelle);
  2. m1, m2.

Plus de trois états appel

C ++ est un trois caractéristiques encapsulation, l' héritage et le polymorphisme, polymorphes doivent compter sur la fonction virtuelle mises en oeuvre.
Point populaires que, si le trouvé table de fonctions virtuelles (le vtbl) en appelant le pointeur de la table de fonctions virtuelles (Le VPTR) d' entrée et exécute la fonction virtuelle, le programme utilisé pour le polymorphisme.
Par exemple:

class Base {
public:
    virtual void virtual_func() {}
};

int main() {
    Base *a = new Base();
    a->virtual_func();  // 多态调用
    Base b;
    b.virtual_func();   // 非多态调用
    Base *c = &b;
    c->virtual_func();  // 多态调用
    return 0;
}

Afin de vérifier les commentaires de vue, nous utilisons le code compilé des preuves:

Code appel de fonction virtuelle assembleur

Le chiffre peut voir que trois appels virtual_func, le code d'assemblage diffèrent grandement.
La raison en est que, c appel par exemple virtual_funcen ce qui concerne par exemple b d'appel virtual_func, la nécessité de table multi-virtuelle (la vtbl) recherche l' virtual_funcentrée fonction de la procédure.

4 Disposition de la mémoire

Ci - après , respectivement , de l' héritage unique, héritage multiple, et la succession de diamant selon trois agencement de mémoire de table virtuelle ( en utilisant la g++mise en mémoire d'exportation).

4.1 héritage unique

class A
{
    int ax;
    virtual void f0() {}
};
class B : public A
{
    int bx;
    virtual void f1() {}
};
class C : public B
{
    int cx;
    void f0() override {}
    virtual void f2() {}
};

Disposition de la mémoire est la suivante:

Vtable for A
A::vtable for A: 3 entries
0     (int (*)(...))0                   // 类型转换偏移量
8     (int (*)(...))(& typeinfo for A)  // 运行时类型信息(Run-Time Type Identification,RTTI)
16    (int (*)(...))A::f0               // 虚函数f0地址

Class A
   size=16 align=8
   base size=12 base align=8
A (0x0x7f753a178960) 0
    vptr=((& A::vtable for A) + 16)

Vtable for B
B::vtable for B: 4 entries
0     (int (*)(...))0                   // 类型转换偏移量
8     (int (*)(...))(& typeinfo for B)  // 运行时类型信息(Run-Time Type Identification,RTTI)
16    (int (*)(...))A::f0               // 虚函数f0地址(未override基类函数,因此继承自A)
24    (int (*)(...))B::f1               // 虚函数f1地址

Class B
   size=16 align=8
   base size=16 base align=8
B (0x0x7f753a00e1a0) 0
    vptr=((& B::vtable for B) + 16)
  A (0x0x7f753a178a20) 0
      primary-for B (0x0x7f753a00e1a0)

Vtable for C
C::vtable for C: 5 entries
0     (int (*)(...))0                   // 类型转换偏移量
8     (int (*)(...))(& typeinfo for C)  // 运行时类型信息(Run-Time Type Identification,RTTI)
16    (int (*)(...))C::f0               // 虚函数f0地址
24    (int (*)(...))B::f1               // 虚函数f1地址(未override基类函数,因此继承自B)
32    (int (*)(...))C::f2               // 虚函数f2地址

Class C
   size=24 align=8
   base size=20 base align=8
C (0x0x7f753a00e208) 0
    vptr=((& C::vtable for C) + 16)
  B (0x0x7f753a00e270) 0
      primary-for C (0x0x7f753a00e208)
    A (0x0x7f753a178ae0) 0
        primary-for B (0x0x7f753a00e270)

Ici besoin d'être clair, Class A/B/Cil y a une table virtuelle correspondant.
table virtuelle contient principalement trois types d'informations:

  • Décalage conversion de type;
  • informations de type d'exécution (Run-Time Type d'identification, RTTI);
  • adresses de fonctions virtuelles (qui peut inclure plus d'un), les informations spécifiques voir la section des commentaires.

Plus de 4,2 héritage

class A {
    int ax;
    virtual void f0() {}
};
class B {
    int bx;
    virtual void f1() {}
};
class C : public A, public B {
    virtual void f0() override {}
    virtual void f1() override {}
};

Disposition de la mémoire de classe obtenue comme suit:

// 因为类A与类B比较简单,因此省略内存布局(可参考单继承内存布局)

Vtable for C
C::vtable for C: 7 entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for C)
16    (int (*)(...))C::f0
24    (int (*)(...))C::f1
32    (int (*)(...))-16                             // 类型转换偏移量
40    (int (*)(...))(& typeinfo for C)              // 运行时类型信息(Run-Time Type Identification,RTTI)
48    (int (*)(...))C::non-virtual thunk to C::f1()

Class C
   size=32 align=8
   base size=28 base align=8
C (0x0x7f9ce2bde310) 0
    vptr=((& C::vtable for C) + 16)
  A (0x0x7f9ce2d37ae0) 0
      primary-for C (0x0x7f9ce2bde310)
  B (0x0x7f9ce2d37b40) 16
      vptr=((& C::vtable for C) + 48)

Le code, les hérite de la classe de la classe A et C Classes B, un plus grand changement de mise en page de la mémoire se sont produits (la fin des trois lignes ajoutées).
g la mise en page de mémoire relativement obscure, est dérivée de la mémoire mise en page clang (essentiellement le même), il serait plus intuitive:

*** Dumping AST Record Layout
         0 | struct C
         0 |   struct A (primary base)
         0 |     (A vtable pointer)
         8 |     int ax
        16 |   struct B (base)
        16 |     (B vtable pointer)
        24 |     int bx
           | [sizeof=32, dsize=28, align=8,
           |  nvsize=28, nvalign=8]

Clang apparents à partir de la mise en page de la mémoire, des exemples de la classe C contient la classe de pointeur virtuel A et classe B.
En effet , A et B sont complètement indépendants, il n'y a pas de relation de séquence entre les fonctions virtuelles f0 et f1, par rapport à la classe de base ont la même quantité de décalage de position de départ.
Par conséquent, en classe C, classe A et d' information de classe B doivent être stockés dans la table virtuelle deux régions disjointes, deux pointeurs virtuels sont indexés.

                                                C Vtable (7 entities)
                                                +--------------------+
struct C                                        | offset_to_top (0)  |
object                                          +--------------------+
    0 - struct A (primary base)                 |     RTTI for C     |
    0 -   vptr_A -----------------------------> +--------------------+
    8 -   int ax                                |       C::f0()      |
   16 - struct B                                +--------------------+
   16 -   vptr_B ----------------------+        |       C::f1()      |
   24 -   int bx                       |        +--------------------+
   28 - int cx                         |        | offset_to_top (-16)|
sizeof(C): 32    align: 8              |        +--------------------+
                                       |        |     RTTI for C     |
                                       +------> +--------------------+
                                                |    Thunk C::f1()   |
                                                +--------------------+

La figure représente une image virtuelle du pointeur de comparaison, ce qui correspond au contenu de la table virtuelle.
Tout d' abord expliqué offset_to_top: la classe de base à la conversion de la classe dérivée, ce pointeur plus un décalage pour obtenir le type d'adresse réelle.
En ce qui concerne Thunk:

adresse de début (1) B & b = c dans la scène, en référence à C + 16, si un f1 d'appel direct, parce que plus de 16 ce pointeur d' erreur de décalage en octets cause,
(2) invite Thunk ce pointeur en conformité avec l'octet de décalage 16 est soustrait offset_to_top, puis en appelant la fonction f1.

explication thunk, lorsque la référence de classe de base possède une instance de classe dérivée, appelez la fonction virtuelle correspondante, sera faite en utilisant les caractéristiques polymorphes.

4.3 héritage de diamant

class A {
public:
    virtual void foo() {}
    virtual void bar() {}
private:
    int ma;
};
class B : virtual public A {
public:
    virtual void foo() override {}
private:
    int mb;
};
class C : virtual public A {
public:
    virtual void bar() override {}
private:
    int mc;
};
class D : public B, public C {
public:
    virtual void foo() override {}
    virtual void bar() override {}
};

Une classe de base pour ajouter une variable membre ma, parce que si contenu dans la variable membre classe A, la classe B / C / D dérivée sont optimisées, difficiles à comprendre.

Première vue de la mise en page de mémoire de classe de B:

*** Dumping AST Record Layout
         0 | class B
         0 |   (B vtable pointer)
         8 |   int mb
        16 |   class A (virtual base)
        16 |     (A vtable pointer)
        24 |     int ma
           | [sizeof=32, dsize=28, align=8,
           |  nvsize=12, nvalign=8]

Notez que, dans ce cas , la classe B comprend deux pointeurs factices et classe une position de départ pointeur virtuel B + 16.
Voir la structure de table virtuelle de classe B, comme suit:

Vtable for 'B' (10 entries).
   0 | vbase_offset (16)
   1 | offset_to_top (0)
   2 | B RTTI
       -- (B, 0) vtable address --
   3 | void B::foo()
   4 | vcall_offset (0)
   5 | vcall_offset (-16)
   6 | offset_to_top (-16)
   7 | B RTTI
       -- (A, 16) vtable address --
   8 | void B::foo()
       [this adjustment: 0 non-virtual, -24 vcall offset offset]
   9 | void A::bar()

A ce moment, la tête augmente la table virtuelle vbase_offset, car au moment de la compilation, la classe de base ne peut pas être quantité de décalage déterminée A de la mémoire de classe B, il est nécessaire d'ajouter table virtuelle vbase_offset, étiqueté Une mémoire d'exécution de classe de base de classe B en position.
En outre, deux ajouter table virtuelle vcall_offset, qui est une fonction virtuelle pour répondre lorsqu'un exemple de référence B en utilisant la classe de base virtuelle appelant la classe A, chaque fonction virtuelle peut être différente par rapport au décalage de ce pointeur, il est nécessaire dans l' enregistrement vcall_offset dans.

  • vcall_offset (0): correspond à la barre de A ();
  • vcall_offset (-16): correspondant à B :: foo ().

Par conséquent, lorsque l'appelant un exemple de référence B A de la fonction bar, parce que ce pointeur vptr_a, donc aucun réglage, l'appel B :: foo (), B est en surcharge ainsi la fonction foo, il est nécessaire d'ajuster ce pointeur pointant vers vptr_b.

Voir la mise en page de la mémoire de la classe D:

*** Dumping AST Record Layout
         0 | class D
         0 |   class B (primary base)
         0 |     (B vtable pointer)
         8 |     int mb
        16 |   class C (base)
        16 |     (C vtable pointer)
        24 |     int mc
        32 |   class A (virtual base)
        32 |     (A vtable pointer)
        40 |     int ma
           | [sizeof=48, dsize=44, align=8,
           |  nvsize=28, nvalign=8]

Dans ce cas, notez qu'en raison de l'utilisation héritage virtuel, de sorte que seule la classe A, un total de pointeur virtuel en trois catégories D.
Table des matières virtuelles relativement complexe, mais peut se référer à la classe de base B parse la table virtuelle, comme indiqué ci - dessous:

Vtable for 'D' (15 entries).
   0 | vbase_offset (32)
   1 | offset_to_top (0)
   2 | D RTTI
       -- (B, 0) vtable address --
       -- (D, 0) vtable address --
   3 | void D::foo()
   4 | void D::bar()
   5 | vbase_offset (16)
   6 | offset_to_top (-16)
   7 | D RTTI
       -- (C, 16) vtable address --
   8 | void D::bar()
       [this adjustment: -16 non-virtual]
   9 | vcall_offset (-32)
  10 | vcall_offset (-32)
  11 | offset_to_top (-32)
  12 | D RTTI
       -- (A, 32) vtable address --
  13 | void D::foo()
       [this adjustment: 0 non-virtual, -24 vcall offset offset]
  14 | void D::bar()
       [this adjustment: 0 non-virtual, -32 vcall offset offset]

5 expansion

C ++ tables virtuelles, et le modèle de mémoire d'exécution est une question très complexe, dans le processus de préparation est constamment rafraîchir leurs connaissances.
Voici quelques modes de réalisation, les objets du modèle de mémoire de vidage de la mémoire, et le type de structure de la table virtuelle.

Utilisez le compilateur clang: clang++ -cc1 -emit-llvm -fdump-record-layouts -fdump-vtable-layouts main.cpp.

Utilisez compilateur gcc:

g++ -fdump-class-hierarchy -c main.cpp
// g++ dump的内容比较晦涩,因此需要使用c++ filt导出具有可读性的文档
cat [g++导出的文档] | c++filt -n > [具有一定可读性的输出文档]

section Disposition de la mémoire ici, la référence à: https://zhuanlan.zhihu.com/p/41309205 article.

PS:
Si vous pensez que mon article utile pour vous, s'il vous plaît attention à mon numéro public micro-canal, merci!
Les programmeurs route Daguai

Je suppose que tu aimes

Origine www.cnblogs.com/jason1990/p/12585744.html
conseillé
Classement