2015-12-09 12 views
6

ich das Beispiel in C++ 14 §3.11/2 mit:Warum wurde die Größe von (D) in VS2015 um 8 Byte erhöht, als ich D von einer virtuellen Basis herleitete?

struct B { long double d; }; 
struct D : virtual B { char c; } 

Nachdem das Snippet unten in Klirren, g ++ und VS2015 laufen

#include <iostream> 
struct B { long double d; }; 
struct D : /*virtual*/ B { char c; }; 

int main() 
{ 
    std::cout << "sizeof(long double) = " << sizeof(long double) << '\n'; 
    std::cout << "alignof(long double) = " << alignof(long double) << '\n'; 

    std::cout << "sizeof(B) = " << sizeof(B) << '\n'; 
    std::cout << "alignof(B) = " << alignof(B) << '\n'; 

    std::cout << "sizeof(D) = " << sizeof(D) << '\n'; 
    std::cout << "alignof(D) = " << alignof(D) << '\n'; 
} 

Ich habe folgende Ergebnisse:

      clang   g++   VS2015 
sizeof(long double)  16    16   8 
alignof(long double)  16    16   8 
sizeof(B)     16    16   8 
alignof(B)     16    16   8 
sizeof(D)     32    32   16 
alignof(D)     16    16   8 

Jetzt, nach der virtual in der Definition von struct D in dem obigen Code uncommenting und laufen Sie den Code erneut für Klirren, g ++ und VS20 15, erhielt ich folgende Ergebnisse:

      clang   g++   VS2015 
sizeof(long double)  16    16   8 
alignof(long double)  16    16   8 
sizeof(B)     16    16   8 
alignof(B)     16    16   8 
sizeof(D)     32    32   24 
alignof(D)     16    16   8 

Ich habe keine Zweifel an den oben erhaltenen Ergebnissen, mit einer einzigen Ausnahme: Warum hat die sizeof(D) 16-24 in VS2015 erhöht?

Ich weiß, dass die Implementierung definiert ist, aber es könnte eine vernünftige Erklärung für diese Größenzunahme geben. Das würde ich gerne wissen, wenn es möglich wäre.

+0

@NathanOliver diese Strukturen haben keine virtuellen Funktionen. Wird Vtable erstellt, wenn Sie virtuell abgeleitet werden? – Alex

+0

Fragen Sie den Compiler, um Ihnen zu sagen, verwenden Sie die Kompilieroption '/ d1reportAllClassLayout'. Sie sehen 8 Bytes für den virtuellen Basistabellenzeiger + 1 Byte für D :: c + 7 Bytes Auffüllen + 8 Bytes für B :: d == 24. Der Zeiger ist im nicht-virtuellen Fall entfernt. –

+0

Ich denke das [Blogbeitrag] (http://lolengine.net/blog/2012/10/21/the-stolen-bytes) hilft dir. In dem Kommentar gibt Jangray, der in einer Vergangenheit MSVC-Compiler geschrieben hat, eine Erklärung. – akakatak

Antwort

1

Wenn Sie tatsächlich den virtuellen Aspekt der virtuellen Vererbung verwenden, denke ich, dass die Notwendigkeit für den Vtable-Zeiger klar wird. Ein Element in der V-Tabelle ist wahrscheinlich der Offset des Beginns von B von Anfang an .

Angenommen E praktisch von B und F erbt von sowohl E und D solche erbt, dass die D innerhalb eines FB die im Inneren der für seine Basisklasse E unter Verwendung endet. In einer Methode von , die nicht wissen, ist es eine Basisklasse von F Wie können Sie Mitglieder von B ohne Informationen in der VTable gespeichert finden?

So Clang und G ++ 8 Bytes Padding in einen VTable-Zeiger geändert und Sie dachten, es gab keine Änderung. Aber VS2015 hatte nie diese Auffüllung, also musste er 8 Bytes für den Vtable-Zeiger hinzufügen.

Möglicherweise bemerkt ein Compiler, dass die einzige Verwendung des Vtable-Pointers in einem ineffizienten Schema zum Berechnen des Basiszeigers ist. Vielleicht ist das optimiert, um einfach einen Basiszeiger anstelle eines VTable-Zeigers zu haben. Aber das würde die Notwendigkeit für die 8 Bytes nicht ändern.

+0

Ich denke, das ist eine vernünftige Antwort: 'So Clang und G ++ 8 Bytes Padding in einen Vtable-Zeiger geändert und Sie dachten, es gab keine Änderung. Aber VS2015 hatte nie diese Auffüllung, also musste er 8 Bytes für den Vtable-Zeiger hinzufügen. Danke (+1). – Ayrosa

+0

Ich denke, dass die meisten Leute, die die Frage sehen, denken würden, dass es bedeutet, "warum virtuelle Vererbung ohne virtuelle Funktionen etwas benötigt, das den Raum nimmt, den ein Vtable-Zeiger normalerweise nimmt". Ich denke, der Teil, den Sie als "Antwort" sehen, ist für die meisten Menschen nur eine Erläuterung der Verbindung zwischen der Antwort und einigen Details im Beispiel der Frage. Ich bin froh, dass du ** deine ** Antwort erhalten hast, aber wenn du vorschlägst, dass du dich auf diese Weise beschneide, denke ich nicht. – JSF

0

Wenn ein Basisobjekt virtual vorhanden ist, ist die Position des Basisobjekts relativ zur Adresse eines abgeleiteten Objekts nicht statisch vorhersagbar. Bemerkenswert ist, wenn Sie Ihre Klassenhierarchie ein wenig verlängern wird deutlich, dass es mehrere D Subobjekte sein, die nach wie vor nur ein B Basisobjekt verweisen müssen:

class I1: public D {}; 
class I2: public D {}; 
class Most: public I1, public I2 {}; 

Sie eine D* von einem Most Objekt entweder durch Umwandlung erhalten können zuerst I1 oder zuerst I2:

Most m; 
D* d1 = static_cast<I1*>(&m); 
D* d2 = static_cast<I2*>(&m); 

werden Sie haben d1 != d2, dh es wirklich zwei D Subobjekte, aber static_cast<B*>(d1) == static_cast<B*>(d2), also gibt es j ust ein B Unterobjekt. Um zu ermitteln, wie d1 und d2 angepasst werden, um einen Zeiger auf das Unterobjekt B zu finden, ist ein dynamischer Offset erforderlich. Die Informationen zum Bestimmen dieses Offsets müssen irgendwo gespeichert werden. Der Speicher für diese Information ist die wahrscheinliche Quelle der zusätzlichen 8 Bytes.

Ich glaube nicht, dass das Objektlayout für Typen in MSVC++ [öffentlich] dokumentiert ist, d. H. Es ist unmöglich, sicher zu sagen, was sie tun. Von ihrem Aussehen her betten sie ein 64-Bit-Objekt ein, um zu erkennen, wo das Basisobjekt relativ zur Adresse des abgeleiteten Objekts lebt (ein Zeiger auf einige Typinformationen, ein Zeiger auf eine Basis, ein Offset zur Basis, etwas) so wie das). Die anderen 8 Byte stammen höchstwahrscheinlich aus der Notwendigkeit, eine char plus eine Auffüllung zu speichern, um das Objekt an einer geeigneten Grenze auszurichten. Das scheint ähnlich zu sein, was die anderen beiden Compiler tun, außer dass sie 16 Bytes für long double verwendet haben (wahrscheinlich sind es nur 10 Bytes, die zu einer geeigneten Ausrichtung aufgefüllt sind).

Um zu verstehen, wie das C++ - Objektmodell funktionieren könnte, möchten Sie vielleicht einen Blick auf Stan Lippmans "Inside the C++ Object Model" werfen. Es ist etwas veraltet, beschreibt aber mögliche Implementierungstechniken. Ob MSVC++ irgendwelche von ihnen verwendet, weiß ich nicht, aber es gibt Ideen, was verwendet werden könnte.

Für das Objektmodell verwendet, von gcc und clang Sie einen Blick auf den Itanium ABI haben: sie verwenden im Wesentlichen das Itanium ABI mit den kleineren Anpassungen an den tatsächlich verwendeten CPU.

0

In Visual Studio ist das Standardverhalten, dass alle Strukturen entlang einer 8-Byte-Grenze ausgerichtet sind. i.e.even wenn Sie

struct A { 
    char c; 
} 

tun und dann sizeof(A) überprüfen, werden Sie sehen, dass es 8 Bytes ist.

Nun, in Ihrem Fall, wenn Sie den Typ der Vererbung von Struktur D geändert haben, um virtuell zu sein, muss der Compiler etwas extra tun, um dies zu erreichen. Zunächst wird eine virtuelle Tabelle für die Struktur D erstellt. Was enthält diese V-Tabelle? Es enthält einen einzelnen Zeiger auf den Offset von Struktur B im Speicher. Als nächstes fügt es ein vptr am Anfang von Struktur D hinzu, die auf die neu erstellte vtable zeigt.

Daher sollte nun struct D wie folgt aussehen:

struct D : virtual B { void* vptr; char c; } 

die Größe von D Also wird:

sizeof (long double) + sizeof (void*) + sizeof (char) = 8 + 8 + 1 = 17 

Dies ist, wo die Grenze Ausrichtung wir am Anfang besprochen kommt. Da alle Strukturen an einer 8-Byte-Grenze ausgerichtet sein müssen und Struktur D nur 17 Byte ist, fügt der Compiler der Struktur 7 Auffüll-Bytes hinzu, um sie auf eine 8-Byte-Grenze auszurichten.

So ist die Größe wird jetzt:

Size of D = Size of elements of D + Padding bytes for byte alignment = 17 + 7 = 24 bytes. 
+2

'In Visual Studio ist das Standardverhalten, dass alle Strukturen entlang einer 8-Byte-Grenze ausgerichtet sind. auch wenn Sie struct A {char c; } und dann sizeof (A) überprüfen, Sie werden sehen, dass es 8 Bytes ist. [Das scheint nicht korrekt zu sein.] (http://rextester.com/TOJ63653) – Ayrosa