Software Engineering

Programmier-Fallstricke

 aufwärts

Ein großer Teil der Programmier­fehler geht darauf zurück, dass Programmierer die Semantik der verwendeten Programmier­sprache nicht genau beachten. Dies ist einerseits Schuld der Programmierer, andererseits vielfach aber auch Schuld der Entwickler der Programmier­sprache, die Miss­verständnisse sozusagen "vor­programmiert" haben.

Ein Beispiel verdeutlicht dies. Probieren Sie aus, welchen Wert folgendes Java-Programm ausgibt:

public class Test
{
    public static void main(String[] args)
    {
        long a;
        a=023l;
        System.out.println(a);
    }
}

Das Ergebnis ist 231. 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 Typ­kennzeichner für den Typ long aufgefasst. Und andererseits die Entwickler der Programmier­sprache, 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 inter­pretieren ist.1)

Die Zeichen +, -, * und /

Die meisten Programmierer gehen davon aus, dass die Zeichen +, -, * und / die vier Grund­rechenarten Addition, Subtraktion, Multi­plikation 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 Ent­sprechende gilt für - und *.

Niemand erwartet, dass die Multi­plikation der beiden positiven int-Zahlen 46000 und 47000 eine negative Zahl ergibt. Es gibt keine Fehler­meldung "Überlauf des Zahlen­bereichs", sondern es wird modulo 232 gerechnet, das Ergebnis ist -2132967296.

public class TestMult
{
    public static void main(String[] args)
    {
        long a=46000*47000;
        System.out.println(a);
    }
}

 

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.

public class TestDiv
{
    public static void main(String[] args)
    {
        double a=1/2;
        System.out.println(a);
    }
}

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.

public class TestPlus
{
    public static void main(String[] args)
    {
        int a=3, b=4;
        System.out.println("Ergebnis: "+a+b);     // Ergebnis: 34
        System.out.println(a+b+" kommt raus");    // 7 kommt raus
    }
}

Semikolon

Das folgende Java-Programm soll die ersten zehn Quadrat­zahlen auf dem Bildschirm ausgeben, probieren Sie es einmal aus:

public class TestQuadrate
{
    public static void main(String[] args)
    {
        int i=0;
        while (i<10);
        {
            i=i+1;
            System.out.println(i*i);
        }
    }
}

Das Programm gerät in eine Endlos­schleife. 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 abge­schlossen. Der Computer denkt also, er solle solange wie i<10 gilt die leere Anweisung ausführen.

In der Programm-Entwicklungs­umgebung Eclipse ist es möglich, das Auftreten einer leeren Anweisung durch eine Warnung zu kennzeichnen. Leider ist dies aber nicht standard­mäßig so eingestellt.

Rundung

Bei Rechnungen mit dem Datentyp double ist es wichtig zu berück­sichtigen, dass es zu Rundungs­fehlern 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 Rundungs­fehlern keineswegs gewähr­leistet, 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:

public class TestRundung
{
    public static void main(String[] args)
    {
        double a, b;
        a=2;
        b=0.4;
        while (a!=0)
            a=a-b;
    }
}

Das Programm­stü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 Endlos­schleife, 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 nie mit den Vergleichs­operatoren == und != miteinander verglichen werden.

Mit b=0.5 funktioniert das obige Programm dagegen. Warum?

Gleichheitszeichen

In C++ und Java ist das Zeichen = das Wert­zuweisungszeichen. Für einen Vergleich zweier Werte wird das doppelte Gleichheits­zeichen == verwendet. Probieren Sie folgendes C++-Programm aus:

void main()
{
    int x=1;
    while (x=1)
        x=x-1;
    cout<<x<<endl;
}

Das Programm gerät in eine Endlos­schleife. Ursache ist eine ganze Kette von Fehlern, von denen nur einer vom Programmierer gemacht worden ist; die anderen Fehler liegen in der Programmier­sprache 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 Wahrheits­wert true inter­pretiert; der Schleifen­kö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 Wahrheits­wert (Typ boolean) ergeben; der Fehler wird hier also erkannt.

Initialisierung von Variablen

Das folgende C++-Programm soll die Zahlen von 1 bis 10 zusammen­zählen. Das Ergebnis ist -858993405. Es wurde vergessen, die Variable s mit 0 zu initialisieren.

void main()
{
    int i, s;
    for (i=1; i<=10; i++)
        s=s+i;
    cout<<s<<endl;
}

Array-Grenzen

In C++ wird nicht überprüft, ob die Grenzen eines Arrays über­schritten werden:

void main()
{
    int* a=new int[2];
    a[0]=1;
    a[1]=1;
    int i, s=0;
    for (i=0; i<=2; i++)
        s=s+a[i];
    cout<<s<<endl;
}

Das Programm gibt den Wert -33686017 aus, weil auf das nicht vorhandene Array-Element a[2] zugegriffen wird.

Seiteneffekte

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(). Merk­würdiger­weise wird dieser Ausdruck in C++ nicht von links nach rechts ausgewertet, sondern von rechts nach links, so dass zuerst der Seiteneffekt von s2() auftritt und dann der von s1().

In Java wird der Ausdruck korrekt von links nach rechts ausgewertet.

#include <iostream>
#include <string>
using namespace std;

string s1()
{
    cout<<1;
    return "1";
}

string s2()
{
    cout<<2;
    return "2";
}

void main()
{
    string y=s1()+s2();
    cout<<endl<<y<<endl;
}

1)  Wahrscheinlich wurde dies schon beim Entwurf einer Vorläufer­sprache von C festgelegt, denn Oktalzahlen wurden nur in der Frühzeit der Daten­verarbeitung benutzt. Man kann sich förmlich vorstellen, wie es dazu gekommen ist. Irgendjemand wollte unbedingt oktal dargestellte Zahlen­konstanten haben. Ein voran­gestellter Buchstabe zur Kenn­zeichnung der oktalen Darstellung, vielleicht ein kleines "o", wäre am deutlichsten gewesen, aber dann wäre z.B. o23 als Variablen­name o23 zu inter­pretieren gewesen. Ein nach­gestelltes Zeichen ging auch nicht, da der Compiler die Zahl schon als Dezimalzahl ausgewertet hat, wenn er auf das nach­gestellte Zeichen trifft. Die Sonder­zeichen 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 Inter­pretation der Zahl als Oktalzahl anzeigt. Hiervon abgeleitet ist wohl auch die sonderbare Kenn­zeichnung für hexadezimal dargestellte Zahlen­kostanten durch "0x", also "0 extended".

 

Weiter mit:   up

 

homeH.W. Lang   Hochschule Flensburg   lang@hs-flensburg.de   Impressum   ©   Created: 06.04.2005   Updated: 04.11.2016
Valid HTML 4.01 Transitional

Hochschule Flensburg
Campus Flensburg

Informatik in Flensburg studieren...

 

Neu gestaltetes Studienangebot:

Bachelor-Studiengang
Angewandte Informatik

mit Schwerpunkten auf den Themen Software, Web, Mobile, Security und Usability.

Ihr Abschluss
nach 7 Semestern:
Bachelor of Science

 

Ebenfalls ganz neu:

Master-Studiengang
Angewandte Informatik

Ein projektorientiertes Studium auf höchstem Niveau mit den Schwerpunkten Internet-Sicherheit, Mobile Computing und Human-Computer Interaction.

Ihr Abschluss
nach 3 Semestern:
Master of Science

 

Weitere Informatik-Studienangebote an der Hochschule Flensburg:

Medieninformatik

Wirtschaftsinformatik