empty_bases
Microsoft-spezifisch
Der C++-Standard erfordert, dass ein am häufigsten abgeleitetes Objekt eine Nicht-Null-Größe aufweisen muss und mindestens ein Bytes Speicher belegen muss. Da sich die Anforderung nur auf die meisten abgeleiteten Objekte erstreckt, unterliegen Basisklassenunterobjekte dieser Einschränkung nicht. Die Leere Basisklassenoptimierung (EMPTY Base Class Optimization, EBCO) nutzt diese Freiheit. Dies führt zu einer reduzierten Arbeitsspeicherauslastung, wodurch die Leistung verbessert werden kann. Der Microsoft Visual C++-Compiler hatte in der Vergangenheit nur eingeschränkte Unterstützung für EBCO. In Visual Studio 2015 Update 3 und höher haben wir ein neues __declspec(empty_bases)
Attribut für Klassentypen hinzugefügt, das diese Optimierung vollständig nutzt.
Wichtig
Die Verwendung kann __declspec(empty_bases)
zu einer ABI-unterbrechungsbedingten Änderung des Struktur- und Klassenlayouts führen, auf die sie angewendet wird. Stellen Sie sicher, dass der gesamte Clientcode dieselben Definitionen für Strukturen und Klassen wie Ihren Code verwendet, wenn Sie dieses Speicherklassen-Attribut verwenden.
Syntax
__declspec( empty_bases )
Hinweise
In Visual Studio ist eine leere Klasse ohne Angabe __declspec(align())
alignas()
1 Byte groß:
struct Empty1 {};
static_assert(sizeof(Empty1) == 1, "Empty1 should be 1 byte");
Eine Klasse mit einem einzelnen nicht statischen Datenmemmemm ist char
ebenfalls 1 Byte groß:
struct Struct1
{
char c;
};
static_assert(sizeof(Struct1) == 1, "Struct1 should be 1 byte");
Das Kombinieren dieser Klassen in einer Klassenhierarchie führt auch zu einer Klasse mit einer Größe von 1 Byte:
struct Derived1 : Empty1
{
char c;
};
static_assert(sizeof(Derived1) == 1, "Derived1 should be 1 byte");
Dieses Ergebnis ist die leere Basisklassenoptimierung bei der Arbeit, wie es ohne Derived1
2 Bytes in der Größe wäre: 1 Byte für Empty1
und 1 Byte für Derived1::c
. Das Klassenlayout ist auch optimal, wenn eine Kette leerer Klassen vorhanden ist:
struct Empty2 : Empty1 {};
struct Derived2 : Empty2
{
char c;
};
static_assert(sizeof(Derived2) == 1, "Derived2 should be 1 byte");
Das Standardklassenlayout in Visual Studio nutzt EBCO jedoch nicht in mehreren Vererbungsszenarien:
struct Empty3 {};
struct Derived3 : Empty2, Empty3
{
char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // Error
Obwohl Derived3
es sich bei 1 Byte um eine Größe handeln könnte, führt das Standardklassenlayout dazu, dass es 2 Byte groß ist. Der Klassenlayoutalgorithmus fügt 1 Byte Abstand zwischen zwei aufeinander folgenden leeren Basisklassen hinzu, wodurch Empty2
ein zusätzliches Byte innerhalb Derived3
von :
class Derived3 size(2):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
1 | +--- (base class Empty3)
| +---
1 | c
+---
Die Auswirkungen dieses suboptimalen Layouts werden zusammengesetzt, wenn die Ausrichtungsanforderungen einer späteren Basisklasse oder eines Memberunterobjekts zusätzlichen Abstand erzwingen:
struct Derived4 : Empty2, Empty3
{
int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // Error
Die natürliche Ausrichtung für ein Objekt vom Typ int
beträgt 4 Byte, sodass 3 Bytes zusätzlicher Abstand nach Empty3
der korrekten Ausrichtung Derived4::i
hinzugefügt werden müssen:
class Derived4 size(8):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
1 | +--- (base class Empty3)
| +---
| <alignment member> (size=3)
4 | i
+---
Ein weiteres Problem mit dem Standardklassenlayout besteht darin, dass eine leere Basisklasse an einem Offset über das Ende der Klasse angeordnet werden kann:
struct Struct2 : Struct1, Empty1
{
};
static_assert(sizeof(Struct2) == 1, "Struct2 should be 1 byte");
class Struct2 size(1):
+---
0 | +--- (base class Struct1)
0 | | c
| +---
1 | +--- (base class Empty1)
| +---
+---
Obwohl Struct2
die optimale Größe ist, wird bei Offset 1 innerhalb Struct2
der Größe angeordnet, Empty1
aber die Größe Struct2
wird nicht erhöht, um sie zu berücksichtigen. Daher ist die Adresse des Unterobjekts A[0]
für ein Array A
von Struct2
Objekten identisch mit der Adresse von A[1]
, die nicht der Fall sein sollte.Empty1
Dieses Problem würde nicht auftreten, wenn Empty1
sie bei Offset 0 innerhalb Struct2
des Bereichs angeordnet wurden, wodurch das Struct1
Unterobjekt überlappen würde.
Der Standardlayoutalgorithmus wurde nicht geändert, um diese Einschränkungen zu beheben und EBCO vollständig zu nutzen. Eine solche Änderung würde die binäre Kompatibilität unterbrechen. Wenn das Standardlayout für eine Klasse aufgrund von EBCO geändert wurde, müssen alle Objektdateien und -bibliotheken, die die Klassendefinition enthalten, neu kompiliert werden, damit sie alle dem Klassenlayout zustimmen. Diese Anforderung würde sich auch auf Bibliotheken aus externen Quellen erstrecken. Die Entwickler solcher Bibliotheken müssten unabhängige Versionen bereitstellen, die sowohl mit als auch ohne ebCO-Layout kompiliert wurden, um Kunden zu unterstützen, die unterschiedliche Versionen des Compilers verwenden. Obwohl wir das Standardlayout nicht ändern können, können wir ein Mittel bereitstellen, um das Layout pro Klasse mit dem Hinzufügen des __declspec(empty_bases)
Klassenattributes zu ändern. Eine mit diesem Attribut definierte Klasse kann EBCO vollständig nutzen.
struct __declspec(empty_bases) Derived3 : Empty2, Empty3
{
char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // No Error
class Derived3 size(1):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
0 | +--- (base class Empty3)
| +---
0 | c
+---
Alle Unterobjekte Derived3
werden bei Offset 0 angeordnet, und ihre Größe ist die optimale 1 Byte. Ein wichtiger Punkt zu beachten ist, dass __declspec(empty_bases)
sich nur das Layout der Klasse auswirkt, auf die sie angewendet wird. Sie wird nicht rekursiv auf Basisklassen angewendet:
struct __declspec(empty_bases) Derived5 : Derived4
{
};
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // Error
class Derived5 size(8):
+---
0 | +--- (base class Derived4)
0 | | +--- (base class Empty2)
0 | | | +--- (base class Empty1)
| | | +---
| | +---
1 | | +--- (base class Empty3)
| | +---
| | <alignment member> (size=3)
4 | | i
| +---
+---
Obwohl __declspec(empty_bases)
sie angewendet Derived5
wird, ist sie nicht für EBCO berechtigt, da sie keine direkten leeren Basisklassen aufweist, sodass sie keine Auswirkung hat. Wenn sie aber stattdessen auf die Derived4
Basisklasse angewendet wird, die für EBCO geeignet ist, haben beide ein Derived5
Derived4
optimales Layout:
struct __declspec(empty_bases) Derived4 : Empty2, Empty3
{
int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // No Error
struct Derived5 : Derived4
{
};
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // No Error
class Derived5 size(4):
+---
0 | +--- (base class Derived4)
0 | | +--- (base class Empty2)
0 | | | +--- (base class Empty1)
| | | +---
| | +---
0 | | +--- (base class Empty3)
| | +---
0 | | i
| +---
+---
Aufgrund der Anforderung, dass sich alle Objektdateien und -bibliotheken auf das Klassenlayout einigen, __declspec(empty_bases)
können nur auf von Ihnen gesteuerte Klassen angewendet werden. Sie kann nicht auf Klassen in der Standardbibliothek oder auf Klassen angewendet werden, die in Bibliotheken enthalten sind, die nicht auch mit dem EBCO-Layout neu kompiliert werden.
Ende Microsoft-spezifisch
Siehe auch
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Issues stufenweise als Feedbackmechanismus für Inhalte abbauen und durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unterFeedback senden und anzeigen für