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.

Anzeige
Anzeige
Anzeige
Anzeige
Anzeige