web-dev-qa-db-fra.com

disposition de la mémoire, objets C ++

Je me demande essentiellement comment C++ dispose l'objet en mémoire. Donc, j'entends que les lancers dynamiques ajustent simplement le pointeur de l'objet en mémoire avec un décalage; et réinterpréter en quelque sorte nous permet de faire quoi que ce soit avec ce pointeur. Je ne comprends pas vraiment ça. Des détails seraient appréciés!

40
rsinha

Chaque classe présente ses membres de données dans l'ordre de déclaration.
Le compilateur est autorisé à placer un remplissage entre les membres pour rendre l'accès efficace (mais il n'est pas autorisé à le réorganiser).

Comment dynamic_cast<> works est un détail d'implémentation du compilateur et non défini par la norme. Tout dépendra de l'ABI utilisé par le compilateur.

reinterpret_cast<> fonctionne en changeant simplement le type de l'objet. La seule chose que vous pouvez garantir que cela fonctionne, c'est que le fait de placer un pointeur sur un vide * et de revenir sur le même pointeur vers la classe vous donnera le même pointeur.

13
Martin York

La disposition de la mémoire est principalement laissée à l'implémentation. La principale exception est que les variables membres pour un spécificateur d'accès donné seront dans l'ordre de leur déclaration.

§ 9.2.14

Les membres de données non statiques d'une classe (non-union) avec le même contrôle d'accès (article 11) sont alloués afin que les membres ultérieurs aient des adresses plus élevées dans un objet de classe. L'ordre d'allocation des membres de données non statiques avec un contrôle d'accès différent n'est pas spécifié (11). Les exigences d'alignement de la mise en œuvre peuvent empêcher deux membres adjacents d'être alloués immédiatement l'un après l'autre; les exigences d'espace pour la gestion des fonctions virtuelles (10.3) et des classes de base virtuelles (10.1) le pourraient aussi.

Outre les variables membres, une classe ou une structure doit fournir de l'espace pour les variables membres, les sous-objets des classes de base, la gestion des fonctions virtuelles (par exemple une table virtuelle), le remplissage et l'alignement de ces données. Cela dépend de l'implémentation, mais la spécification Itanium ABI est un choix populaire. gcc et clang y adhèrent (au moins dans une certaine mesure).

http://mentorembedded.github.io/cxx-abi/abi.html#layout

L'Itanium ABI ne fait bien sûr pas partie de la norme C++ et n'est pas contraignant. Pour obtenir plus de détails, vous devez vous tourner vers la documentation et les outils de votre implémenteur. clang fournit un outil pour afficher la disposition de la mémoire des classes. À titre d'exemple, les éléments suivants:

class VBase {
    virtual void corge();
    int j;
};

class SBase1 {
    virtual void grault();
    int k;
};

class SBase2 {
    virtual void grault();
    int k;
};

class SBase3 {
    void grault();
    int k;
};

class Class : public SBase1, SBase2, SBase3, virtual VBase {
public:
    void bar();
    virtual void baz();
    // virtual member function templates not allowed, thinking about memory
    // layout and vtables will tell you why
    // template<typename T>
    // virtual void quux();
private:
    int i;
    char c;
public:
    float f;
private:
    double d;
public:
    short s;
};

class Derived : public Class {
    virtual void qux();
};

int main() {
    return sizeof(Derived);
}

Après avoir créé un fichier source qui utilise la disposition mémoire de la classe, clang révélera la disposition mémoire.

$ clang -cc1 -fdump-record-layouts layout.cpp

La disposition pour Class:

*** Dumping AST Record Layout
   0 | class Class
   0 |   class SBase1 (primary base)
   0 |     (SBase1 vtable pointer)
   8 |     int k
  16 |   class SBase2 (base)
  16 |     (SBase2 vtable pointer)
  24 |     int k
  28 |   class SBase3 (base)
  28 |     int k
  32 |   int i
  36 |   char c
  40 |   float f
  48 |   double d
  56 |   short s
  64 |   class VBase (virtual base)
  64 |     (VBase vtable pointer)
  72 |     int j
     | [sizeof=80, dsize=76, align=8
     |  nvsize=58, nvalign=8]

Pour en savoir plus sur cette fonctionnalité, cliquez sur le blog d'Eli Bendersky:

http://eli.thegreenplace.net/2012/12/17/dumping-a-c-objects-memory-layout-with-clang/

gcc fournit un outil similaire, `-fdump-class-hierarchy '. Pour la classe donnée ci-dessus, il imprime (entre autres):

Class Class
   size=80 align=8
   base size=58 base align=8
Class (0x0x141f81280) 0
    vptridx=0u vptr=((& Class::_ZTV5Class) + 24u)
  SBase1 (0x0x141f78840) 0
      primary-for Class (0x0x141f81280)
  SBase2 (0x0x141f788a0) 16
      vptr=((& Class::_ZTV5Class) + 56u)
  SBase3 (0x0x141f78900) 28
  VBase (0x0x141f78960) 64 virtual
      vptridx=8u vbaseoffset=-24 vptr=((& Class::_ZTV5Class) + 88u)

Il ne détaille pas les variables membres (ou du moins je ne sais pas comment y arriver) mais vous pouvez dire qu'elles devraient être comprises entre le décalage 28 et 64, tout comme dans la disposition des clangs.

Vous pouvez voir qu'une classe de base est désignée comme primary. Cela supprime la nécessité d'ajuster le pointeur this lorsque Class est accessible en tant que SBase1.

L'équivalent pour gcc est:

$ g++ -fdump-class-hierarchy -c layout.cpp

L'équivalent pour Visual C++ est:

cl main.cpp /c /d1reportSingleClassLayoutTest_A

voir: https://blogs.msdn.Microsoft.com/vcblog/2007/05/17/diagnosing-hidden-odr-violations-in-visual-c-and-fixing-lnk2022/

19
Praxeolitic

Comme indiqué précédemment, les détails complets sont compliqués, pénibles à lire et vraiment utiles uniquement aux développeurs de compilateurs, et varient selon les compilateurs. Fondamentalement, chaque objet contient les éléments suivants (généralement présentés dans cet ordre):

  1. Informations sur le type d'exécution
  2. Objets de base non virtuels et leurs données (probablement par ordre de déclaration).
  3. Variables membres
  4. Objets de base virtuels et leurs données (probablement dans un certain ordre de recherche d'arborescence DFS).

Ces éléments de données peuvent ou non être remplis pour faciliter l'alignement de la mémoire, etc. Les informations sur le type d'exécution sont masquées dans le type, les tables virtuelles pour les classes parentales virtuelles, etc., toutes spécifiques au compilateur.

En ce qui concerne les lancers, reinterpret_cast modifie simplement le type de données C++ du pointeur et ne fait rien d'autre, il vaut donc mieux être sûr de savoir ce que vous faites lorsque vous l'utilisez, sinon vous risquez de gâcher les choses. dynamic_cast fait à peu près la même chose que static_cast (en modifiant le pointeur), sauf qu'il utilise les informations de type d'exécution pour déterminer s'il peut effectuer une conversion vers le type donné et comment le faire. Encore une fois, tout cela est spécifique au compilateur. Notez que vous ne pouvez pas dynamic_cast une void* car il doit savoir où trouver les informations sur le type d'exécution afin de pouvoir effectuer toutes ses merveilleuses vérifications d'exécution.

4
Jason E

La réponse est "c'est compliqué". La diffusion dynamique ne règle pas simplement les pointeurs avec un décalage; il peut en fait récupérer des pointeurs internes à l'intérieur de l'objet afin de faire son travail. GCC suit un ABI conçu pour Itanium mais implémenté plus largement. Vous pouvez trouver les détails sanglants ici: Itanium C++ ABI .

4
Hyman Rosen

cette question est déjà répondue à http://dieharddeveloper.blogspot.in/2013/07/c-memory-layout-and-process-image.html voici un extrait de là: Au milieu de l'espace d'adressage du processus, une région est réservée aux objets partagés. Lorsqu'un nouveau processus est créé, le gestionnaire de processus mappe d'abord les deux segments de l'exécutable en mémoire. Il décode ensuite l'en-tête ELF du programme. Si l'en-tête du programme indique que l'exécutable était lié à une bibliothèque partagée, le gestionnaire de processus (PM) extrait le nom de l'interpréteur dynamique de l'en-tête du programme. L'interpréteur dynamique pointe vers une bibliothèque partagée qui contient le code de l'éditeur de liens d'exécution.

1
cppminds