Ein großer Teil der Programmierfehler geht darauf zurück, dass Programmierer die Semantik der verwendeten Programmiersprache nicht genau beachten. Dies ist einerseits Schuld der Programmierer, andererseits vielfach aber auch Schuld der Entwickler der Programmiersprache, die Missverständnisse sozusagen "vorprogrammiert" haben.
Die folgenden Beispiele verdeutlichen dies.
Probieren Sie aus, welchen Wert folgendes Java-Programm ausgibt:
Die Zahl 231 wird ausgegeben. So sollte man denken. Tatsächlich jedoch ist das Ergebnis 19. Der Computer hat nichts falsch gemacht, sondern einerseits der Programmierer, der statt einer 1 versehentlich ein kleines "L" geschrieben hat. Angehängt an die Zahl 023 wird es als Typkennzeichner für den Typ long aufgefasst. Und andererseits die Entwickler der Programmiersprache, die entschieden haben, dass ein kleines "L" zulässig ist und, schlimmer noch, dass eine führende 0 anzeigt, dass die Zahl als Oktalzahl, also als Zahl zur Basis 8 (hier 2·8 + 3·1 = 19), zu interpretieren ist.1)
Von der vorangestellten 0 abgeleitet ist wohl auch die sonderbare Kennzeichnung für hexadezimal dargestellte Zahlenkostanten durch "0x", also "0 extended".
Tatsächlich hat sich die vorangestellte 0 sogar noch in Python bis zu den Versionen 2.x hinübergeschleppt. Erst in Python 3 hat man das Naheliegende umgesetzt, nämlich Oktalzahlen durch Voranstellen von "0o" zu kennzeichnen, analog zu "0x" für Hexadezimalzahlen. Leider aber ist auch "0O" zulässig, mit dem Großbuchstaben O. Was wird in Python 3 ausgegeben?
Richtig. 19.
Die meisten Programmierer gehen davon aus, dass die Zeichen +, -, * und / die vier Grundrechenarten Addition, Subtraktion, Multiplikation und Division bedeuten. Natürlich ist dies meistens richtig, manchmal aber auch falsch. In C++ und Java bedeutet etwa das Zeichen + bei int-Werten keine normale Addition, sondern eine Addition modulo 232. Das Entsprechende gilt für - und *.
Niemand erwartet, dass die Multiplikation der beiden positiven Zahlen 46000 und 47000 eine negative Zahl ergibt. Es gibt keine Fehlermeldung "Überlauf des Zahlenbereichs", sondern es wird modulo 232 gerechnet, das Ergebnis ist -2132967296.
Die Zahlen 46000 und 47000 werden in Java als int-Werte aufgefasst; das Ergebnis der Multiplikation ist dementsprechend wieder vom Typ int. Weil aber das Ergebnis größer als 231-1 ist, wird es als negative Zahl interpretiert. Erst danach wird es in den Typ long der Variablen a umgewandelt.
Das Zeichen / bedeutet bei int-Werten keine normale Division, sondern eine ganzzahlige Division mit Rest. In folgendem Programm soll die double-Variable a den Wert 0.5 erhalten, aber das Ergebnis ist 0.0.
Die Zahlen 1 und 2 sind vom Typ int und somit auch das Ergebnis des Ausdrucks 1/2. Die ganze Zahl 1 wird ganzzahlig durch 2 geteilt; das Ergebnis ist 0 Rest 1. Das Ergebnis 0 wird in die double-Zahl 0.0 umgewandelt.
Das +-Zeichen wird in Java sowohl für die Addition von Zahlen als auch für die String-Verkettung verwendet. Java wertet die untenstehenden Ausdrücke von links nach rechts aus. Wenn einer der Operanden ein String ist, wird der andere Operand in einen String umgewandelt und das +-Zeichen als Verkettungsoperator interpretiert.
Das folgende Java-Programm soll die ersten zehn Quadratzahlen auf dem Bildschirm ausgeben, probieren Sie es einmal aus:
Das Programm gerät in eine Endlosschleife. Ursache ist das kleine unscheinbare Semikolon hinter der While-Bedingung. Da in Java fast alle Zeilen mit einem Semikolon enden, fällt es nicht weiter auf.
Ein Semikolon schließt aber nicht eine Zeile ab, sondern eine Anweisung. Hier wird eine leere Anweisung durch das Semikolon abgeschlossen. Der Computer denkt also, er solle solange wie i < 10 gilt die leere Anweisung ausführen.
In der Programm-Entwicklungsumgebung Eclipse ist es möglich, das Auftreten einer leeren Anweisung durch eine Warnung zu kennzeichnen. Leider ist dies aber nicht standardmäßig so eingestellt.
Bei Rechnungen mit dem Datentyp double ist es wichtig zu berücksichtigen, dass es zu Rundungsfehlern kommen kann. Insbesondere ist ein Vergleich if (x==0) äußerst problematisch, denn wenn x als Ergebnis einer Berechnung zustande gekommen ist, so ist es meist aufgrund von Rundungsfehlern keineswegs gewährleistet, dass x exakt gleich Null ist, auch wenn es dies eigentlich sein sollte. So kann x zum Beispiel gleich 1.1E-16 = 0.00000000000000011 sein, also praktisch gleich Null, aber nicht exakt gleich Null.
Nie sollte man also so programmieren:
Das Programmstück sieht harmlos aus: b wird von a fünfmal subtrahiert, dann ist der Wert 0 erreicht und die Schleife sollte abbrechen. In Wirklichkeit handelt es sich um eine Endlosschleife, denn a wird nicht exakt gleich Null. Das Programm bleibt also in dieser Schleife hängen und ist damit "abgestürzt".
Daher dürfen double-Werte niemals mit den Vergleichsoperatoren == und != miteinander verglichen werden.
Mit b = 0.5 funktioniert das obige Programm aber doch. Warum?
In C++ und Java ist das Zeichen = das Wertzuweisungszeichen. Für einen Vergleich zweier Werte wird das doppelte Gleichheitszeichen == verwendet. Probieren Sie folgendes C++-Programm aus:
Das Programm gerät in eine Endlosschleife. Ursache ist eine ganze Kette von Fehlern, von denen nur einer vom Programmierer gemacht worden ist; die anderen Fehler liegen in der Programmiersprache C++ selbst begründet:
In der While-Bedingung wird der Variablen x der Wert 1 zugewiesen; das Ergebnis dieses Ausdrucks ist die Zahl 1; diese wird als Wahrheitswert true interpretiert; der Schleifenkörper wird ausgeführt. Zwar hat danach x den Wert 0, aber beim erneuten Prüfen der While-Bedingung wird x wieder auf 1 gesetzt und die Schleife wird noch einmal ausgeführt usw.
In Java sind als Bedingungen in If-Anweisungen und While-Schleifen nur Ausdrücke erlaubt, die einen Wahrheitswert (Typ boolean) ergeben; der Fehler wird hier also erkannt.
Das folgende C++-Programm soll die Zahlen von 1 bis 10 zusammenzählen. Das Ergebnis ist -858993405. Es wurde vergessen, die Variable s mit 0 zu initialisieren.
In C++ wird nicht überprüft, ob die Grenzen eines Arrays überschritten werden:
Das Programm gibt den Wert -33686017 aus, weil auf das nicht vorhandene Array-Element a[2] zugegriffen wird.
Eine Funktion soll normalerweise aus den Argumenten einen Wert berechnen und nichts anderes nebenbei tun. Von diesem Prinzip weichen Programmierer jedoch immer wieder ab, etwa indem sie in der Funktion globale Variablen verändern. Dies wird dann als Seiteneffekt der Funktion bezeichnet.
Die folgenden beiden C++-Funktionen s1() und s2() geben als Seiteneffekt die Strings "1" bzw. "2" aus. Aufgerufen werden die Funktionen in dem Ausdruck s1()+s2() in der Funktion main(). Merkwürdigerweise wird dieser Ausdruck in C++ nicht von links nach rechts ausgewertet, sondern von rechts nach links, sodass zuerst der Seiteneffekt von s2() auftritt und dann der von s1().
In Java wird der Ausdruck korrekt von links nach rechts ausgewertet.
1) Wahrscheinlich wurde dies schon beim Entwurf einer Vorläufersprache von C festgelegt, denn Oktalzahlen wurden nur in der Frühzeit der Datenverarbeitung benutzt. Man kann sich förmlich vorstellen, wie es dazu gekommen ist. Irgendjemand wollte unbedingt oktal dargestellte Zahlenkonstanten haben. Ein vorangestellter Buchstabe zur Kennzeichnung der oktalen Darstellung, vielleicht ein kleines "o", wäre am deutlichsten gewesen, aber dann wäre z.B. o23 als Variablenname o23 zu interpretieren gewesen. Ein nachgestelltes Zeichen ging auch nicht, da der Compiler die Zahl schon als Dezimalzahl ausgewertet hat, wenn er auf das nachgestellte Zeichen trifft. Die Sonderzeichen waren schon alle für Operatoren verbraucht, also blieb nur eine Ziffer. Und so kam es zu der "Vereinbarung" (der ich als Programmierer nie zugestimmt hätte), dass eine führende 0, die ja an ein "O" wie oktal erinnert, die Interpretation der Zahl als Oktalzahl anzeigt.
Weiter mit: [up]