Bitfeld

In der Informationstechnik und Programmierung bezeichnet ein Bitfeld ein vorzeichenloses Integer, in dem einzelne Bits oder Gruppen von Bits aneinandergereiht werden. Es stellt eine Art Verbunddatentyp auf Bit-Ebene dar. Im Gegensatz dazu steht der primitive Datentyp, bei dem der Wert aus allen Stellen gemeinsam gebildet wird.

Eine verbreitete Verwendung ist die, bei der jede einzelne Binärstelle ein Flag repräsentiert. Dabei entspricht jede Stelle einer booleschen Variablen. Es können aber auch mehrere Stellen einen Wert bilden, z. B. können in einem Byte auch zwei Nibbles zusammengefasst sein oder, wie bei IPv4-Adressen, ein 32-Bit-Datenwort in (beispielsweise) 24-Bit-Netzwerk- und 8-Bit-Hostteil aufgeteilt sein.

Es findet sich auch die Sprechweise „Bitvektor“, ohne damit immer auszudrücken, dass das einzelne Bit durch Indizierung ansprechbar ist. Indizierung von Bits wird im Artikel Bitkette behandelt.

Verwendung

Bitfelder werden typischerweise bei der hardwarenahen oder der Systemprogrammierung eingesetzt. Hier dienen sie häufig dazu, bei Peripheriegeräten ein bestimmtes Verhalten einzustellen. Das liegt daran, dass bspw. das Aktivieren einer bestimmten Funktionalität in Hardware leicht über das Setzen einer einzigen Datenleitung, einem Bit, signalisiert werden kann. Um einfacher mit der Software oder anderen Geräten zu kommunizieren oder weil bspw. I/O-Ports nur in begrenzter Menge vorhanden sind, werden mehrere solcher Leitungen dann zu einem Datenwort zusammengefasst, dem Bitfeld.

Daneben werden Bitfelder aber auch bei der Anwendungsprogrammierung verwendet, wo eine Vielzahl an Parametern übergeben werden soll. Hier kann die Lesbarkeit des Quellcodes verbessert werden, wenn statt einer langen Parameterliste, bei der jeder Parameter explizit angegeben werden muss, nur ein Bitfeld mit den gewünschten Flags zusammengestellt wird.

Bitfelder zu verwenden um Speicherplatz einzusparen macht in der Anwendungsprogrammierung heutzutage nicht mehr so viel Sinn, außer in seltenen Fällen, wie z. B. bei eingebetteten Systemen, wenn Speicher eine extrem knappe Ressource ist, oder wenn die Bits in außerordentlich großer Zahl benötigt werden wie beim Sieb des Eratosthenes.[1] Es wird für boolesche Variablen meist ein ganzes Byte oder Wort verwendet. Auch ist der Zugriff auf einzelne Bits eines Datenworts oft ineffizienter als auf das gesamte Datenwort.

Beispiel in OpenGL

In OpenGL wird bspw. die Funktion glClear definiert, welche einen oder mehrere von vier Grafikpuffern löscht. Die Entwickler hätten nun vier Parameter definieren können, welche jeweils angeben, ob der Grafikpuffer gelöscht werden soll oder nicht. Der Funktionsaufruf würde folgendermaßen aussehen:

 void glClear(1, 1, 0, 0);

Dies ist aber weder effizient, da vier Variablen übergeben werden müssen, noch sehr leserlich. Daher wurde in der gl.h für jeden Puffer ein sogenanntes benanntes Flag definiert:

 #define GL_DEPTH_BUFFER_BIT               0x00000100
 #define GL_ACCUM_BUFFER_BIT               0x00000200
 #define GL_STENCIL_BUFFER_BIT             0x00000400
 #define GL_COLOR_BUFFER_BIT               0x00004000

Und für die Funktion wurde nur ein einzelner Parameter definiert:

 void glClear(GLbitfield mask); // GLbitfield ist ein typedef auf unsigned int

Der Funktionsaufruf sieht nun folgendermaßen aus:

 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

Verwendung in C und C#

In der Programmiersprache C ist es möglich, in Datenstrukturen Bitfelder zu definieren und damit kleinere Datentypen kompakt zu speichern. Solange die Daten ausschließlich über die Feldnamen adressiert werden, wird der Quellcode dadurch nicht abhängig vom Compiler oder vom Prozessor. Für Zugriffe auf Teile von Registern oder deserialisierte Daten gilt das nicht.

 struct struct-type-name {
     type [name1] : length;
     type [name2] : length;
     //...
     type [nameN] : length;
 } variable-list;

In der Programmiersprache C# ist es mittels der Flags Attribute möglich, eine Enumeration als Bitfeld zu deklarieren.[2] Außerdem stellt das .NET Framework die Datenstruktur BitArray zur Verfügung, welche einzelne Bits kompakt speichert und boolesche Operationen ermöglicht.

Zugriff

Der Zugriff auf ein einzelnes Bit, sowohl in testender als auch setzender Weise, wird von der Hardware ähnlich gut unterstützt wie der Zugriff auf ein Byte oder Wort – es genügt bei vielen Maschinen ein einziger Befehl. Die Unterstützung durch die Compiler ist aber häufig ähnlich wie beim Zugriff auf mehrere Bits, bei dem die Bitgruppe vor dem Vergleich oder der Manipulation durch eine Bitmaske aus dem Speicherwort „herauspräpariert“ werden muss. Als Bitmasken werden Bitfelder bezeichnet, deren Stellen selbst keine Information repräsentieren, sondern die dazu verwendet werden, um Bitfelder auszulesen oder zu manipulieren.

Ein in der Netzwerktechnik verbreitetes Beispiel für eine Bitmaske ist die Netzmaske. Sie bestimmt, welche der führenden Bits einer IP-Adresse das Netzpräfix sind, d. h. welcher Adressbereich als internes und welcher als externes Netz beim Routen betrachtet wird.

Bit auslesen

Um ein oder mehrere Bits aus einem Bitfeld auszulesen wird es mit einer Bitmaske über die bitweise Und-Operation verknüpft. In der Bitmaske stehen an allen Stellen, die nicht betrachtet werden sollen eine „0“. Diese Stellen werden durch die UND-Verknüpfung ausgeblendet, d. h. auf Null gesetzt. Die Stellen, die betrachtet oder ausgelesen werden sollen enthalten eine „1“ in der Bitmaske und werden daher unverändert aus dem Bitfeld ins Ergebnis übernommen.

Beispiel

1-Bit:

    01001011 Bitfeld
AND 00001000 Bitmaske
-------------
=   00001000 Ergebnis

0-Bit:

    01001011 Bitfeld
AND 00000100 Bitmaske
-------------
=   00000000 Ergebnis

Das Ergebnis ist Null, wenn die Stellen der Bitmaske im Bitfeld ebenfalls Null sind. Ist mindestens eines davon auf „1“ gesetzt, sind die entsprechenden Bits auch im Ergebnis „1“, d. h. das Ergebnis ist ungleich Null.

Da Werte von Null in den meisten Programmiersprachen zu einem logischen false und Werte ungleich Null zu einem logisch true ausgewertet werden, kann nun das Ergebnis leicht mit einer bedingten Verzweigung daraufhin überprüft werden, ob eines der Bits der Bitmaske im Feld gesetzt war:

if (Ergebnis)
   // Bit gesetzt
else
   // Bit nicht gesetzt

Bits setzen

Sollen Bits im Bitfeld einen bestimmten Wert annehmen, müssen die entsprechenden Stellen im Bitfeld auf „1“ und alle anderen auf „0“ gesetzt werden. Um die Stellen auf „1“ zu setzen, muss das Bitfeld mit der Bitmaske addiert oder über ein bitweises Oder verknüpft werden. Umgekehrt erhält man an den gewünschten Stellen eine „0“ wenn man Feld und Maske über ein NAND verknüpft, also zuerst die Maske invertiert und sie dann über eine Und-Operation mit dem Feld verknüpft.

Beispiel

Setzen von Bits auf „1“:

    01001011 Bitfeld
OR  00000100 Bitmaske
-------------
=   01001111 Ergebnis

Setzen von Bits auf „0“:

NOT 00001000 Bitmaske
-------------
=   11110111 invertierte Bitmaske
AND 01001011 Bitfeld
-------------
=   01000011 Ergebnis

Bits umschalten

Sollen einzelne Bits umgeschaltet (englisch to toggle) bzw. invertiert werden, setzt man die entsprechenden Stellen in der Bitmaske auf „1“ und alle anderen auf „0“ und verknüpft Bitfeld und Bitmaske über ein exklusives Oder.

    01001011 Information
XOR 00000110 Bitmaske
-------------
=   01001101 Ergebnis

Bitmasken zusammenfassen

Bitmasken können zusammengefasst werden, z. B. um in einer Operation mehrere Bits auf einmal zu überprüfen oder zu setzen. Dazu addiert man lediglich die einzelnen Bitmasken oder verknüpft sie über ein bitweises Oder.

    00000001 Bitmaske1
OR  00000010 Bitmaske2
-------------
    00000011 zusammengefasste Bitmaske

Siehe auch

Einzelnachweise

  1. Die Verwendung von Bitfeldern beliebiger Ausdehnung und mit einer Indexvariablen wird im Artikel Bitkette ausführlicher behandelt.
  2. http://msdn.microsoft.com/de-de/library/system.flagsattribute.aspx