Ratgeber · 7 Min Lesezeit
Bitweise Operatoren: AND, OR, XOR und Shifts in der Praxis
Was AND, OR, XOR, NOT und die Shift-Operatoren wirklich tun, mit konkreten Anwendungen wie Flag-Sets, schnellen Vergleichen und Verschlüsselung.
In den meisten Programmiersprachen gibt es Operatoren, die direkt auf den einzelnen Bits einer Zahl arbeiten: &, |, ^, ~, <<, >>. Im Alltag moderner Anwendungs-Programmierung tauchen sie seltener auf, in System- und Performance-Code sind sie aber unverzichtbar. Hier ein Überblick mit konkreten Beispielen.
Die fünf Grundoperationen
AND (&) verknüpft jedes Bit-Paar nach der Regel: Ergebnis ist 1, wenn beide Bits 1 sind. Wirkt wie eine Maske, mit der man gezielt Bits ausblenden kann.
10110101
& 00001111
--------
00000101
OR (|) liefert 1, wenn mindestens eines der Bits 1 ist. Wird genutzt, um Bits zu setzen.
10110000
| 00001111
--------
10111111
XOR (^) liefert 1, wenn genau ein Bit 1 ist. Hat die schöne Eigenschaft, dass (a XOR b) XOR b = a. Wird zum Umschalten von Bits und in Verschlüsselung eingesetzt.
11110000
^ 10101010
--------
01011010
NOT (~) kehrt jedes Bit um. Aus 0 wird 1, aus 1 wird 0.
~ 11110000
--------
00001111
Shift Left (<<) verschiebt alle Bits nach links, rechts wird mit 0 aufgefüllt. Entspricht einer Multiplikation mit Zweierpotenzen: x << 3 ist dasselbe wie x * 8.
Shift Right (>>) verschiebt nach rechts. Bei vorzeichenlosen Zahlen wird links mit 0 aufgefüllt, bei vorzeichenbehafteten mit dem Vorzeichenbit (arithmetischer Shift, erhält das Vorzeichen). Entspricht einer Division durch Zweierpotenzen.
Anwendung 1: Flag-Sets
Viele APIs nutzen einzelne Bits, um Optionen zu kodieren. Das Linux-Systemkommando open() etwa erwartet ein Flag-Argument:
int fd = open("datei.txt", O_RDONLY | O_CREAT | O_TRUNC);
O_RDONLY, O_CREAT und O_TRUNC sind Konstanten mit jeweils nur einem gesetzten Bit. Mit | werden sie zu einer einzigen Zahl kombiniert. Im Kernel prüft eine andere Stelle dann mit flags & O_CREAT, ob das jeweilige Bit gesetzt ist.
Diese Technik spart Speicher und ermöglicht beliebige Kombinationen aus einem festen Vokabular von Optionen.
Anwendung 2: Bit-Manipulation
Das Setzen, Löschen, Umschalten und Prüfen einzelner Bits gehört zu den klassischen Bit-Tricks:
// Bit n setzen
x |= (1 << n);
// Bit n löschen
x &= ~(1 << n);
// Bit n umschalten
x ^= (1 << n);
// Bit n prüfen
if (x & (1 << n)) { ... }
In Embedded-Systemen, wo jedes Bit eines Hardware-Registers eine andere Bedeutung hat (LED an, Interrupt aktiv, Buffer voll), sind diese Idiome Alltag.
Anwendung 3: Schnelle Arithmetik
Multiplikation und Division durch Zweierpotenzen lassen sich mit Shifts ersetzen:
y = x * 8; // langsam (CPU-Multiplikator)
y = x << 3; // schnell (ein Takt)
y = x / 4; // Division
y = x >> 2; // schneller Shift
Moderne Compiler erkennen das selbst, also lohnt sich manuelle Optimierung selten. Aber wer Assembler liest oder Mikrocontroller-Code schreibt, trifft das Muster überall.
Ein weiterer Klassiker ist der Modulo-Trick mit AND: x % 8 ist dasselbe wie x & 7, solange die rechte Seite eine Zweierpotenz minus 1 ist. Auch hier sind moderne Compiler clever genug, das selbst zu tun.
Anwendung 4: Eindeutigkeit via XOR
Wer in einem Array mit lauter doppelten Zahlen genau eine sucht, die nur einmal vorkommt, kann alle XOR-verknüpfen:
int result = 0;
for (int i = 0; i < n; i++) result ^= arr[i];
// result ist die einzige nicht-doppelte Zahl
Hintergrund: a XOR a = 0 und 0 XOR b = b. Doppelte Werte heben sich gegenseitig auf, das einzige unpaarige bleibt übrig. O(n) Zeit, O(1) Speicher, ohne Hash-Set.
Anwendung 5: Verschlüsselung
XOR ist die einfachste Form von Verschlüsselung. Wer einen Text Byte für Byte mit einem Schlüssel XOR-verknüpft, bekommt einen Geheimtext. Mit demselben Schlüssel XOR-verknüpft, kommt der Klartext zurück.
Diese Methode (auch Vernam-Chiffre) ist mathematisch sogar perfekt sicher, vorausgesetzt der Schlüssel ist so lang wie der Klartext, wirklich zufällig und nur einmal benutzt. Sobald man den Schlüssel wiederverwendet oder kurz wählt, fällt die Verschlüsselung trivial. Echte Verschlüsselung wie AES nutzt deutlich komplexere Konstruktionen, aber XOR steckt auch dort in den unteren Schichten.
Praktische Stolperfallen
Vorzeichen bei Shift. In Java existiert >>> für unsigned right shift, in C ist das Verhalten von >> bei negativen Zahlen implementationsabhängig. Wer auf das genaue Bitmuster angewiesen ist, sollte mit unsigned Typen arbeiten.
Operator-Präzedenz. a & b == c wird in C als a & (b == c) ausgewertet, weil == stärker bindet als &. Lieber Klammern setzen: (a & b) == c.
Konstanten-Typ. 1 << 31 in 32-Bit-Integer-Code überläuft auf manchen Architekturen. Sicherer ist 1u << 31 oder 1LL << 31.
Lesbarkeit. Bitoperationen sind kompakt, aber nicht jeder Leser versteht sie sofort. In Code, der nicht performance-kritisch ist, sind benannte Konstanten oder einfache Schleifen oft besser.
Bitwise vs. logisch
Verwechslungsgefahr in C-Familie: && ist logisches AND (für Bedingungen, kurzgeschlossen), & ist bitweises AND. if (a & b) prüft das Bitmuster, if (a && b) prüft beide auf wahr. Beides funktioniert oft, hat aber unterschiedliche Semantik. In Python und Ruby sind beide Operationen mit and und & klar getrennt; in C und JavaScript muss man aufpassen.
Werkzeuge zum Visualisieren
Wer ein Bitmuster verstehen will, hilft sich am schnellsten mit einem Konverter. Unser Binär-Umrechner zeigt für jede eingegebene Zahl auch die Bit-Aufschlüsselung. Wer wissen will, was hinter einer Hex-Zahl steckt, nutzt Hex zu Binär.
Verwandte Themen
Vertiefend: Zweierkomplement für das Verständnis negativer Bitmuster, Endianness für Multi-Byte-Werte, und Bit-Tricks für fortgeschrittene Optimierungs-Idiome.