Betrachten Sie die folgenden drei struct
s:
class blub {
int i;
char c;
blub(const blub&) {}
};
class blob {
char s;
blob(const blob&) {}
};
struct bla {
blub b0;
blob b1;
};
Auf typischen Plattformen mit int
4 Bytes sind die Größen, Ausrichtungen und die Gesamtauffüllung 1 wie folgt:
struct size alignment padding
-------- ------ ----------- ---------
blub 8 4 3
blob 1 1 0
bla 12 4 6
Es gibt keine Überlappung zwischen der Lagerung der blub
und der blob
Elemente, obwohl die Größe 1 blob
im Prinzip in die Polsterung von "passen" könnte blub
.
In C ++ 20 wird das no_unique_address
Attribut eingeführt, mit dem benachbarte leere Mitglieder dieselbe Adresse verwenden können. Es erlaubt auch explizit das oben beschriebene Szenario, das Auffüllen eines Mitglieds zum Speichern eines anderen zu verwenden. Aus der Referenz (Hervorhebung von mir):
Gibt an, dass dieses Datenelement keine Adresse haben muss, die sich von allen anderen nicht statischen Datenelementen seiner Klasse unterscheidet. Dies bedeutet, dass der Compiler einen leeren Typ (z. B. zustandslosen Allocator) möglicherweise so optimiert, dass er keinen Speicherplatz belegt, genau wie wenn es sich um eine leere Basis handelt. Wenn das Element nicht leer ist, kann das darin enthaltene Tail-Padding auch zum Speichern anderer Datenelemente wiederverwendet werden.
In der Tat, wenn wir dieses Attribut auf verwenden blub b0
, bla
fällt die Größe der Tropfen auf 8
, so dass die blob
in der Tat in der blub
wie auf Godbolt gesehen gespeichert ist .
Zum Schluss kommen wir zu meiner Frage:
Welcher Text in den Standards (C ++ 11 bis C ++ 20) verhindert diese Überlappung ohne no_unique_address
für Objekte, die nicht trivial kopierbar sind?
Ich muss trivial kopierbare (TC) Objekte von den oben genannten ausschließen, da es für TC-Objekte zulässig ist, std::memcpy
von einem Objekt zum anderen zu wechseln, einschließlich der Unterobjekte der Mitglieder, und wenn sich der Speicher überlappt, würde dies brechen (weil der gesamte oder ein Teil des Speichers für das benachbarte Mitglied würde überschrieben werden) 2 .
1 Wir berechnen die Polsterung einfach als Differenz zwischen der Strukturgröße und der Größe aller ihrer Bestandteile rekursiv.
2 Aus diesem Grund habe ich Kopierkonstruktoren definiert: zu machen blub
und blob
nicht trivial kopierbar .
quelle
Antworten:
Der Standard ist furchtbar leise, wenn es um das Speichermodell geht, und nicht sehr explizit über einige der verwendeten Begriffe. Aber ich denke, ich habe eine funktionierende Argumentation gefunden (die vielleicht etwas schwach ist)
Lassen Sie uns zunächst herausfinden, was überhaupt Teil eines Objekts ist. [basic.types] / 4 :
Die Objektdarstellung von
b0
besteht also aussizeof(blub)
unsigned char
Objekten, also 8 Bytes. Die Füllbits sind Teil des Objekts.Kein Objekt kann den Raum eines anderen einnehmen, wenn es nicht in ihm verschachtelt ist [basic.life] /1.5 :
Die Lebensdauer von
b0
würde also enden, wenn der von ihm belegte Speicher von einem anderen Objekt wiederverwendet würde, db1
. H. Ich habe das nicht überprüft, aber ich denke, der Standard schreibt vor, dass das Unterobjekt eines lebendigen Objekts auch lebendig sein sollte (und ich konnte mir nicht vorstellen, wie dies anders funktionieren sollte).Der Speicher, der
b0
belegt, darf also nicht von verwendet werdenb1
. Ich habe im Standard keine Definition von "besetzen" gefunden, aber ich denke, eine vernünftige Interpretation wäre "Teil der Objektdarstellung". In dem Zitat, das die Objektdarstellung beschreibt, werden die Wörter "aufnehmen" verwendet 1 . Hier wären dies 8 Bytes,bla
benötigt also mindestens ein weiteres fürb1
.Speziell für Unterobjekte (also unter anderem nicht statische Datenelemente) gibt es auch die Bedingung [intro.object] / 9 (dies wurde jedoch mit C ++ 20 hinzugefügt, thx @BeeOnRope)
(Hervorhebung von mir) Auch hier haben wir das Problem, dass "besetzt" nicht definiert ist, und ich würde erneut argumentieren, die Bytes in der Objektdarstellung zu verwenden. Beachten Sie, dass diese [basic.memobj] / Fußnote 29 eine Fußnote enthält
Dies kann es dem Compiler ermöglichen, dies zu unterbrechen, wenn er nachweisen kann, dass keine beobachtbaren Nebenwirkungen vorliegen. Ich würde denken, dass dies für eine so grundlegende Sache wie das Objektlayout ziemlich kompliziert ist. Vielleicht wird diese Optimierung deshalb nur durchgeführt, wenn der Benutzer die Information bereitstellt, dass es keinen Grund gibt, durch Hinzufügen des
[no_unique_address]
Attributs disjunkte Objekte zu haben .tl; dr: Polsterung kann Teil des Objekts sein und Mitglieder müssen disjunkt sein.
1 Ich konnte nicht widerstehen, eine Referenz hinzuzufügen, die besetzt bedeuten könnte: Webster's Revised Unabridged Dictionary, G. & C. Merriam, 1913 (Hervorhebung von mir)
Welcher Standard-Crawl wäre ohne einen Wörterbuch-Crawl vollständig?
quelle
no_unique_address
. Es lässt die Situation vor C ++ 20 weniger klar. Ich habe Ihre Argumentation nicht verstanden, die dazu führte, dass "kein Objekt den Raum eines anderen belegen kann, wenn es nicht in ihm verschachtelt ist" aus basic.life/1.5, insbesondere, wie Sie aus "dem Speicher, den das Objekt belegt, freigegeben werden" gelangen. zu "kein Objekt kann den Raum eines anderen einnehmen".