Systemnahe Programmierung |
Ein Ziel bei der Entwicklung von C++ war es, C so erweitern, dass neue Programmierkonzepte unterstützt werden, aber die Effizienz und Leistungsfähigkeit von C nicht verloren geht. Eine der besonderen Fähigkeiten von C ist der Umgang mit systemnahen Strukturen. Immerhin wurde C dazu entwickelt, um das Betriebssystem UNIX damit zu schreiben. Diese Fähigkeiten sind in C++ dementsprechend erhalten geblieben.
Die Bit-Operationen haben die gleichen Namen und die gleichen Funktionen wie die booleschen Operationen, die bei Abfragen und Schleifen vorgestellt wurden. Die Bit-Operationen müssen allerdings auf einzelne Bits zugreifen können und erhalten deshalb in C/C++ auch einen etwas anderen Operator. Die folgende Tabelle zeigt diese Operatoren im Überblick.
[Bit-Operatoren]
| Operator | Funktion | Wirkung |
|---|---|---|
| & | AND | 1, wenn alle Operanden 1 sind |
| | | OR | 1, wenn mindestens ein Operand 1 ist |
| ~ (Tilde) | NOT | kippt jedes Bit |
| ^ | XOR | 1, wenn genau einer der beiden Operanden 1 ist |
Wenn zwei Werte mit Bit-Operatoren verknüpft werden, geschieht dies auf binärer Ebene. Um die Ergebnisse zu verstehen, müssen auch die beteiligten Werte binär dargestellt werden. Dazu eignen sich natürlich nur ganzzahlige Typen. In den folgenden Beispielen werden Variablen vom Typ unsigned char mit acht Bits verwendet.
~ 01101110
AND 00111100
------------
00101100
Hier werden die vier mittleren Bits maskiert. Auf ähnliche Weise ist es möglich, mit Hilfe der AND-Funktion ein einzelnes Bit in einem Byte zu löschen:
~ 00101101
AND 11110111
------------
00100101
Hier wurde das vierte Bit von rechts des ersten Operanden gelöscht. Dazu muss die Maske aus lauter Einsen bestehen und nur im vierten Bit von rechts eine 0 aufweisen.
if (RegisterInhalt & 16)
Wenn das fünfte Bit 0 ist, dann ist der Gesamtausdruck auch 0, also falsch. Ist das fünfte Bit gesetzt, ergibt die Maskierung mit 16 den Wert 16. Da dieser nicht 0 und damit nicht falsch ist, muss er wahr sein.
~ 00101101
XOR 00001000
------------
00100101
~ 00100101
OR 00001000
------------
00101101
Der OR-Operator wird beispielsweise bei den Datei-Operationen verwendet. Dort wurden beim Öffnen einer Datei die Zugriffsarten mit einem senkrechten Strich, also mit einem binären Oder, verknüpft:
fstream f(..., ios::out|ios::binary|ios::in);
Das deutet darauf hin, dass die Konstanten ios::out, ios::binary und ios::in binär kodiert sind. Beispielsweise könnte folgende Kodierung vorliegen:
[Beispielkodierung]
| Konstante | dezimal | binär | Bedeutung |
|---|---|---|---|
| ios::in | 1 | 0%00000001 | Datei zum Lesen öffnen |
| ios::out | 2 | 0%00000010 | Datei zum Schreiben öffnen |
| ios::binary | 4 | 0%00000100 | Datei ist keine Textdatei |
| ios::trunc | 8 | 0%00001000 | Datei wird beim Öffnen geleert |
| ios::app | 16 | 0%00010000 | Geschriebene Daten ans Ende anhängen |
Wollen Sie nun eine binäre Datei zum Lesen und Schreiben so öffnen, dass geschriebene Daten immer an das Ende angehängt werden, verknüpfen Sie die Konstanten mit ODER und übergeben diesen Wert als Parameter:
ios::in|ios::out|ios::binary|ios::app
Durch die Oder-Verknüpfung entsteht eine Zahl, die widerspiegelt, welche Optionen eingeschaltet sind und welche nicht.
ios::in 0%00000001 ios::out 0%00000010 ios::binary 0%00000100 ios::app 0%00010000 ODER: 0%00010111
Mit einem Byte können Sie so bereits acht Optionen transportieren. Wenn Sie eine long-Variable verwenden, können es bis zu 32 Optionen sein.
wert = 24 >> 1;
Die Operation bewirkt, dass die Zahl 24 binär um 1 Bit nach rechts verschoben wird. Das Ergebnis dieser Operation ist 12. Dazu betrachten wir 24 zunächst in Binärdarstellung:
2410 = 24 + 23 = 000110002
Wird 00011000 bitweise um eins nach rechts verschoben, ergibt sich der Wert 00001100. Das ist in Dezimaldarstellung die Zahl 12:
000011002 = 23 + 22 = 810 + 410 = 1210
Der Wert hinter dem Shift-Operator gibt an, um wie viele Bits der Wert geschoben wird. Wie Sie oben gesehen haben, werden die frei werdenden Bits links mit Nullen aufgefüllt.
Der Operator << schiebt die Bits nach links, arbeitet ansonsten genau wie der Operator >>. Auch hier werden die frei werdenden Bits mit Nullen aufgefüllt.
wert = 12 << 2;
Das Ergebnis würde 48 sein. Hier wird um zwei Bitstellen geschoben. Das Schieben um ein Bit ergibt 24. Ein weiteres Schieben verdoppelt das Ergebnis wiederum auf 48. Das Schieben um eine Stelle nach links entspricht also einer Multiplikation mit 2, ist aber durch den Prozessor wesentlich effizienter zu realisieren als eine Multiplikation. So liegt es nahe, Multiplikationen und Divisionen mit Zweierpotenzen in zeitkritischen Umgebungen mit Hilfe des Shift-Operators durchzuführen. Einige Compiler führen diese Optimierung allerdings selbst durch, so dass Sie dadurch vielleicht keinen Zeitgewinn erreichen.
[Zugriff auf den Druckerstatus]
unsigned char *DruckerStatusRegister;
unsigned char;
DruckerStatusRegister = 0x379;
Status = *DruckerStatusRegister;
if (Status & 0%10000000)
{
// Drucker ist frei
}
else
{
// Drucker ist BUSY
}
struct tRegister
{
unsigned int Target:3;
unsigned int reserved1:1;
unsigned int busy:1;
unsigned int conditionmet:1;
unsigned int check:1;
unsigned int reserved2:1;
} StatusByte;
Dieses Statusbyte modelliert das Register eines Controllers. Es enthält in den höchsten drei Bits das Target. Es folgt ein reserviertes Bit. Danach folgen je ein Bit für busy, condition met und check. Das niedrigstwertige Bit ist wieder reserviert. Tatsächlich wird für diese Struktur nur ein Byte benötigt. Das Ziel ist aber weniger das platzsparende Abspeichern, sondern das Abbilden von Registerstrukturen.
Sie können auf die Bestandteile der Bitstruktur wie bei einer üblichen Struktur zugreifen:
StatusByte.Target = 5;
Der Präprozessor ist eine Compilerstufe, die vor der eigentlichen Übersetzung durch den Quelltext geht und ihn vorübersetzt. Der Präprozessor ist darauf spezialisiert, textuelle Ersetzungen durchzuführen.
#ifdef __unix__
fork();
#endif
Auch Besonderheiten von C++ können Sie auf diese Weise ausgrenzen. In der Datei stdlib.h ist das Makro __cplusplus definiert, wenn es sich um einen C++-Compiler handelt.
#ifdef __cplusplus
template <class T> T min(T a, T b) { return a<b?a:b; }
#else
#define min(a,b) a>b?b:a
#endif
Die Compiler-Hersteller codieren ihre Compiler-Versionen in Makros.
So verwendet der Borland C++ Builder das Makro __BCPLUSPLUS__, um
seine Versionsnummer zu kodieren. Das kann ausgenutzt werden, wenn Mechanismen
verwendet werden sollen, die nur in neueren Versionen verfügbar sind.
#if __BCPLUSPLUS__ >= 0x530
// läuft unter C++ Builder ab Version 3.0
#endif
Sie können diesen Mechanismus auch verwenden, um kurzfristig Code auszugrenzen, der aber später vielleicht wieder gebraucht wird. Dann prüfen Sie auf einen Namen, der vermutlich nicht definiert ist. Im Beispiel wird das Wort HUHU verwendet.
#ifdef HUHU
// Hier steht Experimentalcode, der so lange
// ausgeblendet ist, bis jemand HUHU definiert.
/* mit vielen Kommentaren */
#endif
Es gibt auch eine Anweisung #ifndef, deren Block nur dann übersetzt wird, wenn ein Name nicht definiert ist. Den Befehl #ifndef finden Sie häufiger in Header-Dateien, um einen Schutz gegen doppeltes Einbinden zu realisieren. Dazu wird ein Name für jede Header-Datei verwandt. Es bietet sich dabei an, den Namen der Header-Datei zu verwenden. Wenn dieser Name noch nicht definiert wurde, wird der Block betreten. Dort wird als Erstes genau dieser Name definiert, so dass beim zweiten Einbinden dieser Datei der Inhalt nicht mehr gelesen wird.
#ifndef HEADERFILE_H #define HEADERFILE_H // Hier ist der Code der Header-Datei #endif // als letzte Zeile der Header-Datei
|
Diese Seite basiert auf Inhalten aus dem Buch
Arnold Willemer: Einstieg in C++ Mit freundlicher Genehmigung und Unterstützung des Verlags galileo computing |
| Informatik-Ecke Einstieg in C++ |
(C) Copyright 2005 Arnold Willemer
|