Bei der Programmierung in CI war es von unschätzbarem Wert, Strukturen mithilfe des GCCs- __attribute__((__packed__))
Attributs zu packen , damit ich leicht einen strukturierten Teil des flüchtigen Speichers in ein Array von Bytes konvertieren kann, die über einen Bus übertragen, gespeichert oder auf einen Registerblock angewendet werden. Gepackte Strukturen garantieren, dass sie, wenn sie als Array von Bytes behandelt werden, keine Auffüllung enthalten, was sowohl verschwenderisch als auch ein mögliches Sicherheitsrisiko darstellt und möglicherweise mit Schnittstellenhardware nicht kompatibel ist.
Gibt es keinen Standard für Packstrukturen, der in allen C-Compilern funktioniert? Wenn nicht, bin ich ein Ausreißer in der Annahme, dass dies ein kritisches Merkmal für die Systemprogrammierung ist? Haben frühe Benutzer der Sprache C keine Notwendigkeit für Packstrukturen gefunden oder gibt es eine Alternative?
quelle
Antworten:
In einer Struktur ist der Versatz jedes Mitglieds von der Adresse jeder Strukturinstanz von Bedeutung. Es geht nicht so sehr darum, wie eng die Dinge sind.
Ein Array spielt jedoch eine Rolle, wie es "gepackt" wird. Die Regel in C lautet, dass jedes Array-Element genau N Bytes vom vorherigen enthält, wobei N die Anzahl der Bytes ist, die zum Speichern dieses Typs verwendet werden.
Aber mit einer Struktur gibt es kein solches Bedürfnis nach Einheitlichkeit.
Hier ist ein Beispiel für ein seltsames Packschema:
Freescale (Hersteller von Kfz-Mikrocontrollern) stellt ein Mikro her, das über einen Coprozessor für die Zeitverarbeitungseinheit verfügt (Google für eTPU oder TPU). Es hat zwei native Datengrößen, 8 Bit und 24 Bit, und behandelt nur Ganzzahlen.
Diese Struktur:
wird sehen, dass jeder U24 seinen eigenen 32-Bit-Block speichert, aber nur im höchsten Adressbereich.
Dies:
werden zwei U24s in benachbarten 32-Bit-Blöcken gespeichert, und die U8 werden in dem "Loch" vor der ersten U24 gespeichert
elementA
.Sie können den Compiler jedoch anweisen, alles in einen eigenen 32-Bit-Block zu packen, wenn Sie möchten. Es ist teurer im RAM, benötigt aber weniger Anweisungen für Zugriffe.
"Packen" bedeutet nicht "dicht packen" - es bedeutet nur ein Schema zum Anordnen von Elementen einer Struktur in Bezug auf den Offset.
Es gibt kein generisches Schema, es ist vom Compiler + der Architektur abhängig.
quelle
struct b
zu bewegen ,elementC
bevor eine der anderen Elemente, dann ist es nicht ein konformer C - Compiler. Eine Elementumlagerung ist in CDa Sie erwähnen
__attribute__((__packed__))
, gehe ich davon aus , dass Sie beabsichtigen, alle Auffüllungen in a zu entfernenstruct
(jedes Mitglied muss eine 1-Byte-Ausrichtung haben).... 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:
... 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:
... wo
.
Polsterung anzeigt. Jederx
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):Wir haben die Struktur effektiv abgerissen. In diesem Fall sieht die Speicherdarstellung folgendermaßen aus:
... und das:
... 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.
quelle
Nicht alle Architekturen sind gleich. Aktivieren Sie einfach die 32-Bit-Option auf einem Modul und sehen Sie, was passiert, wenn Sie denselben Quellcode und denselben Compiler verwenden. Die Bytereihenfolge ist eine weitere bekannte Einschränkung. Wenn Sie die Gleitkommadarstellung verwenden, werden die Probleme noch schlimmer. Das Senden von Binärdaten mit Packing ist nicht portierbar. Um es so zu standardisieren, dass es praktisch nutzbar ist, müssen Sie die C-Sprachspezifikation neu definieren.
Obwohl üblich, ist die Verwendung von Pack zum Senden von Binärdaten eine schlechte Idee, wenn Sie Sicherheit, Portabilität oder Langlebigkeit der Daten wünschen. Wie oft lesen Sie einen Binär-Blob aus einer Quelle in Ihr Programm ein? Wie oft überprüfen Sie, ob alle Werte korrekt sind, die ein Hacker oder eine Programmänderung nicht auf die Daten "bekommen" hat? Wenn Sie eine Prüfroutine programmiert haben, können Sie auch Import- und Exportroutinen verwenden.
quelle
Eine sehr häufige Alternative ist "Named Padding":
Dies setzt voraus, dass die Struktur nicht auf 8 Bytes aufgefüllt wird.
quelle