Bitfelder
Nach oben zu Strukturen - Index
Bitfelder sind festgelegte Bit-Nummern mit oder ohne Bezeichner. Sie bieten eine Möglichkeit, Strukturen, Varianten und Klassen in Komponenten von benutzerdefinierter Größe zu unterteilen und diese zu benennen.
Bitfelder deklarieren
Sie geben die Größe des Bitfeldes und den optionalen Bezeichner wie folgt an:
type-specifier <bitfield-id> : width;
In C++ sind als Typ-Bezeichner bool, char, unsigned char, short, unsigned short, long, unsigned long, int, unsigned int, __int64 oder unsigned __int64 erlaubt. In strengem ANSI C sind die Typbezeichner: int oder unsigned int.
Die Größe eines Bitfeldes muss angegeben werden und einen konstanten Integerwert haben. In C++ können Bitfelder von beliebiger Größe deklariert werden. In ANSI C darf die Größe eines Bitfeldes die Größe des deklarierten Typs nicht überschreiten. Ein Bitfeld der Größe 0 wird der nächsten Zuweisungseinheit zugerechnet.
Wenn man den Bezeichner des Bitfeldes weglässt, wird die angegebene Größe des Bitfeldes angelegt, aber das Feld ist nicht verwendbar. Damit können Bitmuster wie z.B. Hardware-Register nachgebildet werden, in denen einige Bits ungenutzt sind.
Bitfelder können nur in Strukturen, Varianten und Klassen verwendet werden. Auf Bitfelder wird genauso zugegriffen wie auf alle anderen Elemente ( . und ->).
Einschränkungen bei der Verwendung von Bitfeldern
Wenn Sie Bitfelder verwenden, müssen Sie folgende Punkte beachten:
- Der Code ist nicht portierbar, da die Organisation von Bits innerhalb von Bytes und von Bytes innerhalb von Worten maschinenabhängig ist.
- Bitfelder haben keine Adresse. Der Ausdruck &mystruct.x ist unzulässig, wenn x ein Bitfeld-Bezeichner ist, da es keine Garantie dafür gibt, dass mystruct.x an einer Byte-Adresse liegt.
- Bitfelder dienen dazu, mehr Variablen in weniger Speicherplatz unterzubringen. Dafür muss der Compiler jedoch zusätzlichen Code erzeugen, um diese Variablen manipulieren zu können. Dies geht zu Lasten der Codegröße und der Ausführungsgeschwindigkeit.
Wegen dieser Nachteile wird im allgemeinen von der Verwendung von Bitfeldern abgeraten. Eine Ausnahme stellt die Low-Level-Programmierung dar. Die empfohlene Alternative für Bit-Variablen ist die Verwendung von define. Ein Beispiel:
#define Nothing 0x00
#define bitOne 0x01
#define bitTwo 0x02
#define bitThree 0x04
#define bitFour 0x08
#define bitFive 0x10
#define bitSix 0x20
#define bitSeven 0x40
#define bitEight 0x80
Damit können Sie beispielsweise folgenden Code schreiben:
if (flags & bitOne) {...} // wenn bitOne gesetzt ist
flags |= bitTwo; // bitTwo setzen
flags &= ~bitThree; // bitThree löschen
In ähnlicher Weise können Bitfelder von beliebiger Größe simuliert werden.
Füllen von Bitfeldern
Wenn in C++ die Größe eines Bitfeldes die Größe seines Typs überschreitet, fügt der Compiler einen Füllbereich hinzu, der der benötigten Größe abzüglich der Typgröße des Bitfeldes entspricht. Die Deklaration
struct mystruct
{
int i : 40;
int j : 8;
};
erstellt einen 32-Bit-Speicher für 'i', ein 8-Bit-Füllung und 8-Bit-Speicher für 'j'. Um den Zugang zu optimieren, interpretiert der Compiler 'i' als reguläre int-Variable und nicht als Bitfeld.
Layout und Ausrichtung
Bitfelder werden ohne Rücksicht auf ein etwaiges Vorzeichen in Gruppen von Bitfeldern desselben Typs unterteilt. Dabei wird jede Gruppe so ausgerichtet, wie es der aktuellen Ausrichtung des Typs der Gruppenelemente entspricht. Diese Ausrichtung richtet sich nach dem Typ AND und nach der allgemeinen Ausrichtung (die über die Option –aN) festgelegt wird). Innerhalb jeder Gruppe legt der Compiler die Bitfelder in Bereichen ab, deren Größe der Größe des Typs der Bitfelder entspricht. Dabei darf allerdings kein Bitfeld die Grenze zwischen zwei Bereichen überschreiten. Die gesamte Struktur wird so ausgerichtet, wie es die aktuelle Ausrichtung vorsieht.
Ein Beispiel für Layout, Füllen und Ausrichtung von Bitfeldern
In der folgenden C++ Deklaration enthält my_struct sechs Bitfelder von drei verschiedenen Typen: int, long und char:
struct my_struct
{
int one : 8;
unsigned int two : 16;
unsigned long three : 8;
long four : 16;
long five : 16;
char six : 4;
};
Die Bitfelder one und two werden in einem Bereich von 32 Bit abgelegt.
Als nächstes fügt der Compiler, falls erforderlich, einen Füllbereich ein, der sich aus der aktuellen Ausrichtung und aus dem Typ von three ergibt (wegen des unterschiedlichen Typs der Variablen two und three). Wenn die aktuelle Ausrichtung beispielsweise -a1 ist (Ausrichtung an Byte-Grenzen), ist kein Füllbereich erforderlich. Ist die Ausrichtung dagegen -a4 (Ausrichtung an Dword-Grenzen), so wird ein Füllbereich von 8 Bit eingefügt.
Die Variablen three, four und five sind alle vom Typ long. Die Variablen three und four werden in einem 32-Bit-Bereich abgelegt. Die Variable five findet hier keinen Platz mehr, da dies einen Bereich von 40 Bit ergeben würde, also mehr als die 32 Bit, die für den Typ long erlaubt sind. Beim Anlegen eines neuen Bereichs für five würde der Compiler keinen Füllbereich einfügen, wenn die aktuelle Ausrichtung -a1 ist, oder einen 8 Bit großen Füllbereich, wenn die aktuelle Ausrichtung -a4 ist.
Bei der Variable six ändert sich der Typ erneut. Da char immer an Byte-Grenzen ausgerichtet wird, ist kein Füllbereich erforderlich. Um die Ausrichtung der gesamten Struktur zu erzwingen, füllt der Compiler den letzten Bereich mit 4 Bits auf, wenn die aktuelle Ausrichtung -a1 ist, oder mit 12 Bits, wenn die aktuelle Ausrichtung -a4 ist.
Bei der Ausrichtung an Byte-Grenzen (-a1) ist my_struct insgesamt 9 Byte, bei der Ausrichtung an Dword-Grenzen (-a4) 12 Byte groß.
Die besten Ergebnisse mit Bitfeldern erzielen Sie, wenn Sie
- Bitfelder nach ihrem Typ sortieren.
- sicherstellen, dass sie innerhalb ihrer Bereiche abgelegt werden, indem Sie sie so sortieren, dass kein Bitfeld eine Bereichsgrenze überschreitet.
- sicherstellen, dass die Struktur optimal gefüllt ist.
Eine weitere Empfehlung ist die Verwendung von "#pragma option -a1", um für diese Struktur die Ausrichtung an Byte-Grenzen zu erzwingen. Wenn Sie wissen wollen, wie groß Ihre Struktur ist, fügen Sie "#pragma sizeof(mystruct)" hinzu.
Bitfelder mit Vorzeichen
Bei einem Typ mit Vorzeichen, der ein Bit groß ist, sind die möglichen Werte 0 oder –1. Bei einem Typ ohne Vorzeichen, der ein Bit groß ist, sind die möglichen Werte 0 oder 1. Beachten Sie, dass ein Bitfeld mit Vorzeichen, dem Sie den Wert 1 zuweisen, als -1 (minus 1) ausgewertet wird.
Wenn Sie die Werte true und false einem ein Bit großen Bitfeld mit Vorzeichen zuweisen, dürfen Sie das Feld nicht auf true prüfen, da diese Felder nur die Werte 0 und -1 aufnehmen können, die mit true und false nicht kompatibel sind. Prüfen Sie in diesem Fall auf ungleich 0.
Bei vorzeichenlosen Variablen aller Typen (natürlich auch bool) funktioniert die Prüfung auf true wie gewohnt.
Dazu ein Beispiel:
struct mystruct
{
int flag : 1;
} M;
int testing()
{
M.flag = true;
if(M.flag == true)
printf("success");}
funktioniert NICHT, während
struct mystruct
{
int flag : 1;
} M;
int testing()
{
M.flag = true;
if(M.flag)
printf("success");
}
wie gewohnt funktioniert.
Hinweise zur Kompatibilität
Zwischen unterschiedlichen Versionen des Compilers oder aus Gründen der Kompatibilität mit anderen Compilern können Änderungen an der voreingestellten Ausrichtung vorgenommen werden. Daraus können sich Änderungen an der Ausrichtung von Bitfeldern ergeben. Es gibt daher keine Garantie dafür, dass die Ausrichtung von Bitfeldern zwischen verschiedenen Versionen des Compilers konsistent bleibt. Um die Abwärtskompatibilität von Bitfeldern in Ihrem Code zu sicherzustellen, überprüfen Sie, ob die verwendeten Strukturen die erwartete Größe haben.
In Anlehnung an die C und C++ Sprachspezifikationen ist die Ausrichtung und die Speicherung von Bitfeldern implementationsabhängig. Daher können Compiler Bitfelder in unterschiedlicher Weise ausrichten und speichern. Wenn Sie das Layout von Bitfeldern vollständig kontrollieren wollen, empfiehlt es sich, eigene Bitfelder zu deklarieren und die Routinen, die darauf zugreifen, selbst zu schreiben.