6
votes

Pourquoi la taille de (D) a augmenté de 8 octets dans VS2015 lorsque j'ai dérivé D d'une base virtuelle?

J'utilise l'exemple dans C ++ 14 §3.11 / 2:

                         clang           g++         VS2015  
sizeof(long double)        16             16            8
alignof(long double)       16             16            8
sizeof(B)                  16             16            8
alignof(B)                 16             16            8
sizeof(D)                  32             32           24
alignof(D)                 16             16            8


5 commentaires

@Nathanoliver Ces structures n'ont pas de fonctions virtuelles. Est-ce que Table est créé lorsque vous dérivez virtuellement?


Demandez simplement au compilateur de vous dire, utilisez l'option / d1reporallllasslayout Compiler. Vous voyez 8 octets pour le pointeur de table de base virtuel + 1 octet pour D :: C + 7 octets Padding + 8 octets pour B :: D == 24. Le pointeur est optimisé dans le cas non virtuel.


Je pense que cette Blog Post vous aide. Dans le commentaire, Jangray, qui a écrit MSVC Compiler dans un passé, donne une explication.


@Akakatak que la publication de blog intéressante décrit un problème totalement différent (une raison de raison différente est gaspillée). Dans ce cas, il existe un pointeur fourmissible où il n'est pas clair pourquoi un pointeur à fourre-circuits serait requis. Dans ce poteau de blog, l'exigence de pointeur à fourre-mains est claire, mais le rembourrage en excès est introduit.


@Alex GCC utilise une table virtuelle en suivant le Titanium ABI. Visual C ++ utilise un pointeur interne.


3 Réponses :


0
votes

Lorsqu'il y a un objet de base virtuel , l'emplacement de l'objet de base par rapport à l'adresse d'un objet dérivé n'est pas prévisible statique. Notamment, si vous étendez votre hiérarchie de classe un peu, il devient clair qu'il peut y avoir plusieurs sous-observations d qui doivent encore faire référence à un seul objet B objet de base: xxx < / pré>

Vous pouvez obtenir un d * à partir d'un objet la plus en convertissant d'abord sur i1 ou d'abord à i2 : xxx

vous aurez d1! = d2 , c'est-à-dire qu'il y a véritablement deux d Subbjects, mais static_cast (d1) == static_cast (d2) , c'est-à-dire qu'il n'y a qu'un B subobject. Pour déterminer comment ajuster d1 et d2 pour trouver un pointeur sur le B subbject un décalage dynamique est nécessaire. Les informations sur la détermination de ce décalage doivent être stockées quelque part. Le stockage de ces informations est la source probable des 8 octets supplémentaires.

Je ne pense pas que la mise en page d'objet pour les types dans MSVC ++ est [publiquement] documentée, c'est-à-dire qu'il est impossible de raconter à coup sûr de quoi ils sont en train de faire. De l'apparence de celle-ci, ils ont incorporé un objet 64 bits pour pouvoir dire où l'objet de base vit par rapport à l'adresse de l'objet dérivé (un pointeur à certaines informations de type, un pointeur à une base, un décalage à la base, quelque chose comme ça). Les 8 autres octets proviennent très probablement de la nécessité de stocker un char plus un remplissage pour que l'objet aligné à une limite appropriée. Cela semble semblable à ce que font les deux autres compilateurs, sauf qu'ils utilisaient 16 octets pour Long Double pour commencer (probablement 10 octets rembourrés à un alignement approprié).

Pour comprendre comment le modèle d'objet C ++ pourrait fonctionner, vous voudrez peut-être consulter Stan Lippman's "à l'intérieur du modèle d'objet C ++" . C'est un peu daté mais décrit les techniques de mise en œuvre potentielles. Si MSVC ++ utilise l'un d'entre eux, je ne sais pas, mais cela donne des idées ce qui pourrait être utilisé.

pour le modèle d'objet utilisé par gcc et clang Vous pouvez consulter le Itanium ABI : Ils utilisent essentiellement l'Itanium ABI avec les ajustements mineurs à la CPU réellement utilisée.


0 commentaires

1
votes

Si vous utilisez effectivement l'aspect virtuel de l'héritage virtuel, je pense que la nécessité du pointeur de la fourgonnette devient claire. Un élément de la machine à fourres est probablement le décalage du début de B à partir du début de d .

supposer E hérite pratiquement à partir de b et f hérite des deux E et d tel que le d à l'intérieur d'un f finit ultérieurement à l'aide du B à l'intérieur du E pour sa classe de base. Dans une méthode de d qui ne sait pas qu'il s'agit d'une classe de base de f Comment trouver des membres de B sans informations stockées dans le circonscription?

SO CLANG ET G ++ a changé 8 octets de rembourrage en un pointeur à fourchettes et vous pensiez qu'il n'y avait pas de changement. Mais VS2015 n'a jamais eu ce rembourrage, il fallait donc ajouter 8 octets pour un pointeur à fourre-vtable.

Peut-être un compilateur remarque que la seule utilisation du pointeur à fourtilation est dans un schéma inefficace pour calculer le pointeur de base. Donc, peut-être que cela est optimisé dans simplement avoir un pointeur de base au lieu d'un pointeur à fourre-vtes. Mais cela ne changerait pas le besoin des 8 octets.


2 commentaires

Je pense que c'est une réponse raisonnable: SO CLIG et G ++ a modifié 8 octets de rembourrage en un pointeur à fourchettes et vous pensiez qu'il n'y avait pas de changement. Mais VS2015 n'a jamais eu ce rembourrage, il a donc besoin d'ajouter 8 octets pour un pointeur à fourre-vtable. . Merci (+1).


Je pense que la plupart des gens de voir la question penseraient que cela signifiait "pourquoi l'héritage virtuel sans fonctions virtuelles nécessite-t-elle besoin de quelque chose de prendre l'espace qu'un pointeur à usage équitable prend normalement". Je pense donc que la partie que vous voyez comme la "réponse" est pour la plupart des gens juste une clarification de la connexion entre la réponse et certains détails à l'intérieur de l'exemple de la question. Je suis content que vous ayez votre réponse, mais si vous suggérez de vous élaborer jusqu'à ce que je ne pense pas.



0
votes

Dans Visual Studio, le comportement par défaut est que toutes les structures sont alignées sur une limite de 8 octets. ieeven si vous faites xxx

, puis vérifiez Tailleof (a) , vous verrez qu'il s'agit de 8 octets.

maintenant, dans Votre cas, lorsque vous avez changé le type d'héritage de la structure D pour être virtuel, le compilateur doit faire quelque chose de plus pour accomplir cela. Premièrement, cela crée une table virtuelle pour la structure D. Qu'est-ce que ce circule contient-il? Il contient un seul pointeur sur le décalage de la structure B dans la mémoire.Next, il ajoute un VPTR à la tête de la structure D qui pointe vers la table de table nouvellement créée.

, Maintenant, la structure devrait ressembler à: xxx

Donc, la taille de D sera la suivante: xxx

Ceci est l'alignement de la limite que nous avons discuté au début vient en. Puisque toutes les structures doivent être alignées sur une limite de 8 octets et une structure D ne représente que 17 octets, le compilateur ajoute 7 octets de rembourrage à la structure pour le rendre aligné sur une limite de 8 octets.

La taille devient maintenant: xxx


1 commentaires

Dans Visual Studio, le comportement par défaut est que toutes les structures sont alignées sur une limite de 8 octets. i.e.even si vous faites structurer un {char c; }, puis cochez la taille de (a), vous verrez qu'il s'agit de 8 octets. qui ne semble pas être correct.