Padding bei Klassen die ausschliesslich aus build-in Typen bestehen
-
Ich bezweifle nicht, dass kein Compiler da Padding einbauen wird, hab nie was Anderes behauptet. Aber der Standard garantiert imo nicht, dass der Compiler da kein Padding einbaut und ganz sicher nicht, dass du einfach mit Pointerarithmetik in dem struct rumlaufen kannst. In der Praxis wird das natürlich funktionieren. Das ist dann aber eben bestenfalls implementation defined Behavior. Wenn man nicht gerade einen Betriebssystemkernel schreibt, dann macht man sowas einfach nicht...
Abgesehen davon, kann ich zumindest nach schnellem Überfliegen der offensichtlichen Stellen nicht ausmachen, wo genau im Standard stehen soll, dass die Alignment Requirements der einfachen Typen ihrer Größe entsprechen. Ich seh da lediglich Dinge wie dass alle unsigned Integer das selbe Alignment haben wie die korrespondierenden signed Typen oder dass alle char Typen das schwächste Alignment haben. Auch steht dort sogar explizit folgendes:
The alignment required for a type might be different when it is used as the type of a complete object and when it is used as the type of a subobject.
-
dot schrieb:
Ich bezweifle nicht, dass kein Compiler da Padding einbauen wird, hab nie was Anderes behauptet. Aber der Standard garantiert imo nicht, dass der Compiler da kein Padding einbaut und ganz sicher nicht, dass du einfach mit Pointerarithemtik in dem struct rumlaufen kannst. In der Praxis wird das natürlich funktionieren. Das ist dann aber eben bestenfalls implementation defined Behavior. Wenn man nicht gerade einen Betriebssystemkernel schreibt, dann macht man sowas einfach nicht...
Das Ding ist eine POD-class, das umschließt insbesondere Standard-layout (§9). Daraus folgt, dass der Speicherbereich kontinuierlich gefüllt ist (§1.8), außer wenn es um das Alignment geht (§9.2). Dies sollte garantieren, dass ein Compiler nicht aus Jux und Dollerei Paddingbytes in ein SL-struct einbauen kann.
Für Arrays gelten ziemlich genau die gleichen Einschränkungen (§8.3.4), insbesondere die Anforderung "contiguously allocated". Daher muss die Pointerarithmetik auf dem struct funktionieren.
Metaargument: Selbst wenn ich die falschen Stellen im Standard zitiert haben sollte: Der ganze Sinn und Zweck der POD-Klassen ist, dass das Layout festgelegt ist, so dass man eine genau definierte Schnittstelle zu anderen Sprachen schreiben kann. Auch wenn der Standard hier schwer verständlich formuliert ist, so ist doch bekannt, dass die Absicht hinter den zitierten Abschnitten war, dass diese einfachen structs ein vorhersehbares und kompatibles Layout haben.
-
SeppJ schrieb:
Das Ding ist eine POD-class, das umschließt insbesondere Standard-layout (§9).
korrekt
SeppJ schrieb:
Daraus folgt, dass der Speicherbereich kontinuierlich gefüllt ist (§1.8), außer wenn es um das Alignment geht (§9.2). Dies sollte garantieren, dass ein Compiler nicht aus Jux und Dollerei Paddingbytes in ein SL-struct einbauen kann.
Das kann ich nicht nachvollziehen; wieso genau garantiert das, dass kein Compiler da jemals Padding einbauen kann!?
SeppJ schrieb:
Für Arrays gelten ziemlich genau die gleichen Einschränkungen (§8.3.4), insbesondere die Anforderung "contiguously allocated". Daher muss die Pointerarithmetik auf dem struct funktionieren.
Standard Layout garantiert dir so Zeug wie dass, wenn du zwei Standard Layout Typen, deren ersten vier Member von identischem Typ sind, in eine union packst, du die ersten vier Member egal welches Members der union anschauen darfst und den gleichen Wert vorfinden wirst. Was Pointerarithmetik betrifft, garantiert der Standard nur, dass Pointerarithmetik funktioniert, so lange du dich in einem Array bewegst. Wenns nach dem Standard geht, könnte dein Programm schon in die Luft fliegen, sobald du einen Pointer an irgendeine andere Stelle als auf ein Arrayelement auch nur ausrechnest oder zwei Pointer, die in verschiedene Arrays zeigen, subtrahierst. Allein die Tatsache, dass es überhaupt gültig ist, einen Pointer, der ein Element hinter das letzte zeigt, auszurechnen, stellt bereits einer Ausnahme dar und von was anderem als Arrays ist dort sowieso nirgendwo die Rede...
SeppJ schrieb:
Metaargument: Selbst wenn ich die falschen Stellen im Standard zitiert haben sollte: Der ganze Sinn und Zweck der POD-Klassen ist, dass das Layout festgelegt ist, so dass man eine genau definierte Schnittstelle zu anderen Sprachen schreiben kann. Auch wenn der Standard hier schwer verständlich formuliert ist, so ist doch bekannt, dass die Absicht hinter den zitierten Abschnitten war, dass diese einfachen structs ein vorhersehbares und kompatibles Layout haben.
An dem Argument ist nichts auzusetzen, aber kompatibles Layout impliziert leider nicht, dass es kein Padding geben darf. Der C Standard sagt ja auch nicht, dass es kein Padding geben darf, im Gegenteil. Wenn der C++ Standard das nun sagen würde, wäre er also inkompatibel mit C, was, genau obigem Argument folgend, völlig gegen den Sinn des ganzen wäre...
-
hier gibt es noch etwas zu lesen:
-
dd++ schrieb:
hier gibt es noch etwas zu lesen:
Dafür gibts mittlerweile übrigens static_assert...

-
dot schrieb:
SeppJ schrieb:
Daraus folgt, dass der Speicherbereich kontinuierlich gefüllt ist (§1.8), außer wenn es um das Alignment geht (§9.2). Dies sollte garantieren, dass ein Compiler nicht aus Jux und Dollerei Paddingbytes in ein SL-struct einbauen kann.
Das kann ich nicht nachvollziehen; wieso genau garantiert das, dass kein Compiler da jemals Padding einbauen kann!?
Wegen des
An object of trivially copyable or standard-layout
type (3.9) shall occupy contiguous bytes of storageDas ist ziemlich ähnlich zur Formulierung, wie Arrays aufgebaut sind.
SeppJ schrieb:
Für Arrays gelten ziemlich genau die gleichen Einschränkungen (§8.3.4), insbesondere die Anforderung "contiguously allocated". Daher muss die Pointerarithmetik auf dem struct funktionieren.
Standard Layout garantiert dir so Zeug wie dass, wenn du zwei Standard Layout Typen, deren ersten vier Member von identischem Typ sind, in eine union packst, du die ersten vier Member egal welches Members der union anschauen darfst und den gleichen Wert vorfinden wirst. Was Pointerarithmetik betrifft, garantiert der Standard nur, dass Pointerarithmetik funktioniert, so lange du dich in einem Array bewegst. Wenns nach dem Standard geht, könnte dein Programm schon in die Luft fliegen, sobald du einen Pointer an irgendeine andere Stelle als auf ein Arrayelement auch nur ausrechnest oder zwei Pointer, die in verschiedene Arrays zeigen, subtrahierst. Allein die Tatsache, dass es überhaupt gültig ist, einen Pointer, der ein Element hinter das letzte zeigt, auszurechnen, stellt bereits einer Ausnahme dar und von was anderem als Arrays ist dort sowieso nirgendwo die Rede...
Es völlig legal, innerhalb eines Objektes herumzuspringen. Ein Objekt kann nicht auf der Grenze von zwei Speicherseiten im real-mode allokiert werden, das wird gerade sichergestellt durch die Arrayklauseln.
[quote="SeppJ"]
An dem Argument ist nichts auzusetzen, aber kompatibles Layout impliziert leider nicht, dass es kein Padding geben darf. Der C Standard sagt ja auch nicht, dass es kein Padding geben darf, im Gegenteil. Wenn der C++ Standard das nun sagen würde, wäre er also inkompatibel mit C, was, genau obigem Argument folgend, völlig gegen den Sinn des ganzen wäre...Es wird aber genau definiert, wo es padding gegen muss und wo es keines geben darf. Kennt man die Typen, ihre Größe und ihr Alignment, kann man das Layout exakt vorhersagen.
In dem hier vorliegenden Fall müssen die ints so gut es geht gepackt werden, ebenso in einem Array. Folglich sind das struct und das Array gleich aufgebaut.
-
SeppJ schrieb:
dot schrieb:
SeppJ schrieb:
Daraus folgt, dass der Speicherbereich kontinuierlich gefüllt ist (§1.8), außer wenn es um das Alignment geht (§9.2). Dies sollte garantieren, dass ein Compiler nicht aus Jux und Dollerei Paddingbytes in ein SL-struct einbauen kann.
Das kann ich nicht nachvollziehen; wieso genau garantiert das, dass kein Compiler da jemals Padding einbauen kann!?
Wegen des
An object of trivially copyable or standard-layout
type (3.9) shall occupy contiguous bytes of storageDas ist ziemlich ähnlich zur Formulierung, wie Arrays aufgebaut sind.
Da liest du zuviel hinein "contiguous bytes of storage". Das ist effektiv nur eine Aussage dahingehend, dass ein char-Array genutzt werden kann, um den Speicher des Objektes genau abzubilden. C++ kennt Objekte, für die das nicht (zwingend) der Fall ist: Klassenobjekte mit virtuellen Basisklassen (und diese Einschränkung verhindert auch sonst u.U. denkbare Optimierungen, wie z.B. das Aufteilen von Objekten mit nicht-statischen const- und nicht-const-Membern).
Die o.g. Aussage ist ganz sicher keine Definition über das interne Layout (d.h. die Valuerepräsentation) von Objekten.
Klassisches Gegenbeispiel:struct foo { int a; char b; };mit üblichen 32-bit-(aligned)-ints und 8-bit chars verfügt mit absoluter Sicherheit über internes Padding, hat aber auch Standardlayout und ist trivial kopierbar.
SeppJ schrieb:
SeppJ schrieb:
Für Arrays gelten ziemlich genau die gleichen Einschränkungen (§8.3.4), insbesondere die Anforderung "contiguously allocated". Daher muss die Pointerarithmetik auf dem struct funktionieren.
Standard Layout garantiert dir so Zeug wie dass, wenn du zwei Standard Layout Typen, deren ersten vier Member von identischem Typ sind, in eine union packst, du die ersten vier Member egal welches Members der union anschauen darfst und den gleichen Wert vorfinden wirst. Was Pointerarithmetik betrifft, garantiert der Standard nur, dass Pointerarithmetik funktioniert, so lange du dich in einem Array bewegst. Wenns nach dem Standard geht, könnte dein Programm schon in die Luft fliegen, sobald du einen Pointer an irgendeine andere Stelle als auf ein Arrayelement auch nur ausrechnest oder zwei Pointer, die in verschiedene Arrays zeigen, subtrahierst. Allein die Tatsache, dass es überhaupt gültig ist, einen Pointer, der ein Element hinter das letzte zeigt, auszurechnen, stellt bereits einer Ausnahme dar und von was anderem als Arrays ist dort sowieso nirgendwo die Rede...
Es völlig legal, innerhalb eines Objektes herumzuspringen. Ein Objekt kann nicht auf der Grenze von zwei Speicherseiten im real-mode allokiert werden, das wird gerade sichergestellt durch die Arrayklauseln.
Das war bisher nicht so klar.
3.7.4.3 Safely-derived pointers
stellt klar, dass das für Implementationen mit strict pointer safety nicht so universell gilt (oder gelten würde, mir ist bisher keine solche Implementation bekannt).SeppJ schrieb:
SeppJ schrieb:
An dem Argument ist nichts auzusetzen, aber kompatibles Layout impliziert leider nicht, dass es kein Padding geben darf. Der C Standard sagt ja auch nicht, dass es kein Padding geben darf, im Gegenteil. Wenn der C++ Standard das nun sagen würde, wäre er also inkompatibel mit C, was, genau obigem Argument folgend, völlig gegen den Sinn des ganzen wäre...
Es wird aber genau definiert, wo es padding gegen muss und wo es keines geben darf. Kennt man die Typen, ihre Größe und ihr Alignment, kann man das Layout exakt vorhersagen.
In dem hier vorliegenden Fall müssen die ints so gut es geht gepackt werden, ebenso in einem Array. Folglich sind das struct und das Array gleich aufgebaut.
Gerade weil solche Layoutfragen nicht nur C++ betreffen, können sie nicht im C++-Standard vollständig geklärt werden. M.A.n. ist 9.2/14 nicht als abschließende Aufzählung anzusehen. Wäre es anders, könnte es von vornherein kein C++ auf Plattformen geben, deren ABI etwas anderes aussagt (ob solche Plattformen existieren, ist eine andere Frage, die ich nicht beantworten kann - jedenfalls ist das nichts, was der Sprachstandard zu regeln hätte). Eine solche EInschränkung erscheint mir nicht zweckmäßig.
-
camper schrieb:
Wäre es anders, könnte es von vornherein kein C++ auf Plattformen geben, deren ABI etwas anderes aussagt (ob solche Plattformen existieren, ist eine andere Frage, die ich nicht beantworten kann - jedenfalls ist das nichts, was der Sprachstandard zu regeln hätte). Eine solche EInschränkung erscheint mir nicht zweckmäßig.
Das ist doch gerade sehr zweckmäßig, denn dies ist ein großer Schritt hin zu einer einheitlichen ABI (ein großer Vorteil von C gegenüber C++). So hat man garantiert funktionierenden Datenaustausch über C-Schnittstellen und kann für den ganzen C++-Kram weiterhin alles so halten, wie der Hersteller der Implementierung es für sinnvoll erachtet.
-
SeppJ schrieb:
camper schrieb:
Wäre es anders, könnte es von vornherein kein C++ auf Plattformen geben, deren ABI etwas anderes aussagt (ob solche Plattformen existieren, ist eine andere Frage, die ich nicht beantworten kann - jedenfalls ist das nichts, was der Sprachstandard zu regeln hätte). Eine solche EInschränkung erscheint mir nicht zweckmäßig.
Das ist doch gerade sehr zweckmäßig, denn dies ist ein großer Schritt hin zu einer einheitlichen ABI (ein großer Vorteil von C gegenüber C++).
Again, du scheinst dem Irrtum zu unterliegen, dass der C Standard irgendwie ein ABI definiert. Das ist nicht der Fall. Weder der C noch der C++ Standard machen irgendwelche Vorgaben darüber, wie genau das Speicherlayout von Objekten auszusehen hat, sie legen mehr oder weniger lediglich ein Minimum an Bedingungen fest, die notwendig sind um überhaupt irgendwas mit der Sprache anzufangen. Es ist einfach nur so, dass die meisten Betriebssysteme auf C basieren und die meisten ABIs daher mehr oder weniger per Definition sind, "was auch immer der native C Compiler macht"...
Ich kann jedenfalls weder im C noch im C++ Standard (bzw. genauer in den mir zur Verfügung stehenden Drafts) etwas finden, das auch nur annähernd darauf hinweisen würde, dass ein struct aus lauter ints garantiert das selbe Speicherlayout wie ein Array aus ints haben müsste, geschweige denn, dass man einen Pointer auf den ersten Member eines solchen struct als Pointer in ein Array aus int auffassen dürfte und schon gar nicht, dass man dabei dann auch noch in irgendeiner Form definierte Ergebnisse erwarten könnte...

-
Von mir aus soll es der Standard nicht garantieren.
Es wäre aber vollkommen bekloppt wenn ein Compiler etwas anderes macht.14 Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified (11). Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (10.3) and virtual base classes (10.1).
OK. Da steht jetzt nicht dass die genannten drei Punkte die einzigen erlaubten Gründe sind "Löcher" einzufügen.
Ich finde es trotzdem schlecht formuliert. Könnte man ja einfach klar dazuschreiben - ein "among other unspecified reasons" o.ä. wäre der Verständlichkeit hier mMn. durchaus zuträglich.Bzw. die bessere Variante wäre gleich das Layout vorzuschreiben.
Zumindest für ausreichend einfache Fälle könnte man das IMO durchaus.Wobei dann wieder die strict aliasing Regeln dazwischenpfuschen, und viele Dinge die man damit anstellen könnte verbieten. (Ich denke jetzt z.B. an einen
reinterpret_cast<POINT*>(&rect)und ähnliche Dinge -- die in Windows-Programmen nichtmal so selten anzutreffen sind.)
-
hustbaer schrieb:
(Ich denke jetzt z.B. an einen
reinterpret_cast<POINT*>(&rect)und ähnliche Dinge -- die in Windows-Programmen nichtmal so selten anzutreffen sind.)

-
volkard schrieb:
hustbaer schrieb:
(Ich denke jetzt z.B. an einen
reinterpret_cast<POINT*>(&rect)und ähnliche Dinge -- die in Windows-Programmen nichtmal so selten anzutreffen sind.)

Ist aber nicht in Bausch und Bogen zu verdammen.
struct POINT { int x,y; }; struct rectangle { POINT nw, se; }; void f(){ rectangle rect = { {1,0}, {1,1} }; POINT* pp = reinterpret_cast<POINT*>(&rect); // valide s. 9.2/20 rectangle* pr = reinterpret_cast<rectangle*>(pp); // dto. }
-
hustbaer schrieb:
Es wäre aber vollkommen bekloppt wenn ein Compiler etwas anderes macht.
Ich würde mal sagen das hängt von der Architektur ab. Ich finde eine Architektur, wo Speicherzugriffe 4 Byte aligned sein müssen, jetzt nicht gerade so unvorstellbar und es könnte für einen Compiler auf so einer Architektur durchaus Sinn machen, bei einem struct aus lauter shorts dann zu padden...
hustbaer schrieb:
(Ich denke jetzt z.B. an einen
reinterpret_cast<POINT*>(&rect)und ähnliche Dinge -- die in Windows-Programmen nichtmal so selten anzutreffen sind.)Witzigerweise ist genau das eines der Beispiele, die dank dem nun eingeführten Standard Layout Kram nun legal sind...

-
dot schrieb:
hustbaer schrieb:
(Ich denke jetzt z.B. an einen
reinterpret_cast<POINT*>(&rect)und ähnliche Dinge -- die in Windows-Programmen nichtmal so selten anzutreffen sind.)Witzigerweise ist genau das eines der Beispiele, die dank dem nun eingeführten Standard Layout Kram nun legal sind...

Echt?
Und das trotztypedef struct _RECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT; typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT;Wie verträgt sich das dann mit Strict Aliasing? Wurde die Regel entsprechend aufgeweicht oder ...?
Wenn das RECT natürlich als
POINT topleft; POINT bottomRight;definiert wäre, dann gäbe es kein Problem. Dann wäre es einfach ein "inner pointer", und die sind ja schliesslichnichterlaubt. Und neue legale Wege zu schaffen wie man so einen "inner pointer" erzeugen kann... OK, das ist ja nicht SO ein Ding.@Furble Wurble
Siehe oben.
-
volkard schrieb:
hustbaer schrieb:
(Ich denke jetzt z.B. an einen
reinterpret_cast<POINT*>(&rect)und ähnliche Dinge -- die in Windows-Programmen nichtmal so selten anzutreffen sind.)

Hihi, ja, Faulheit siegt halt.
RECT rect = ... HWND hwnd = ... ::ClientToScreen(hwnd, reinterpret_cast<POINT*>(&rect)); ::ClientToScreen(hwnd, reinterpret_cast<POINT*>(&rect) + 1);Würde ich heute nimmer schreiben, aber schon oft gesehen.
Und wenn ich den 2. Aufruf nicht bräuchte, sondern aus irgend einem Grund nur "top-left" ändern will, dann ... muss ich ehrlich sagen, könnte es auch heute noch passieren dass ich das schreibe.
-
hustbaer schrieb:
dot schrieb:
hustbaer schrieb:
(Ich denke jetzt z.B. an einen
reinterpret_cast<POINT*>(&rect)und ähnliche Dinge -- die in Windows-Programmen nichtmal so selten anzutreffen sind.)Witzigerweise ist genau das eines der Beispiele, die dank dem nun eingeführten Standard Layout Kram nun legal sind...

Echt?
Ok, da war ich etwas zu vorschnell. Mit reinem Pointercast allein geht das nicht, man braucht doch eine union (nur so ist garantiert, dass beide Objekte passend aligned sind):
union { RECT r; POINT p; } stuff; stuff.r.left = 42; if (p.x) ; // wohldefiniert
-
hustbaer schrieb:
dot schrieb:
hustbaer schrieb:
(Ich denke jetzt z.B. an einen
reinterpret_cast<POINT*>(&rect)und ähnliche Dinge -- die in Windows-Programmen nichtmal so selten anzutreffen sind.)Witzigerweise ist genau das eines der Beispiele, die dank dem nun eingeführten Standard Layout Kram nun legal sind...

Echt?
Und das trotztypedef struct _RECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT; typedef struct tagPOINT { LONG x; LONG y; } POINT, *PPOINT;[...]
Wenn das RECT natürlich alsPOINT topleft; POINT bottomRight;definiert wäre, dann gäbe es kein Problem. Dann wäre es einfach ein "inner pointer", und die sind ja schliesslich nicht erlaubt.Wieso "nicht erlaubt"? Das ist doch genau mein Beispiel - und durchaus erlaubt.
-
@Furble Wurble
Oops.
Das "nicht" ist zu viel. Ich glaub ich wollte "nicht verboten" schreiben, und hab dann zu später auf "erlaubt" umformuliert.