Magische Zahl (Informatik)

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

Eine Magische Zahl (englisch magic number) hat in der Programmierung drei Bedeutungen:

  1. Ursprünglich aus der Unix-Welt kommend, ist es ein spezieller Wert, der ein bestimmtes Dateiformat am Dateibeginn kennzeichnet (wie ihn zum Beispiel der Unix-Befehl file auswertet).
  2. Ein auffälliger Wert, um ein Register oder einen Speicherbereich zu markieren, der später mittels eines Debuggers auf Fehler untersucht werden soll. Solche markierende Magische Zahlen werden meistens aus folgenden Domänen ausgewählt:
    • ASCII (meistverwendet)
    • hexadezimale Repräsentation von Zahlen (beispielsweise 305419896 = 0x12345678)
    • Manchmal wird Hexspeak verwendet
  3. Ein im Quellcode eines Programms auftauchender Zahlenwert (auch englisch hard coded value genannt), dessen Bedeutung sich nicht unmittelbar erkennen lässt – seine Bedeutung ist somit „magisch“. Derartige Magische Zahlen sind zu vermeiden und durch gut benannte Konstantendefinitionen zu ersetzen, deren Namen Bedeutung und Herkunft klar angeben.

Magische Zahlen zur Kennzeichnung von Dateitypen[Bearbeiten | Quelltext bearbeiten]

Eine frühe Konvention in unixartigen Betriebssystemen war, dass Binaries mit zwei Bytes anfingen, die eine „Magische Zahl“ enthielten, die den Typ der Datei angibt. Am Anfang wurden damit Objektdateien für verschiedene Plattformen gekennzeichnet. Nach und nach wurde dieses Konzept auch auf andere Dateien übertragen, und mittlerweile findet sich in fast jeder Binärdatei eine magische Zahl.

Viele andere Typen von Dateien haben einen Inhalt, der den Dateitypen identifiziert. So fängt XML mit der speziellen Zeichenfolge „<?xml“ an, die die Datei als XML kennzeichnet. Wandelt man diesen Dateianfang in eine Zahl um, kann man anhand eines einfachen Vergleiches schnell den Dateityp bestimmen, ohne viel über das Format wissen zu müssen.

Einige Beispiele:

  • Die Stelle mit wichtigen Netzwerkparametern des BOOTP/DHCP-Protokolls beginnt mit einem (hexadezimalen) Magic Cookie 0x63825363.
  • kompilierte Java-Klassendateien (Bytecode) beginnen mit 0xCAFEBABE.
  • GIF-Dateien enthalten am Anfang den ASCII-Code für ‚GIF89a‘ (0x474946383961) oder ‚GIF87a‘ (0x474946383761)
  • JPEG/JFIF-Dateien fangen mit 0xFFD8FF an und enthalten weiterhin die ASCII-Entsprechung für ‚JFIF‘ (0x4A464946).
  • PNG-Dateien beginnen mit einem 8-Byte-MagicByte, welche die Datei als PNG identifiziert und eine Erkennung von Dateiübertragungsproblemen ermöglicht: \211 P N G \r \n \032 \n (0x89504e470d0a1a0a)
  • Standard-MIDI-Dateien enthalten die ASCII-Zeichenfolge ‚MThd‘ (0x4D546864) gefolgt von Metadaten.
  • Unix-Scripte aller Art starten normalerweise mit einem Shebang, ‚#!‘ (0x23 0x21), gefolgt von einem Pfad zum Interpreter (z. B. ,#!/usr/bin/perl‘ für Perl)
  • MS-DOS-EXE-Dateien und Microsoft-Windows-PE-Dateien (Portable Executable) starten mit den ASCII-Zeichen ‚MZ‘ (0x4D5A) oder auch selten ‚ZM‘ (0x5A4D), den Initialen des Erfinders dieses Formats, Mark Zbikowski.
  • Der Berkeley-Fast-File-System-Superblock wird identifiziert durch 0x19540119 oder 0x011954 je nach Version; beides ist das Geburtsdatum des Designers Marshall Kirk McKusick.
  • Programme für den Game Boy und Game Boy Advance haben eine 48 oder 156 Byte lange magische Zahl. Diese Zahl kodiert ein Bitmap des Nintendo-Logos.
  • Alte Fat Binaries (die Code für sowohl den 68K- als auch den PowerPC-Prozessor enthalten) auf Mac OS 9 beginnen mit der ASCII-Zeichenfolge von ‚Joy!‘ (englisch für Freude!; hexadezimal 0x4A6F7921).
  • TIFF-Dateien fangen mit II oder MM an, abhängig von der Endianness („II“ entspricht Intel und „MM“ Motorola), gefolgt von 0x2A00 oder 0x002A (im Dezimalsystem 42).

Das Unix-Kommando file liest und interpretiert magische Zahlen aus Dateien. Auch das Linux-Kernelmodul binfmt misc erkennt anhand magischer Zahlen den Dateityp einer Anwendung. Die eigentliche „Magie“ liegt in der Datei /usr/share/misc/magic.mgc (unter Debian /usr/share/file/magic.mgc).

Magische Zahlen als Markierung in der Programmierung[Bearbeiten | Quelltext bearbeiten]

Hexadezimalzahlen werden oft dazu benutzt, Werte auf Datenträgern oder anderem Speicher darzustellen. Die meisten Zahlen sehen dabei recht „uninteressant“ und „zufällig“ aus. Manchmal ist es aber vorteilhaft, einen sofort auffallenden Wert zu haben (beispielsweise bei der Fehlersuche).

0xDEADBEEF (dezimal: 3.735.928.559) ist eine Zahl in hexadezimaler Notation, die als ‚dead beef‘ (also englisch für „totes Rindfleisch“) gelesen wird.

Normalerweise tritt ein Wert wie 0xDEADBEEF eher selten auf und wird somit dazu verwendet, besondere Werte anzuzeigen. Die Zahl an sich hat dabei keine spezielle Bedeutung und kann genauso durch andere „lesbare“ Werte wie 0xABABABAB, 0x00C0FFEE oder 0x0BADF00D (englisch ‚bad food‘, etwa „schlechtes Essen“) ersetzt werden.

Da ein solcher Wert selten vorkommt (bei Gleichverteilung von 32-Bit-Zahlen mit einer Wahrscheinlichkeit von , nach dem Benfordschen Gesetz sogar seltener), wird er oft von Softwareentwicklern dazu benutzt, Fehler wie Pufferüberläufe oder uninitialisierte Variablen zu finden bzw. zu untersuchen. Wenn der Wert also im Speicher auftaucht, sollte der Programmierer sich diese Stelle genauer ansehen. Auch werden zu Debuggingzwecken Speicherbereiche, die vom Programm nicht beschrieben werden sollten, mit 0xDEADBEEF vollgeschrieben. Schreibt das Programm in diesem Bereich, wird es sofort bemerkt.

Viele Versionen des PowerPC-Prozessors initialisieren ihre Register mit 0xDEADBEEF nach einem Hardware-Reset. 0xDEADBEEF wurde im originalen Mac-OS-Betriebssystem und auch bei den 1990 eingeführten RS/6000-Servern von IBM zu Diagnosezwecken benutzt.

Auch dezimale Zahlen werden dazu verwendet, z. B. um bei Konzepten und/oder Präsentationen Zahlen „ein Gesicht zu geben“, Platzhalter zu sein, aber gleichzeitig für alle verständlich anzuzeigen, dass der bestimmte Wert der Zahl völlig belanglos ist. Gern gewählt wird im Programmiererumfeld der Wert 42, der im SF-Roman Per Anhalter durch die Galaxis von einem allwissenden Computer als Lösung aller Probleme verkündet wird. Andere Beispiele sind bekannte „Allerwelts-Zahlen“ wie „08/15“ (deutsches Maschinengewehr aus den Weltkriegen) oder „4711“ (bekannte Parfümmarke).
Beispiel: »Der Kunde „4711“ bestellt den Artikel „08/15“. Ein anderer Kunde „42“ bestellt diesen Artikel auch. Was soll passieren, wenn nur ein Artikel „08/15“ vorrätig ist?«

Magische Zahlen in Code[Bearbeiten | Quelltext bearbeiten]

Der Term magische Zahl (englisch magic number, oft auch hard coded value) bezeichnet auch den schlechten Programmierstil, Werte unmittelbar zwischen die Befehle des Quellcodes zu schreiben. Das macht in vielen Fällen Programmcode schwerer lesbar und unverständlich. Besser ist es meistens, Zahlen mit Bedeutung als Konstante festzulegen und so mit einem aussagekräftigen Namen zu versehen. Außerdem lässt sich so eine Zahl besser im gesamten Code ändern, da oft andere Zahlen von ihr abhängen.

Ein Beispiel in Pascal-ähnlichem Pseudocode, das 52 Zahlen in einem auch sogenanntem Array mischt:

for i from 1 to 52
{
  j:= i + randomInt(53 - i) - 1
  swapEntries(i, j)
}

Die Funktion randomInt(x) generiert eine Zahl zwischen 1 und x, und swapEntries(i, j) vertauscht die Einträge i und j im Array. 52 ist dabei eine magische Zahl. Besserer Stil ist das folgende Programm:

constant int cardGame_deckSize:= 52
for i from 1 to cardGame_deckSize
{
  j:= i + randomInt(cardGame_deckSize + 1 - i) - 1
  swapEntries(i, j)
}

Die Vorteile hier sind:

  • Einfacher zu verstehen. Ein Programmierer, der das erste Programm liest, wird sich nach der Bedeutung der 52 fragen, und womöglich lange suchen, bevor er den Sinn dahinter erfasst.
  • Einfacher zu ändern. Wenn im oberen Beispiel die magische Zahl nachträglich programmweit geändert werden soll, muss diese davon abhängende 53 ebenfalls geändert werden. In größeren Programmen kann solch eine Vorgehensweise sehr unübersichtlich und aufwendig werden. Es können Fehler entstehen, welche später sehr aufwendig behoben werden müssen und unter Umständen sehr schwer auszumachen sind. Im Gegensatz dazu muss im unteren Beispiel lediglich eine einzige Zeile geändert werden.
  • Sämtliche bedeutsame Zahlen befinden sich am Anfang des Programmes, so dass der Überblick nicht verloren gehen kann.
  • Vereinfachte Parametrisierung. Soll das obige Programm beliebig große Arrays mischen, kann aus cardGame_deckSize einfach ein auch sogenannter Parameter gemacht werden. Beispiel:
function shuffle (int cardGame_deckSize)
{
  for i from 1 to cardGame_deckSize
  {
    j:= i + randomInt(cardGame_deckSize + 1 - i) - 1
    swapEntries(i, j)
  }
}
  • Tippfehler werden vermieden. Der Compiler wird kein Problem haben, wenn statt 52 die Zahl 42 getippt wurde, das Programm wird aber nicht ordnungsgemäß funktionieren. Wird dagegen cardGame_dekcSize getippt, wird der Fehler schon vom Compiler erkannt.

Nachteile sind:

  • Der Code wird verlängert. Wenn viele Konstanten in einer Zeile genutzt werden, müssen Zeilenumbrüche eingefügt werden.
  • Es erschwert das Debugging auf Systemen, auf denen die Werte von Konstanten nicht angezeigt werden. (Es erleichtert jedoch sehr das Debugging bei Verwendung eines symbolischen Debuggers.)
  • Bei nicht sinnvoll eingeführten Konstanten muss der Leser ggf. einen weiteren Blick auf dessen Definition werfen.

Siehe auch[Bearbeiten | Quelltext bearbeiten]

Referenzen[Bearbeiten | Quelltext bearbeiten]