c++ - Unterschied zwischen Klasse und Struktur in Bezug auf Auffüllung und Vererbung




c++11 gcc (2)

Alle folgenden -O3 werden unter GCC 9.1 mit dem Compiler-Explorer in x86-64 mit -O3 .

Ich habe diesen Code:

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

Es gibt wie erwartet 16 zurück, 8 Bytes für foo und 4 Bytes für bar und 4 Bytes für baz . Dies funktioniert nur, weil Derived von Base erbt und daher nicht nach dem bar aufgefüllt werden muss, da Derived ein einzelner Typ ist, der sowohl Base als auch Derived Elemente enthält.

Ich habe zwei Fragen wie folgt:

Erste Frage

Wenn ich den expliziten Konstruktor von Base() {} entferne, wird 24 statt 16 . Das heißt, es fügt Polster nach bar und baz .

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

Ich kann nicht erklären, warum ein expliziter Standardkonstruktor sich von einem impliziten Standardkonstruktor unterscheidet.

Zweite Frage

Wenn ich dann struct in class für Base ändere, wird wieder 16 . Das kann ich auch nicht erklären. Warum sollten die Zugriffsmodifikatoren die Größe der Struktur ändern?

https://godbolt.org/z/SCYKwL


Dies alles läuft darauf hinaus, ob Ihr Typ ein Aggregat ist oder nicht. Mit

struct Base {
    Base() {}
    double foo;
    int bar;
};

struct Derived : public Base {
    int baz;
};

Base ist aufgrund des Konstruktors kein Aggregat. Wenn Sie den Konstruktor entfernen, machen Sie Base einem Aggregat, das durch .com/q/47914612/560648 , bedeutet, dass gcc den Platz nicht "optimiert" und das abgeleitete Objekt die base nicht verwendet Schwanzpolsterung.

Wenn Sie den Code in ändern

class Base {
    double foo;
    int bar;
};

struct Derived : public Base {
    int baz;
};

foo und bar sind jetzt privat (da Klassen standardmäßig privat zugänglich sind), was wiederum bedeutet, dass Base kein Aggregat mehr ist, da Aggregate keine privaten Mitglieder haben dürfen. Das heißt, wir sind wieder bei der Arbeitsweise des ersten Falls.


Mit Ihrer Basisklasse erhalten Sie 4 Byte Tail-Auffüllung, und das Gleiche gilt für die Derived-Klasse. Aus diesem Grund sollte die Gesamtgröße für Derived normalerweise 24 bytes Derived .

Es werden 16 Bytes, da Ihr Compiler die Wiederverwendung von Tail-Padding durchführen kann .

Die Wiederverwendung von Tail-Padding ist jedoch bei POD Typen (alle Member öffentlich, Standardkonstruktor usw.) problematisch, da hierdurch die üblichen Annahmen eines Programmierers verletzt werden. (Grundsätzlich führt kein vernünftiger Compiler die Wiederverwendung des Heckpolsters für Pod-Typen durch.)

Stellen wir uns vor, Compiler würden die tail padding reuse des tail padding reuse für POD-Typen verwenden:

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

Wenn Sie der Basisklasse einen expliziten Konstruktor hinzufügen oder die Schlüsselwörter struct in class ändern, erfüllt die Derived Klasse die POD-Definition nicht mehr und daher wird das Heckpolster nicht mehr wiederverwendet.







gcc