Tout ce qui suit sera fait sur GCC 9.1 en utilisant Explorateur du compilateur , en x86-64, en utilisant -O3
.
J'ai ce code:
struct Base {
Base() {}
double foo;
int bar;
};
struct Derived : public Base {
int baz;
};
int main(int argc, char** argv)
{
return sizeof(Derived);
}
Il renvoie correctement 16
, Comme je m'y attendais, 8 octets pour foo
, et 4 octets pour bar
et 4 octets pour baz
. Cela ne fonctionne que parce que Derived
hérite de Base
et qu'il n'a donc pas besoin de remplir après bar
car Derived
étant un seul type contenant à la fois Base
et Derived
éléments.
J'ai deux questions, comme ci-dessous:
Première question
Si je supprime le constructeur explicite de Base() {}
, il commence à renvoyer 24
, Au lieu de 16
. c'est-à-dire qu'il ajoute un remplissage après bar
et baz
.
Je ne peux pas expliquer pourquoi avoir un constructeur par défaut explicite est différent d'avoir un constructeur implicite par défaut.
Deuxième question
Si je change ensuite struct
en class
pour Base
, cela revient à retourner 16
. Je ne peux pas l'expliquer non plus. Pourquoi les modificateurs d'accès changeraient-ils la taille de la structure?
Tout se résume à savoir si votre type est un agrégat ou non. Avec
struct Base {
Base() {}
double foo;
int bar;
};
struct Derived : public Base {
int baz;
};
Base
n'est pas un agrégat à cause du constructeur. Lorsque vous supprimez le constructeur, vous créez Base
un agrégat qui, par l'ajout d'un constructeur par défaut à une classe de base modifie sizeof () un type dérivé , signifie que gcc ne sera pas "optimisé" pour l'espace et l'objet dérivé n'utilisera pas le rembourrage arrière de la base.
Lorsque vous modifiez le code en
class Base {
double foo;
int bar;
};
struct Derived : public Base {
int baz;
};
foo
et bar
sont désormais privés (parce que les classes ont une accessibilité privée par défaut), ce qui signifie que Base
n'est plus un agrégat car les agrégats ne sont pas autorisés à avoir des membres privés. Cela signifie que nous revenons au fonctionnement du premier cas.
Avec votre classe Base, vous obtiendrez 4 octets de remplissage de queue, et la même chose avec la classe Derived, c'est pourquoi il devrait normalement être 24 bytes
total pour la taille de Derived
.
Il devient 16 octets, car votre compilateur est capable de faire réutilisation du remplissage de la queue .
Cependant, la réutilisation du rembourrage de la queue est problématique avec POD
types (tous les membres sont publics, constructeur par défaut, etc ...), car il casse les hypothèses courantes qu'un programmeur ferait. (Donc, fondamentalement, tout compilateur sensé ne fera pas de réutilisation du rembourrage de queue pour les types de pod)
Imaginons que les compilateurs utilisent le tail padding reuse
pour les types de POD:
struct Base {
double foo;
int bar;
};
struct Derived : Base {
int baz;
};
int main(int argc, char** argv)
{
// if your compiler would reuse the tail padding then the sizes would be:
// sizeof(Base) == 16
// sizeof(Derived) == 16
Derived d;
d.baz = 12;
// trying to zero *only* the members of the base class,
// but this would zero also baz from derived, not very intuitive
memset((Base*)&d, 0, sizeof(Base));
printf("%d", d.baz); // d.baz would now be 0!
}
Lors de l'ajout d'un constructeur explicite à la classe Base ou en changeant les mots clés struct
en class
, la classe Derived
ne satisfait plus la définition du POD et donc la réutilisation du remplissage de la queue ne se produit pas.