wake-up-neo.com

C / C ++: Bitfeldreihenfolge und -ausrichtung erzwingen

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, wie 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 so im Speicher angeordnet, wie sie angezeigt werden. Message.version waren die ersten 3 Bits im Puffer und Message.type gefolgt. Wenn ich für verschiedene Compiler gleichwertige Struktur-Packoptionen finde, ist dies plattformübergreifend?

80
dewald

Nein, es wird nicht vollständig portierbar 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 Zuweisung von Bitfeldern innerhalb einer Einheit (hoch zu niedrig oder niedrig zu hoch) ist implementierungsdefiniert."

Sogar ein einzelner Compiler kann das Bitfeld abhängig von der Endianze der Zielplattform unterschiedlich auslegen.

95
Stephen Canon

Bitfelder sind von Compiler zu Compiler sehr unterschiedlich, sorry.

Bei GCC legen Big-Endian-Maschinen das Bit-Big-End zuerst und Little-Endian-Maschinen das Bit-Little-End zuerst an.

K & R sagt: "Benachbarte [Bit-] Feldmitglieder von Strukturen werden in einer implementierungsabhängigen 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 padded. Ein unbenanntes Feld der Breite 0 erzwingt dieses Auffüllen ... "

Wenn Sie ein maschinenunabhängiges binäres Layout benötigen, müssen Sie dies daher selbst tun.

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

42
Joshua

Bitfelder sollten vermieden werden - sie sind nicht sehr portabel zwischen Compilern, selbst für dieselbe Plattform. Aus der C99-Norm 6.7.2.1/10 - "Struktur- und Verbindungsspezifizierer" (es gibt einen ähnlichen Wortlaut in der C90-Norm):

Eine Implementierung kann eine beliebige adressierbare Speichereinheit zuweisen, die groß genug ist, um ein Bitfeld zu halten. 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 Platz vorhanden ist, wird implementierungsdefiniert, ob ein Bitfeld, das nicht passt, in die nächste Einheit eingefügt wird oder benachbarte Einheiten überlappt. Die Reihenfolge der Zuweisung von Bitfeldern innerhalb einer Einheit (hoch 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 Ganzzahlgrenze überspannt oder nicht, und Sie können nicht angeben, ob ein Bitfeld am unteren Ende der Ganzzahl oder am oberen Ende der Ganzzahl beginnt (dies ist unabhängig davon, ob es sich um einen Prozessor handelt) 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.

33
Michael Burr

endianness sprechen von Bytereihenfolgen, nicht von Bitreihenfolgen. Heutzutage, es ist zu 99% sicher, dass die Bitreihenfolgen festgelegt sind. Bei der Verwendung von Bitfeldern sollte jedoch die Endianze 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
9
pierrotlefou

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

Wenn Sie wirklich, wirklich identische binäre Informationen benötigen, müssen Sie Bitfelder mit Bitmasken erstellen - z. Sie verwenden eine vorzeichenlose Abkürzung (16 Bit) für Message und stellen dann als Darstellung der drei obersten Bits versionMask = 0xE000 ein.

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

Daher sind bei einigen Strukturen die Größen von Sparc und PowerPC identisch, bei 680x0 jedoch kleiner, und einige der Elemente befinden sich in unterschiedlichen Speicher-Offsets innerhalb der Struktur.

Dies war ein Problem bei einem Projekt, an dem ich gearbeitet habe, da ein Serverprozess, der auf Sparc ausgeführt wurde, einen Client abfragte und herausfand, dass es sich um ein Big-Endian-System handelte, und davon ausging, dass es nur binäre Strukturen im Netzwerk herausspritzen und der Client damit umgehen konnte. Und das hat auf PowerPC-Clients einwandfrei funktioniert und ist auf 680x0-Clients häufig abgestürzt. 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, als ich es tat.

6
Bob Murphy