C++ Tutorial

Bitfelder

Bitfelder werden hauptsächlich eingesetzt, wenn auf die Peripherie eines Systems zugegriffen werden soll, z.B. auf einen Baustein für die Datenübertragung. Ein solcher Peripheriebausteinen wird in der Regel über entsprechende Bits in Registern angesteuert.

Definition

Ein Bitfeld wird wie folgt definiert:

struct [NAME]
{
   DTYP1 element1: BITS1 [INIT1];
   DTYP2 element2: BITS2 [INIT2];
   ...
} [bitVar1,...];

Eingeleitet wird ein Bitfeld durch das Schlüsselwort struct sowie einem optionalen Namen NAME für das Bitfeld. Anschließend folgen innerhalb einer geschweiften Klammer die einzelnen Elemente des Bitfeldes. Jedes Element des Bitfeldes besteht aus einem Datentyp DTYPx, einem Elementnamen elementx, einem Doppelpunkt und der Anzahl der Bits BITSx die das Element belegt. Optional kann einem Element ein Initialisierungswert INITx entweder per Zuweisung oder in Klammern mitgegeben werden.

Für die Datentypen der Elemente sind nur Integer-Datentypen, enums und der Datentyp bool zugelassen. Ist die Anzahl der Bits BITx größer als die Anzahl der Bits des Datentyps DTYPx, so bestimmt der Datentyp die Anzahl der Bits.

Im nachfolgenden Beispiel wird mithilfe eines Bitfeldes auf einen fiktiven Peripherie-Baustein zugegriffen. Peripherie-Bausteine besitzen meistens mehrere 8, 16, 32 oder 64 Bit breite Register, über denen die Funktionen des Bausteins durch einzelne Bits gesteuert werden. Nachfolgend der Aufbau eines fiktiven Timer-Bausteins. Er enthält drei 8-Bit breite Register, deren Bits verschiedene Funktionen steuern.

Im folgenden Beispiel wird mithilfe eines Bitfeldes auf einen fiktiven Peripherie-Baustein zugegriffen. Peripherie-Bausteine besitzen meistens mehrere 8, 16, 32 oder 64 Bit breite Register, über denen die Funktionen des Bausteins durch einzelne Bits gesteuert werden. Nachfolgend der Aufbau eines fiktiven Timer-Bausteins. Er enthält drei 8-Bit breite Register, deren Bits verschiedene Funktionen steuern.

Aufbau des fiktiven Peripherie-Bausteins:

Register
Bit-Nr.
Funktion
0
0
0 = Baustein gesperrt 1 = Baustein freigegeben
1
0 = Interrupt gesperrt 1 = Interrupt freigegeben
2
0 = einmaliger Interrupt 1 = periodischer Interrupt
3
nicht belegt
4..7
Vorteiler
1
0..7
Zähler
2
0
0 = kein Interrupt anstehend 1 = Interrupt anstehend
1..6
nicht belegt
7
Überlauf

Als Erstes werden die einzelnen Bits der Register durch einsprechende Bitfelder reg0 und reg2 abgebildet. Lediglich das Register 1 bildet hierbei eine Ausnahme, da es 8 Bit belegt und als unsigned char Element reg1 definiert werden kann.

Anschließend werden die zwei Bitfelder reg0 und reg2 sowie das unsigned char-Datum reg1 in einer übergeordneten Struktur Timer zusammengefasst (Strukturen werden später behandelt und dienen zum Zusammenfassen von Daten). Und da Peripherie-Bausteine sich in der Regel auf fixen Adressen befinden, wird ein entsprechender Strukturzeiger pTimer definiert und diesem die Adresse des ersten Registers des Bausteins zugewiesen.

1: // Abbildung des Peripherie-Bausteins
2: struct Timer
3: {
4:    // 1. Bitfeld fuer Register 0
5:    struct
6:    {
7:        bool enable            : 1;
8:        bool intEnable         : 1;
9:        bool intPeriod         : 1;
10:       bool                   : 1;
11:       unsigned char preScale : 4;
12:    } reg0;
13:    unsigned char reg1;
14:    // 2. Bitfeld fuer Register 2
15:    struct
16:    {
17:       bool pending  : 1;
18:       unsigned char : 6;
19:       bool overf    : 1;
20:    } reg2;
21: } *pTimer = (struct Timer*)0x0100;

Sehen Sie sich im Beispiel an, wie die nicht belegten Bits des Peripherie-Bausteins 'übersprungen' werden (Zeile 10 und 18). Um Füllbits zu definieren, werden nur der Datentyp und die Anzahl der Bits angegeben. Der Elementname kann hier entfallen.

Zugriff auf Bitfeld-Elemente

Der Zugriff auf die Elemente in einem Bitfeld erfolgt in der Weise, dass zuerst der Name der Bitfeld-Variable bzw. des Bitfeld-Zeigers angegeben wird, dann der Punktoperator bzw. Dereferenzierungsoperator und zum Schluss der Name des Bitfeld-Elements. Wird der Wert eines Bitfeld-Elements ausgelesen, wird er immer beginnend ab dem niederwertigsten Bit in der Zielvariable abgelegt. Beim Schreiben eines Werts in ein Bitfeld-Element ist zu beachten, dass überzählige Bits abgeschnitten werden. Wird z.B. einem Bitfeld-Element mit der Länge 4 Bits der Wert 0x1F (5 Bits!) zugewiesen, wird nur der Wert 0x0F abgelegt (die niederwertigen 4 Bits).

1: // Bitfeld-Definition
2: struct Timer
3: {
4:     ... // Definition wie vorhin beschrieben
5: } *pTimer = (struct Timer*)0x0100;
6:
7: // Zugriffe auf Bitfeld-Elemente ueber Zeiger
8: pTimer->reg0.enable = true;
9: auto error = pTimer->reg2.overf;

Besonderheiten von Bitfelder

Bitfelder weisen einige Besonderheiten auf:

  • Von einem Bitfeld-Element kann keine Adresse oder Referenz gebildet werden. Dies wäre ein Verweis auf ein Bit im Speicher!
  • Da keine Referenzen auf Bitfeld-Elemente gebildet werden können, können Bitfeld-Elemente nicht direkt eingelesen werden, da cin die Referenz auf das einzulesende Datum benötigen.
  • Von Bitfeld-Elementen kann kein Feld definiert werden, wohl aber von einem Bitfeld.
  • Und zum Schluss der wichtigste Punkt: Die Reihenfolge der einzelnen Bits in einem Bitfeld ist im C++-Standard nicht festgeschrieben, d.h. es nicht definiert, dass das erste Bit im Bitfeld das niederwertigste Bit im Speicher ist. Werden Bitfelder eingesetzt muss damit gerechnet werden, dass andere Compiler die Reihenfolge der Bits anders handhaben. Hier hilft nur ein Blick in die Beschreibung zum Compiler.

Copyright 2024 © Wolfgang Schröder
E-Mail mit Fragen oder Kommentaren zu dieser Website an: info@cpp-tutor.de
Impressum & Datenschutz