C / C ++: Bitfeldreihenfolge und -ausrichtung erzwingen


85

Ich habe gelesen, dass die Reihenfolge der Bitfelder innerhalb einer Struktur plattformspezifisch ist. Was passiert, wenn ich verschiedene compilerspezifische Verpackungsoptionen verwende? Werden diese Garantiedaten in der richtigen Reihenfolge gespeichert, in der sie geschrieben wurden? Beispielsweise:

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

Auf einem Intel-Prozessor mit dem GCC-Compiler wurden die Felder wie gezeigt im Speicher angeordnet. Message.versionwar die ersten 3 Bits im Puffer und Message.typefolgte. Wenn ich für verschiedene Compiler äquivalente Strukturpackoptionen finde, ist dies plattformübergreifend?


17
Da ein Puffer eine Menge von Bytes und keine Bits ist, sind "die ersten 3 Bits im Puffer" kein genaues Konzept. Würden Sie die 3 Bits niedrigster Ordnung des ersten Bytes als die ersten 3 Bits oder die 3 Bits höchster Ordnung betrachten?
Café

2
Beim Transit im Netzwerk erweisen sich "Die ersten 3 Bits im Puffer" als sehr gut definiert.
Joshua

2
@Joshua IIRC, Ethernet überträgt das niedrigstwertige Bit jedes Bytes zuerst (weshalb die Broadcast - Bit ist , wo es ist).
tc.

Wenn Sie "portabel" und "plattformübergreifend" sagen, was meinen Sie damit? Die ausführbare Datei greift unabhängig vom Zielbetriebssystem korrekt auf die Reihenfolge zu - oder - der Code wird unabhängig von der Toolchain kompiliert?
Garet Claborn

Antworten:


101

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

Selbst ein einzelner Compiler kann das Bitfeld beispielsweise je nach Endianness der Zielplattform unterschiedlich anordnen.


Ja, der GCC stellt zum Beispiel ausdrücklich fest, dass Bitfelder gemäß dem ABI angeordnet sind, nicht gemäß der Implementierung. Es reicht also nicht aus, nur auf einem einzigen Compiler zu bleiben, um die Bestellung zu garantieren. Auch die Architektur muss überprüft werden. Eigentlich ein Albtraum für Portabilität.
underscore_d

10
Warum hat der C-Standard keine Bestellung für Bitfelder garantiert?
Aaron Campbell

6
Es ist schwierig, die "Reihenfolge" von Bits innerhalb von Bytes konsistent und portabel zu definieren, geschweige denn die Reihenfolge von Bits, die Bytegrenzen überschreiten können. Jede Definition, auf die Sie sich festlegen, entspricht nicht einer beträchtlichen Menge der vorhandenen Praxis.
Stephen Canon

2
Implementierungsdefiniert ermöglicht eine plattformspezifische Optimierung. Auf einigen Plattformen kann das Auffüllen zwischen den Bitfeldern den Zugriff verbessern. Stellen Sie sich vier Sieben-Bit-Felder in einem 32-Bit-Int vor: Das Ausrichten bei jedem 8. Bit ist eine signifikante Verbesserung für Plattformen mit Byte-Lesevorgängen.
Peterchen


45

Bitfelder variieren stark von Compiler zu Compiler, sorry.

Mit GCC legen Big-Endian-Maschinen zuerst das große Ende und die kleinen Endian-Maschinen zuerst das kleine Ende an.

K & R sagt: "Benachbarte [Bit-] Feldmitglieder von Strukturen werden in implementierungsabhängiger Richtung in implementierungsabhängige Speichereinheiten gepackt. Wenn ein Feld, das einem anderen Feld folgt, nicht passt ... kann es zwischen Einheiten aufgeteilt werden oder die Einheit kann es sein." gepolstert. Ein unbenanntes Feld mit der Breite 0 erzwingt diese Polsterung ... "

Wenn Sie ein maschinenunabhängiges Binärlayout benötigen, müssen Sie dies daher 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 Sie bereits für GCC entdeckt haben.


Wird K & R wirklich als nützliche Referenz angesehen, da es sich um eine Vorstandardisierung handelte und (ich nehme an?) Wahrscheinlich in vielen Bereichen abgelöst wurde?
underscore_d

1
Mein K & R ist Post-ANSI.
Joshua

1
Das ist peinlich: Ich wusste nicht, dass sie eine Post-ANSI-Revision veröffentlicht hatten. Mein Fehler!
underscore_d

34

Bitfelder sollten vermieden werden - sie sind selbst für dieselbe Plattform nicht sehr portabel zwischen Compilern. aus der C99-Norm 6.7.2.1/10 - "Struktur- und Vereinigungsspezifizierer" (die C90-Norm enthält ähnliche Formulierungen):

Eine Implementierung kann jede adressierbare Speichereinheit zuweisen, die groß genug ist, um ein Bitfeld aufzunehmen. Wenn noch genügend Platz vorhanden ist, wird ein Bitfeld, das unmittelbar auf ein anderes Bitfeld in einer Struktur folgt, in benachbarte Bits derselben Einheit gepackt. Wenn nicht genügend Speicherplatz vorhanden ist, wird implementiert, ob ein nicht passendes Bitfeld in die nächste Einheit eingefügt wird oder benachbarte Einheiten überlappt. Die Reihenfolge der Zuordnung von Bitfeldern innerhalb einer Einheit (von hoher zu niedriger Ordnung oder von niedriger zu hoher Ordnung) 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 (dies ist unabhängig davon, ob der Prozessor dies ist Big-Endian oder Little-Endian).

Bevorzugen Sie Bitmasken. Verwenden Sie Inlines (oder sogar Makros), um die Bits zu setzen, zu löschen und zu testen.


2
Die Reihenfolge der Bitfelder kann zur Kompilierungszeit bestimmt werden.
Greg A. Woods

8
Bitfelder werden auch sehr bevorzugt, wenn Bitflags behandelt werden, die keine externe Darstellung außerhalb des Programms haben (dh auf der Festplatte oder in Registern oder im Speicher, auf den andere Programme zugreifen usw.).
Greg A. Woods

1
@ GregA.Woods: Wenn dies wirklich der Fall ist, geben Sie bitte eine Antwort, die beschreibt, wie. Ich konnte nichts anderes als Ihren Kommentar finden, als ich danach
googelte

1
@ GregA.Woods: Sorry, hätte schreiben sollen, auf welchen Kommentar ich mich bezog. Ich meinte: Sie sagen, dass "die Reihenfolge der Bitfelder zur Kompilierungszeit bestimmt werden kann." Ich kann nichts darüber und wie es geht.
Mozzbozz

2
@mozzbozz Schauen Sie sich planix.com/~woods/projects/wsg2000.c an und suchen Sie nach Definitionen und Verwendung von _BIT_FIELDS_LTOHund_BIT_FIELDS_HTOL
Greg A. Woods

11

Endianness spricht von Bytereihenfolgen, nicht von Bitreihenfolgen. Heutzutage ist es zu 99% sicher, dass Bitreihenfolgen festgelegt sind. Bei der Verwendung von Bitfeldern sollte jedoch die Endianness berücksichtigt werden. Siehe das folgende Beispiel.

#include <stdio.h>

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
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a

5
Die Ausgabe von a und b zeigt an, dass Endianness immer noch über Bitreihenfolgen UND Bytereihenfolgen spricht.
Windows-Programmierer

wunderbares Beispiel mit Bitreihenfolge und Bytereihenfolgeproblemen
Jonathan

1
Haben Sie den Code tatsächlich kompiliert und ausgeführt? Die Werte für "a" und "b" erscheinen mir nicht logisch: Sie sagen im Grunde, dass der Compiler die Halbbytes aufgrund der Endianness innerhalb eines Bytes austauschen wird. Im Fall von "d" sollten Endiannen die Bytereihenfolge in char-Arrays nicht beeinflussen (vorausgesetzt, char ist 1 Byte lang). Wenn der Compiler dies tun würde, könnten wir ein Array nicht mit Zeigern durchlaufen. Wenn Sie andererseits ein Array von zwei 16-Bit-Ganzzahlen verwendet haben, z. B.: Uint16 data [] = {0x1234,0x5678}; dann wäre d in Little-Endian-Systemen definitiv 0x7856.
Krauss

6

Wahrscheinlich die meiste Zeit, aber setzen Sie die Farm nicht darauf, denn wenn Sie sich irren, verlieren Sie viel.

Wenn Sie wirklich, wirklich identische Binärinformationen benötigen, müssen Sie Bitfelder mit Bitmasken erstellen - z. B. verwenden Sie einen vorzeichenlosen Kurzschluss (16 Bit) für die Nachricht und erstellen dann versionMask = 0xE000, um die drei obersten Bits darzustellen.

Es gibt ein ähnliches Problem bei der Ausrichtung innerhalb von Strukturen. Zum Beispiel sind Sparc-, PowerPC- und 680x0-CPUs alle Big-Endian-CPUs, und die übliche Standardeinstellung für Sparc- und PowerPC-Compiler besteht darin, Strukturelemente an 4-Byte-Grenzen auszurichten. Ein Compiler, den ich für 680x0 verwendet habe, ist jedoch nur an 2-Byte-Grenzen ausgerichtet - und es gab keine Option, die Ausrichtung zu ändern!

Bei einigen Strukturen sind die Größen bei Sparc und PowerPC identisch, bei 680 x 0 jedoch kleiner, und einige der Mitglieder befinden sich in unterschiedlichen Speicherversätzen innerhalb der Struktur.

Dies war ein Problem bei einem Projekt, an dem ich gearbeitet habe, da ein auf Sparc ausgeführter Serverprozess einen Client abfragte und herausfand, dass es sich um Big-Endian handelte, und davon ausging, dass er nur binäre Strukturen im Netzwerk herausspritzen und der Client damit umgehen konnte. Und das funktionierte gut auf PowerPC-Clients und stürzte auf 680x0-Clients stark ab. Ich habe den Code nicht geschrieben und es hat eine ganze Weile gedauert, bis ich das Problem gefunden habe. Aber es war einfach zu reparieren, sobald ich es tat.


0

Vielen Dank an @BenVoigt für Ihren sehr nützlichen Kommentar

Nein, sie wurden erstellt, um Speicherplatz zu sparen.

Linux Quelle tut ein bisschen Feld verwenden , um eine externe Struktur anzupassen: /usr/include/linux/ip.h hat diesen Code für das erste Byte einer IP - Datagramms

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

Jedoch in Anbetracht Ihres Kommentars Ich gebe es auf, das für den Multi-Byte - Bit - Feld , um Arbeit zu bekommen frag_off .


-9

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


4
Ich denke, es ist falsch zu behaupten, dass es dumm ist, Bitfelder zu verwenden, da es eine sehr saubere Möglichkeit bietet, Hardwareregister darzustellen, die es modelliert hat, in C.
trondd

13
@trondd: Nein, sie wurden erstellt, um Speicherplatz zu sparen. Bitfelder sind nicht dazu gedacht, externen Datenstrukturen wie speicherabgebildeten Hardwareregistern, Netzwerkprotokollen oder Dateiformaten zuzuordnen. Wenn sie auf externe Datenstrukturen abgebildet werden sollten, wäre die Verpackungsreihenfolge standardisiert worden.
Ben Voigt

2
Die Verwendung von Bits spart Speicher. Die Verwendung von Bitfeldern erhöht die Lesbarkeit. Die Verwendung von weniger Speicher ist schneller. Die Verwendung von Bits ermöglicht komplexere atomare Operationen. In unseren Anwendungen in der realen Welt besteht Bedarf an Leistung und komplexen atomaren Operationen. Diese Antwort würde bei uns nicht funktionieren.
Johnnycrash

@BenVoigt ist wahrscheinlich wahr, aber wenn ein Programmierer bereit ist zu bestätigen, dass die Reihenfolge seines Compilers / ABI den Anforderungen entspricht, und die schnelle Portabilität entsprechend zu opfern, kann er diese Rolle sicherlich erfüllen. Was 9 * betrifft, welche maßgebliche Masse von "Real-World-Codierern" betrachtet jede Verwendung von Bitfeldern als "unprofessionell / faul / dumm" und wo haben sie dies angegeben?
underscore_d

2
Die Verwendung von weniger Speicher ist nicht immer schneller. Es ist oft effizienter, mehr Speicher zu verwenden und Nachlesevorgänge zu reduzieren, und der Prozessor / Prozessor-Modus kann dies noch wahrer machen.
Dave Newton
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.