Cast Ein primitiver Typzeiger auf A-Strukturzeiger - Ausrichtung und Auffüllung?

8

Nur 20 Minuten alt, als ich eine Frage beantwortete, kam ich auf ein interessantes Szenario, dass ich mir des Verhaltens nicht sicher bin:

Lassen Sie mich ein ganzzahliges Array der Größe n haben, auf das intPtr zeigt;

%Vor%

und lassen Sie mich auch eine Struktur wie folgt haben:

%Vor%

Meine Frage ist, ob ich einen Cast intStruct* structPtr = (intStruct*) intPtr;

mache

Bin ich sicher, jedes Element korrekt zu erhalten, wenn ich die Elemente der Struktur durchquere? Gibt es eine Möglichkeit der Fehl-Ausrichtung (möglich wegen der Auffüllung) in irgendeiner Architektur / Compiler?

    
Seçkin Savaşçı 27.08.2012, 08:02
quelle

6 Antworten

5

Der Standard ist ziemlich spezifisch, dass sogar eine POD-Struktur (die ich glaube, die restriktivste Klasse von Strukturen) Padding zwischen Mitgliedern haben kann. ("Es könnte also in einem POD-struct-Objekt ein unbenanntes Padding geben, aber nicht an seinem Anfang, um eine angemessene Ausrichtung zu erreichen." - eine nicht-normative Notiz, die die Absicht jedoch noch deutlich macht).

Vergleichen Sie beispielsweise die Anforderungen für eine Standard-Layout-Struktur (C ++ 11, §1.8 / 4):

  

Ein Objekt vom trivial kopierbaren oder Standard-Layout-Typ (3.9) muss zusammenhängende Speicherbytes belegen. "

... mit denen für ein Array (§8.3.4 / 1):

  

Ein Objekt vom Typ Array enthält einen zusammenhängend zugeordneten nicht leeren Satz von N Unterobjekten vom Typ T.

Im Array müssen die Elemente selbst zusammenhängend zugewiesen werden, während in der Struktur nur der Speicher zusammenhängend sein muss.

Die dritte Möglichkeit, die die Anforderung "zusammenhängender Speicher" sinnvoller machen würde, wäre, eine Struktur / Klasse zu betrachten, die nicht trivial kopierbar oder Standard-Layout ist. In diesem Fall ist es möglich, dass der Speicher möglicherweise nicht zusammenhängend ist. Beispielsweise kann eine Implementierung einen Speicherbereich für die Speicherung aller privaten Variablen und einen vollständig separaten Speicherbereich für alle öffentlichen Variablen reservieren. Um das ein wenig konkreter zu machen, betrachte man zwei Definitionen wie:

%Vor%

Bei diesen Definitionen könnte der Speicher so aussehen:

%Vor%

In diesem Fall müssen weder die Elemente noch für den Speicher zusammenhängend sein, sodass die Verschachtelung von Teilen völlig separater Strukturen / Klassen zulässig ist.

Das heißt, das erste Element muss sich an der gleichen Adresse wie die Struktur als Ganzes befinden (9.2 / 17): "Ein Pointer auf ein POD-struct-Objekt, das mit einem reinterpret_cast entsprechend konvertiert wurde, zeigt auf seinen ursprünglichen Member (oder wenn dieses Mitglied ein Bit-Feld ist, dann zu der Einheit, in der es sich befindet und umgekehrt. "

In Ihrem Fall haben Sie eine POD-Struktur, also (§9.2 / 17): "Ein Pointer auf ein POD-struct-Objekt, das mit einem reinterpret_cast entsprechend konvertiert wurde, zeigt auf seinen ursprünglichen Member (oder wenn dieser ein a Bit-Feld, dann zu der Einheit, in der es sich befindet) und umgekehrt. " Da das erste Mitglied muss ausgerichtet sein muss und die übrigen Mitglieder alle vom selben Typ sind, ist es unmöglich, dass zwischen den anderen Mitgliedern ein Padding wirklich notwendig ist (außer für Bitfelder, irgendeinen Typ) Sie können eine Struktur einfügen, die Sie auch in ein Array einfügen können, wo eine zusammenhängende Zuweisung der Elemente erforderlich ist. Wenn Sie Elemente haben, die kleiner als ein Wort sind, auf einer wortorientierten Maschine (z. B. frühen DEC-Alphas), ist es möglich, dass das Auffüllen den Zugriff etwas einfacher machen könnte. Zum Beispiel konnten frühe DEC-Alphas (auf der Hardware-Ebene) nur ein vollständiges (64-Bit-) Wort zu einem Zeitpunkt lesen / schreiben. Betrachten wir als solche eine Struktur von vier char -Elementen:

%Vor%

Wenn es erforderlich wäre, diese im Speicher so anzuordnen, dass sie zusammenhängend sind, würde der Zugriff auf ein foo::b (zum Beispiel) erfordern, dass die CPU das Wort lädt, dann 8 Bits nach rechts verschiebt, dann Maske auf Null erweitern Dieses Byte füllt das gesamte Register.

Das Speichern wäre noch schlimmer - die CPU müsste den aktuellen Wert des ganzen Wortes laden, den aktuellen Inhalt des entsprechenden Teils davon ausmaskieren, den neuen Wert an die richtige Stelle verschieben, ODER es in das Wort und speichern Sie schließlich das Ergebnis.

Im Gegensatz dazu wird beim Padding zwischen den Elementen jedes einzelne zu einem einfachen Laden / Speichern, ohne Verschieben, Maskieren usw.

Zumindest, wenn der Speicher mit dem normalen DEC-Compiler für Alpha arbeitet, war int 32 Bit und long war 64 Bit (vorher long long ). So könnten Sie mit Ihrer Struktur von vier int s erwartet haben, dass zwischen den Elementen noch 32 Bit Padding sichtbar sind (und weitere 32 Bit nach dem letzten Element).

Da Sie eine POD-Struktur haben, haben Sie trotzdem einige Möglichkeiten. Die eine, die ich wahrscheinlich bevorzugen würde, wäre offsetof zu verwenden, um die Offsets der Mitglieder der Struktur zu erhalten, ein Array von ihnen zu erstellen und über diese Offsets auf die Mitglieder zuzugreifen. Ich habe gezeigt, wie man das in einem Paar von vorherige Antworten .

    
Jerry Coffin 29.08.2012, 15:22
quelle
3

Es ist garantiert legal, da der Elementtyp Standard-Layout ist. Hinweis: Alle Referenzen im Folgenden beziehen sich auf die Standard.

  

8.3.4 Arrays [dcl.array]

     

1 - [...] Ein Objekt vom Typ Array enthält eine zusammenhängend zugeordnete nicht leere Menge von N Unterobjekten vom Typ T . [...]

Betrifft eine struct mit N Mitglieder vom Typ T ,

  

9.2 Klassenmitglieder [class.mem]

     

14 - Nichtstatische Datenelemente einer (nicht gewerkschaftlichen) Klasse mit derselben Zugriffskontrolle werden so zugeordnet   dass spätere Mitglieder höhere Adressen innerhalb eines Klassenobjekts haben. [...] Implementation Alignment Anforderungen könnten   weil zwei benachbarte Mitglieder nicht unmittelbar hintereinander zugeordnet werden [...]
  20 - Ein Zeiger auf ein Standard-Layout-Strukturobjekt, das mit reinterpret_cast geeignet konvertiert wurde, zeigt auf seinen   Erstmitglied [...] und umgekehrt. [ Hinweis:   Es könnte daher in einem Standard-Layout-Strukturobjekt ein unbenanntes Padding geben, aber nicht am Anfang,   wie notwendig, um eine angemessene Ausrichtung zu erreichen. -Hinweis]

Es stellt sich also die Frage, ob eine Ausrichtung-bedingte Auffüllung innerhalb eines struct dazu führen könnte, dass seine Elemente einander nicht zusammenhängend zugewiesen werden. Die Antwort ist:

  

1.8 Das C ++ Objektmodell [intro.object]

     

4 - [...] Ein Objekt vom Typ trivial copyable oder standard-layout muss zusammenhängende Speicherbytes belegen.

Mit anderen Worten, ein Standard-Layout struct a mit mindestens zwei Mitgliedern x , y des gleichen (Standard-Layout) Typs, der die Identität &a.y == &a.x + 1 nicht respektiert, ist verletzt von 1,8: 4.

Beachten Sie, dass Ausrichtung definiert ist als ( 3.11 Alignment [basic.align] ) die Anzahl der Bytes zwischen aufeinander folgenden Adressen, bei denen ein bestimmtes Objekt zugeordnet werden kann ; Daraus folgt, dass die Ausrichtung eines Typs T nicht größer sein kann als die Entfernung zwischen benachbarten Objekten in einem Array von T und (da 5.3.3 Sizeof [expr.sizeof] das angibt < em> Die Größe eines Arrays von n Elementen ist n mal die Größe eines Elements ) alignof(T) darf nicht größer als sizeof(T) sein. Daher wäre ein zusätzliches -Polster zwischen benachbarten Elementen einer Struktur desselben Typs nicht erforderlich durch Ausrichtung und würde daher nicht von 9.2: 14 unterstützt.

Im Hinblick auf den AProgrammer-Punkt würde ich die Sprache in 26.4 Komplexe Zahlen [complex.numbers] so interpretieren, dass die Instanziierungen von std::complex<T> sich wie Standard-Layout-Typen verhalten sollten Position ihrer Mitglieder, ohne dass sie alle Anforderungen von Standard-Layout-Typen erfüllen müssen.

    
ecatmur 29.08.2012 10:07
quelle
2

Streng genommen sind solche Pointercasts nicht erlaubt und führen zu undefiniertem Verhalten.

Das Hauptproblem bei der Umwandlung ist jedoch, dass es dem Compiler freisteht, irgendwo innerhalb einer Struktur eine beliebige Anzahl von Füllbytes hinzuzufügen, außer vor dem allerersten Element. Ob es funktioniert oder nicht, hängt von den Ausrichtungsanforderungen des spezifischen Systems ab und davon, ob das Struct Padding aktiviert ist oder nicht.

int hat nicht unbedingt die gleiche Größe wie die optimale Größe für einen adressierbaren Datenblock, obwohl dies für die meisten 32-Bit-Systeme zutrifft. Einige 32-bitters interessieren sich nicht für eine Fehlausrichtung, einige erlauben eine Fehlausrichtung, erzeugen aber weniger effizienten Code und einige müssen die Daten ausgerichtet haben. Theoretisch möchten 64-bitters möglicherweise auch nach einem int (das 32-bit dort sein soll) Auffüllung hinzufügen, um einen 64-Bit-Chunk zu erhalten, aber in der Praxis unterstützen sie 32-Bit-Befehlssätze.

Wenn Sie Code schreiben, der auf dieser Besetzung basiert, sollten Sie etwas wie folgt hinzufügen:

%Vor%     
Lundin 27.08.2012 08:22
quelle
2

Das Verhalten dort ist fast sicher Compiler-, Architektur- und ABI-abhängig. Wenn Sie jedoch gcc verwenden, können Sie __attribute__((packed)) verwenden, um den Compiler dazu zu zwingen, Strukturelemente nacheinander ohne Pufferung zu packen. Damit sollte das Speicherlayout dem eines flachen Arrays entsprechen.

    
kelnos 29.08.2012 11:33
quelle
1

Ich habe nichts gefunden, was garantiert, dass es gültig ist, als ich vor einiger Zeit gesucht habe, und ich habe explizite Garantie für den Fall von std :: complex & lt; & gt; in C ++, die leichter formuliert hätte werden können, wenn es allgemein zutreffender wäre, also bezweifle ich, dass ich etwas bei meiner Suche übersehen habe (aber Beweislosigkeit ist kaum ein Beweis für Abwesenheit und der Standard ist manchmal unklar in seiner Formulierung).

>     
AProgrammer 27.08.2012 08:24
quelle
1

Eine typische Ausrichtung von C-Strukturen garantiert, dass die Datenstrukturelemente in der Struktur sequentiell gespeichert werden, was dasselbe wie ein C-Array ist. Ordnung kann also kein Problem sein.

Da es beim Alignment nur einen Datentyp gibt (int), obwohl der Compiler dazu berechtigt ist, gibt es kein Szenario, in dem es notwendig wäre, die Ausrichtung der Datenelemente mit einem Padding zu ergänzen. Der Compiler kann vor dem Anfang der Struktur eine Auffüllung hinzufügen, aber am Anfang der Datenstruktur keine Auffüllung hinzufügen. Wenn also der Compiler in Ihrer Situation ein Padding einfügen soll,

Statt dessen: [4 Byte int] [4 Byte int] [4 Byte int] ... [4 Byte int]

Ihre Datenstruktur müsste so gespeichert werden:
[4 Byte Daten] [4 Byte Pufferung] [4 Byte Daten] ... was unvernünftig ist.

Insgesamt denke ich, dass diese Besetzung in Ihrer Situation ohne Probleme funktionieren sollte, obwohl ich denke, dass es eine schlechte Übung ist, sie zu benutzen.

    
hamami 30.08.2012 21:22
quelle

Tags und Links