web-dev-qa-db-ja.com

パディングと継承に関するクラスと構造体の違い

以下のすべては、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);
}
_

https://godbolt.org/z/OjSCZB

私が期待するとおり、_16_を正しく返します。fooには8バイト、barには4バイト、bazには4バイトです。これが機能するのは、DerivedBaseを継承するため、barDerivedBaseの両方の要素を含む単一の型であるため、Derivedの後にパディングする必要がないためです。

以下の2つの質問があります。

最初の質問

Base() {}の明示的なコンストラクターを削除すると、_24_ではなく_16_が返され始めます。つまり、barbazの後にパディングを追加します。

https://godbolt.org/z/0gaN5h

明示的なデフォルトコンストラクターがあることと、暗黙的なデフォルトコンストラクターがあることとの違いは説明できません。

2番目の質問

次に、structclassBaseに変更すると、_16_が返されます。これも説明できません。アクセス修飾子が構造のサイズを変更するのはなぜですか?

https://godbolt.org/z/SCYKwL

43
Salgar

これはすべて、タイプが集約であるかどうかに要約されます。と

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;
};

foobarがプライベートになりました(クラスにはデフォルトでプライベートアクセシビリティがあるため)。これも、Baseはアグリゲートではないことを意味し、アグリゲートはプライベートメンバーを持つことができません。これは、最初のケースの仕組みに戻ったことを意味します。

36
NathanOliver

Baseクラスを使用すると、4バイトのテールパディングが得られ、Derivedクラスと同じなので、通常は24 bytesDerivedのサイズの合計。

コンパイラは テールパディングの再利用 を実行できるため、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定義を満たさなくなるため、テールパディングの再利用起こりません。

10
Turtlefight