Bei der Programmierung in CI war es von unschätzbarem Wert, Strukturen mit GCCs zu packen __attribute__((__packed__))
[...]
Da Sie erwähnen __attribute__((__packed__))
, gehe ich davon aus , dass Sie beabsichtigen, alle Auffüllungen in a zu entfernen struct
(jedes Mitglied muss eine 1-Byte-Ausrichtung haben).
Gibt es keinen Standard für Packstrukturen, der in allen C-Compilern funktioniert?
... und die Antwort ist "nein". Das Auffüllen und Ausrichten von Daten in Bezug auf eine Struktur (und zusammenhängende Arrays von Strukturen im Stapel oder Heap) gibt es aus einem wichtigen Grund. Auf vielen Computern kann ein nicht ausgerichteter Speicherzugriff zu erheblichen Leistungseinbußen führen (bei einigen neueren Hardware-Versionen ist dies jedoch weniger der Fall). In einigen seltenen Fällen führt ein falsch ausgerichteter Speicherzugriff zu einem nicht behebbaren Busfehler (der sogar das gesamte Betriebssystem zum Absturz bringen kann).
Da der C-Standard auf Portabilität ausgerichtet ist, ist es wenig sinnvoll, eine Standardmethode zu haben, mit der alle Auffüllungen in einer Struktur beseitigt werden und beliebige Felder nur falsch ausgerichtet werden können, da dies möglicherweise dazu führen kann, dass C-Code nicht mehr portabel ist.
Die sicherste und portabelste Möglichkeit, solche Daten auf eine Weise an eine externe Quelle auszugeben, bei der jegliches Auffüllen vermieden wird, besteht darin, in Byte-Streams zu serialisieren, anstatt nur zu versuchen, den Rohspeicherinhalt Ihrer Daten zu übertragen structs
. Dies verhindert auch, dass Ihr Programm Leistungseinbußen außerhalb dieses Serialisierungskontexts erleidet, und ermöglicht es Ihnen, neue Felder hinzuzufügen, struct
ohne die gesamte Software auszulösen und fehlerhaft zu machen. Es gibt Ihnen auch etwas Raum, sich mit Endianness und ähnlichen Dingen zu befassen, falls dies jemals zu einem Problem wird.
Es gibt eine Möglichkeit, das gesamte Auffüllen zu eliminieren, ohne nach compilerspezifischen Anweisungen zu greifen. Dies ist jedoch nur dann möglich, wenn die relative Reihenfolge zwischen den Feldern keine Rolle spielt. Gegeben so etwas:
struct Foo
{
double x; // assume 8-byte alignment
char y; // assume 1-byte alignment
// 7 bytes of padding for first field
};
... benötigen wir die Auffüllung für den ausgerichteten Speicherzugriff in Bezug auf die Adresse der Struktur, die diese Felder enthält, wie folgt:
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______y.......x_______y.......x_______y.......x_______y.......
... wo .
Polsterung anzeigt. Jeder x
muss an einer 8-Byte-Grenze ausgerichtet sein, um die Leistung (und manchmal sogar das richtige Verhalten) zu gewährleisten.
Sie können das Auffüllen auf tragbare Weise beseitigen, indem Sie eine SoA-Darstellung (Structure of Array) wie folgt verwenden (nehmen wir an, wir benötigen 8 Foo
Instanzen):
struct Foos
{
double x[8];
char y[8];
};
Wir haben die Struktur effektiv abgerissen. In diesem Fall sieht die Speicherdarstellung folgendermaßen aus:
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______x_______x_______x_______x_______x_______x_______x_______
... und das:
01234567
yyyyyyyy
... kein Padding-Overhead mehr und kein falsch ausgerichteter Speicherzugriff, da auf diese Datenfelder nicht mehr als Versatz einer Strukturadresse zugegriffen wird, sondern als Versatz einer Basisadresse für ein Array.
Dies hat auch den Vorteil, dass der sequenzielle Zugriff schneller ist, da weniger Daten verbraucht werden müssen (keine irrelevanten Auffüllungen im Mix mehr, um die relevante Datenverbrauchsrate der Maschine zu verlangsamen) und der Compiler die Verarbeitung sehr trivial vektorisieren kann .
Der Nachteil ist, dass das Codieren eine PITA ist. Es ist auch potenziell weniger effizient für den wahlfreien Zugriff mit größeren Schritten zwischen den Feldern, bei denen AoS- oder AoSoA-Mitarbeiter häufig besser abschneiden. Aber das ist eine Standardmethode, um das Polstern und Verpacken von Dingen so eng wie möglich zu machen, ohne mit der Ausrichtung von allem zu schrauben.