以下のすべては、GCC 9.1で Compiler Explorer を使用して、x86-64では_-O3
_を使用して実行されます。
私はこのコードを持っています:
_struct Base {
Base() {}
double foo;
int bar;
};
struct Derived : public Base {
int baz;
};
int main(int argc, char** argv)
{
return sizeof(Derived);
}
_
私が期待するとおり、_16
_を正しく返します。foo
には8バイト、bar
には4バイト、baz
には4バイトです。これが機能するのは、Derived
がBase
を継承するため、bar
がDerived
とBase
の両方の要素を含む単一の型であるため、Derived
の後にパディングする必要がないためです。
以下の2つの質問があります。
最初の質問
Base() {}
の明示的なコンストラクターを削除すると、_24
_ではなく_16
_が返され始めます。つまり、bar
とbaz
の後にパディングを追加します。
明示的なデフォルトコンストラクターがあることと、暗黙的なデフォルトコンストラクターがあることとの違いは説明できません。
2番目の質問
次に、struct
のclass
をBase
に変更すると、_16
_が返されます。これも説明できません。アクセス修飾子が構造のサイズを変更するのはなぜですか?
これはすべて、タイプが集約であるかどうかに要約されます。と
struct Base {
Base() {}
double foo;
int bar;
};
struct Derived : public Base {
int baz;
};
Base
は、コンストラクターのため、集合体ではありません。コンストラクターを削除すると、Base
を集合体にして、 デフォルトコンストラクターを基本クラスに追加すると、sizeof()の派生型が変更されます は、gccがスペースを「最適化」せず、派生オブジェクトがベースのテールパディングを使用しないことを意味します。
コードを次のように変更した場合
class Base {
double foo;
int bar;
};
struct Derived : public Base {
int baz;
};
foo
とbar
がプライベートになりました(クラスにはデフォルトでプライベートアクセシビリティがあるため)。これも、Base
はアグリゲートではないことを意味し、アグリゲートはプライベートメンバーを持つことができません。これは、最初のケースの仕組みに戻ったことを意味します。
Baseクラスを使用すると、4バイトのテールパディングが得られ、Derivedクラスと同じなので、通常は24 bytes
Derived
のサイズの合計。
コンパイラは テールパディングの再利用 を実行できるため、16バイトになります。
ただし、テールパディングの再利用は POD
types (すべてのメンバーがパブリック、デフォルトのコンストラクターなど)を使用すると問題が発生します。プログラマが行う一般的な想定に反するためです。 (したがって、基本的に、正常なコンパイラーは、ポッドタイプのテールパディングを再利用しません)
コンパイラがtail padding reuse
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!
}
明示的なコンストラクターをBaseクラスに追加するとき、またはstruct
キーワードをclass
に変更するときに、Derived
クラスはPOD定義を満たさなくなるため、テールパディングの再利用起こりません。