C / C ++: Bitfeldreihenfolge und Ausrichtung erzwingen

Ich habe gelesen, dass die Reihenfolge der Bitfelder innerhalb einer Struktur plattformspezifisch ist. Was ist, wenn ich verschiedene compilerspezifische Packoptionen verwende, werden diese Garantiedaten in der richtigen Reihenfolge gespeichert, während sie geschrieben werden? Beispielsweise:

struct Message { unsigned int version : 3; unsigned int type : 1; unsigned int id : 5; unsigned int data : 6; } __attribute__ ((__packed__)); 

Auf einem Intel-processor mit dem GCC-Compiler wurden die Felder wie gezeigt im Speicher abgelegt. Message.version war die ersten 3 Bits im Puffer und Message.type folgte. Wenn ich für verschiedene Compiler gleichwertige Packungsoptionen finde, ist das plattformübergreifend?

Nein, es wird nicht vollständig tragbar sein. Packoptionen für Strukturen sind Erweiterungen und sind selbst nicht vollständig portierbar. Darüber hinaus heißt es in C99 §6.7.2.1 Absatz 10: “Die Reihenfolge der Zuweisung von Bitfeldern innerhalb einer Einheit (hoch- zu niedrig oder niedrig zu hoch) ist implementierungsdefiniert.”

Selbst ein einzelner Compiler könnte das Bitfeld abhängig von der Endianz der Zielplattform beispielsweise unterschiedlich positionieren.

Bitfelder variieren stark von Compiler zu Compiler, sorry.

Bei GCC legen Big-Endian-Maschinen zuerst die Bits Big-End und Little-Endian-Maschinen die Bits mit dem kleinen Ende zuerst aus.

K & R sagt: “Benachbarte [Bit-] Feldelemente von Strukturen werden in implementierungsabhängige Speichereinheiten in einer implementierungsabhängigen Richtung gepackt. Wenn ein Feld, das einem anderen Feld folgt, nicht passt, kann es zwischen Einheiten aufgeteilt werden oder die Einheit kann sein gepolstert. Ein unbenanntes Feld der Breite 0 erzwingt diese Auffüllung … ”

Wenn Sie daher ein maschinenunabhängiges Binärlayout benötigen, müssen Sie dies selbst tun.

Diese letzte Aussage gilt auch für Nicht-Bitfelder aufgrund des Auffüllens – jedoch scheinen alle Compiler eine Möglichkeit zu haben, das Byte-Packen einer Struktur zu erzwingen, wie ich sehe, dass Sie bereits für GCC entdeckt wurden.

Bitfelder sollten vermieden werden – sie sind nicht sehr portierbar zwischen Compilern, selbst für die gleiche Plattform. aus dem C99-Standard 6.7.2.1/10 – “Struktur- und Gewerkschaftsspezifizierer” (es gibt einen ähnlichen Wortlaut in der C90-Norm):

Eine Implementierung kann irgendeine adressierbare Speichereinheit zuweisen, die groß genug ist, um ein Bitfeld zu halten. Wenn genügend Speicherplatz vorhanden ist, sollte ein Bitfeld, das unmittelbar einem anderen Bitfeld in einer Struktur folgt, in benachbarte Bits derselben Einheit gepackt werden. Wenn nicht genügend Speicherplatz vorhanden ist, wird die Implementierung definiert, ob ein Bitfeld, das nicht in die nächste Einheit passt oder benachbarte Einheiten überlappt. Die Reihenfolge der Zuordnung von Bitfeldern innerhalb einer Einheit (höher zu niedrig oder niedrig zu hoch) ist implementierungsdefiniert. Die Ausrichtung der adressierbaren Speichereinheit ist nicht spezifiziert.

Sie können nicht garantieren, ob ein Bitfeld eine int-Grenze “überspannt” oder nicht, und Sie können nicht angeben, ob ein Bitfeld am unteren Ende des int oder am oberen Ende des int beginnt (unabhängig davon, ob der processor ist) Big-Endian oder Little-Endian).

Bevorzugen Sie Bitmasken. Verwenden Sie Inlines (oder sogar Makros) zum Setzen, Löschen und Testen der Bits.

Endianness spricht von Byte-Befehlen, nicht von Bit-Befehlen. Heutzutage ist es zu 99% sicher, dass Bit-Bestellungen fest sind. Bei der Verwendung von Bitfeldern sollte jedoch die Endian-Zahl berücksichtigt werden. Siehe das Beispiel unten.

 #include  typedef struct tagT{ int a:4; int b:4; int c:8; int d:16; }T; int main() { char data[]={0x12,0x34,0x56,0x78}; T *t = (T*)data; printf("a =0x%x\n" ,t->a); printf("b =0x%x\n" ,t->b); printf("c =0x%x\n" ,t->c); printf("d =0x%x\n" ,t->d); return 0; } //- big endian : mips24k-linux-gcc (GCC) 4.2.3 - big endian a =0x1 b =0x2 c =0x34 d =0x5678 // - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2 a =0x2 b =0x1 c =0x34 d =0x7856 

Die meiste Zeit, wahrscheinlich, aber wette nicht die Farm darauf, denn wenn du falsch liegst, wirst du groß verlieren.

Wenn Sie wirklich, wirklich identische binäre Informationen benötigen, müssen Sie Bitfelder mit Bitmasken erstellen – z. B. verwenden Sie eine nicht signierte Abkürzung (16 Bit) für Message und machen dann Dinge wie VersionMask = 0xE000, um die drei obersten Bits darzustellen.

Es gibt ein ähnliches Problem mit der Ausrichtung innerhalb von Strukturen. Zum Beispiel sind Sparc-, PowerPC- und 680×0-CPUs alle Big-Endian-processoren, und der allgemeine Standard für Sparc- und PowerPC-Compiler besteht darin, Strukturelemente auf 4-Byte-Grenzen auszurichten. Ein Compiler, den ich für 680×0 verwendet habe, wurde jedoch nur auf 2-Byte-Grenzen ausgerichtet – und es gab keine Möglichkeit, die Ausrichtung zu ändern!

Für einige Strukturen sind die Größen auf Sparc und PowerPC identisch, aber kleiner auf 680×0, und einige der Mitglieder befinden sich in verschiedenen Speicher-Offsets innerhalb der Struktur.

Das war ein Problem mit einem Projekt, an dem ich arbeitete, weil ein Server-process, der auf Sparc ausgeführt wurde, einen Client abfragen und herausfinden würde, dass es Big-Endian war und davon ausging, dass es nur Binärstrukturen im Netzwerk ausspuckte. Und das funktionierte auf PowerPC-Clients gut und stürzte bei 680×0-Clients in hohem Maße ab. Ich habe den Code nicht geschrieben und es hat eine Weile gedauert, bis ich das Problem gefunden habe. Aber es war einfach zu reparieren, sobald ich es getan habe.

Die beste Antwort ist natürlich die Verwendung einer class, die Bitfelder als Stream liest / schreibt. Die Verwendung der C-Bit-Feldstruktur ist einfach nicht garantiert. Ganz zu schweigen davon, dass es unprofessionell / faul / dumm ist, dies in der realen Welt zu verwenden.