Warum überlappt sich der Speicher für eine doppelte leere Basis nicht mit einem vtable-Zeiger?


11

Betrachten Sie dieses Beispiel:

#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?
}

(renn auf Godbolt)

Hier können Sie sehen, dass für struct Edie leere Basisklasse (die 1 Byte groß ist) erwartungsgemäß derselbe Speicher wie für den vtable-Zeiger verwendet wird.

Aber für struct F, die eine doppelte leere Basis hat, passiert dies nicht. Was verursacht das?

Ich erhalte das gleiche Ergebnis bei GCC, Clang und MSVC. Die obigen Ergebnisse gelten für x64 sizeof(void *) == 8.


Interessanterweise führen struct G : A, B {void *ptr;};GCC und Clang EBO durch (die Größe ist 8), MSVC jedoch nicht (die Größe ist 16).


3
Merkwürdigerweise von erben C(die erben von A, B) Sie anderes Ergebnis erhalten , als Form vererben Aund Bdirekt
Guillaume Racicot

1
Ich habe es genossen, diesen zu erforschen. Danke für die Frage und den Link. Ich bin mir nicht sicher, ob ich eine Antwort habe und werde sie einfach kommentieren. Könnte es sein, dass dies aus der Mehrdeutigkeit resultiert, die sich aus der Ableitung von Cund ergibt F? Immerhin 2 * sizeof(void*) == 16auf x86_64, wie Sie gesagt haben. Der Compiler kann nicht vollständig optimieren (wie Story Teller sagte) und tut dies auch nicht.
Andrew Falanga

2
Es ist normal, dass Sie bei gcc und clang das gleiche Ergebnis erzielen, da beide dem itanium ABI folgen. Und wenn dies der Fall ist, befürchteten sie bei der Definition des ABI, dass der Layoutalgorithmus zu teuer werden könnte, und nahmen daher einige Verknüpfungen (auch Pessimisierungen genannt).
Marc Glisse

2
@RianQuinn Eine doppelte Basis macht eine Struktur nicht ungültig.
HolyBlackCat

1
@RianQuinn, das mehrmals über verschiedene "Pfade" von derselben Klasse erbt, ist in C ++ vollkommen gültig. Wenn Sie eine Diamantstruktur erstellen möchten, dh die Basisklasse nur einmal haben möchten, müssen Sie die virtuelle Vererbung verwenden. Wenn Sie jedoch keinen Diamanten möchten und eine doppelte Basisklasse für Sie kein Problem haben, ist dies auch für die Sprache kein Problem. Der OP-Code erzeugt nur eine Warnung, die besagt, dass auf die zweite A, die über geerbt wurde B, nicht zugegriffen werden kann. Das ist gut. Nur wenn Sie tatsächlich versuchen, wie in Ihrem Beispiel darauf zuzugreifen, wird eine Fehlermeldung angezeigt.
sebrockm

Antworten:


4

Weil der Compiler nach Struktur A ein Byte-Padding hinzufügt

F {vptr (8) + 0 Mitglieder von A + 1 Auffüllen (weil A leer ist) +0 von b} = 9, dann fügt der Compiler 7 Byte Auffüllen hinzu, um den Speicher der Struktur auszurichten;

E {vptr (8) + 0 Mitglieder für A} = 8 Keine Auffüllung erforderlich

von Microsoft

Jedes Datenobjekt hat eine Ausrichtungsanforderung. Für Strukturen ist die Anforderung die größte ihrer Mitglieder. Jedem Objekt wird ein Versatz zugewiesen, sodass der Versatz% Ausrichtungsanforderung == 0 ist

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

BEARBEITEN:

Hier ist meine Demo:

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

}

Ausgabe:

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

Wie Sie sehen können, überschneiden sich a & b nie miteinander und mit vptr bei Mehrfachvererbung hat jeder seinen eigenen Zeigerwert

Anmerkung kompiliert von VC2019 x64 Build


Ich glaube nicht, dass es so funktioniert. Obwohl Aes keine Mitglieder hat, belegt es immer noch 1 Byte (das mit einem anderen Objekt geteilt werden kann). In E, Anicht nach dem vptr befindet; es überlappt das erste Byte von vptr. (Hier ist eine Demo ; ich habe den Code leicht geändert, um ihn Azugänglich zu machen .) Gleiches passiert für den ersten Ain F. Da A(und B) auf dem vptr platziert werden können, bin ich mir nicht sicher, warum dies nicht der Fall ist B.
HolyBlackCat

@HolyBlackCat, aber genau das ist passiert, überprüfen Sie den Testcode
Ahmed Anter

Aha, also verhält sich MSVC hier anders als GCC / Clang; Es ist nicht klug genug, um es Aauf den vptr zu setzen. Es kann erklären, warum die Ausgabe bei MSVC 16 ist, aber ich bin nicht sicher, was mit GCC & Clang los ist.
HolyBlackCat
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.