Pourquoi le stockage d'une base vide en double ne se chevauche-t-il pas avec un pointeur vtable?


11

Considérez cet exemple:

#include <iostream>

int main()
{
    struct A {};
    struct B : A {};
    struct C : A, B {};

    std::cout << sizeof(A) << '\n'; // 1
    std::cout << sizeof(B) << '\n'; // 1
    std::cout << sizeof(C) << '\n'; // 2, because of a duplicate base

    struct E : A {virtual ~E() {}};
    struct F : A, B {virtual ~F() {}};

    std::cout << sizeof(E) << '\n'; // 8, the base overlaps the vtable pointer
    std::cout << sizeof(F) << '\n'; // 16, but why?
}

(courir sur godbolt)

Ici, vous pouvez voir que struct Ela classe de base vide (qui fait 1 octet de large) utilise le même stockage que le pointeur vtable, comme prévu.

Mais pour struct F, qui a une base vide en double, cela ne se produit pas. Qu'est-ce qui cause cela?

J'obtiens le même résultat sur GCC, Clang et MSVC. Les résultats ci-dessus sont donc pour x64 sizeof(void *) == 8.


Fait intéressant, pour struct G : A, B {void *ptr;};GCC et Clang, effectuez EBO (la taille est de 8), mais pas MSVC (la taille est de 16).


3
Étrangement, en héritant de C(qui héritent de A, B) vous obtenez un résultat différent de héritant forme Aet Bdirectement
Guillaume Racicot

1
J'ai aimé faire des recherches sur celui-ci. Merci pour la question et le lien. Je ne suis pas sûr d'avoir une réponse et je vais donc simplement commenter. Se pourrait-il que cela découle de l'ambiguïté introduite par la dérivation de Cet F? Après tout, 2 * sizeof(void*) == 16sur x86_64 comme vous l'avez dit. Le compilateur ne peut pas entièrement optimiser (comme l'a dit Story Teller) et ne le fait pas.
Andrew Falanga,

2
Il est normal que vous obteniez le même résultat sur gcc et clang, car ils suivent tous les deux l'itanium ABI. Et si c'est le cas je pense, lors de la définition de l'ABI, ils avaient peur que l'algorithme de mise en page devienne trop cher, alors ils ont pris quelques raccourcis (aka pessimisations).
Marc Glisse

2
@RianQuinn Une base en double ne rend pas une structure invalide.
HolyBlackCat

1
@RianQuinn héritant de la même classe plusieurs fois via différents "chemins" est parfaitement valide en C ++. Si vous voulez créer une structure en losange, c'est-à-dire que vous n'avez la classe de base qu'une seule fois, vous devez utiliser l'héritage virtuel. Mais si vous ne voulez pas de diamant et que la classe de base en double n'est pas un problème pour vous, ce n'est pas un problème pour la langue non plus. Le code OP ne produit qu'un avertissement, indiquant que le second A, hérité via B, n'est pas accessible. C'est bon. Ce n'est que si vous essayez réellement d'y accéder, comme dans votre exemple, que vous obtenez une erreur.
sebrockm

Réponses:


4

Parce que le compilateur ajoute un remplissage d'un octet après la structure A

F {vptr (8) + 0 membres de A + 1 padding (car A est vide) +0 de b} = 9 puis le compilateur ajoute 7 octets de padding pour aligner le stockage de la structure;

E {vptr (8) + 0 membres pour A} = 8 Aucun remplissage requis

de Microsoft

Chaque objet de données a une exigence d'alignement. Pour les structures, l'exigence est la plus importante de ses membres. Chaque objet se voit attribuer un décalage de sorte que le décalage% alignement-requis == 0

https://docs.microsoft.com/en-us/cpp/c-language/storage-and-alignment-of-structures?view=vs-2019

ÉDITER:

voici ma démo:

int main()
{
    C c;
    A* a = &c;
    B* b = &c;

    std::cout << sizeof(A) << " " << a << '\n'; 
    std::cout << sizeof(B) << " " << b << '\n'; 
    std::cout << sizeof(C) << " " << &c << '\n'; 

    E e;
    a = &e;
    std::cout << sizeof(E) <<" " << &e << " " << a << '\n'; 

    F f;
    a = &f;
    b = &f;
    std::cout << sizeof(F) << " " << &f << " " << a << " " << b << '\n';

}

production:

1 0000007A45B7FBB4
1 0000007A45B7FBB5
1 0000007A45B7FBB4
8 0000007A45B7FC18 0000007A45B7FC20
16 0000007A45B7FC38 0000007A45B7FC40 0000007A45B7FC41

comme vous pouvez le voir, a & b ne se chevauchent jamais et avec vptr sur l'héritage multiple, chacun a sa propre valeur de pointeur

note compilée par VC2019 x64 build


Je ne pense pas que cela fonctionne ainsi. Même s'il An'a pas de membres, il occupe toujours 1 octet (qui peut être partagé avec un autre objet). Dans E, Ane se trouve pas après le vptr; il chevauche le premier octet de vptr. (Voici une démo ; j'ai légèrement modifié le code pour le rendre Aaccessible.). Même chose se produit pour la première Aen F. Puisque A(et B) peut être placé au-dessus du vptr, je ne sais pas pourquoi cela ne se produit pas B.
HolyBlackCat

@HolyBlackCat mais c'est ce qui s'est passé vérifier le code de test
Ahmed Anter

Aha, donc MSVC se comporte différemment de GCC / Clang ici; ce n'est pas assez intelligent pour mettre Aau dessus du vptr. Cela peut expliquer pourquoi la sortie est 16 sur MSVC, mais je ne suis pas sûr de ce qui se passe avec GCC et Clang.
HolyBlackCat
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.