Die beliebtesten Stolperfallen für Programmierer

von
Thomas Heinrich

Der freie Fall

Kompiliert - gestartet - abgestürzt. "Nicht schon wieder", denkt sich der Programmierer, verdreht die Augen und beherrscht sich mühsam, um nicht seinen Rechner zu demolieren.

Kommt Ihnen das bekannt vor? Mir geht es öfters so. Dadurch, daß der Amiga keinerlei Resourcenschutz besitzt, ist ein Programmabsturz sehr oft gleichbedeutend mit einem sofortigen Reset und Neustart. Für den Programmierer (und die Betatester) sehr ärgerlich, aber andererseits verlangt der Amiga dadurch eine äußerst genaue und disziplinierte Programmierung. Auf anderen Systemen bietet das OS viele Rettungsleinen, die meistens sogar funktionieren. Unix und Windows NT sind meistens in der Lage, einen abgestürzten Task zu entfernen, und das System ist immer noch stabil. Die anderen Windows-Versionen versuchen das zwar auch, aber normalerweise bleibt es bei dem Versuch...

Fallschirme

Es bleibt uns Amiganern also nichts anderes übrig, als bei der Programmierung gewissenhafter zu sein, als es auf anderen Systemen üblich ist. Das ist aber gar nicht so schwer.

Die Erste-Hilfe-Grundausstattung ist sicherlich "Enforcer" oder "Cyberguard". Jeder Enforcer-Hit ist grundsätzlich ein Programmier- Fehler. Zwar gibt es ein paar spezielle Hits, die tatsächlich "harmlos" sind, wie viele Programmierer gerne behaupten, aber das ist kein Grund, diese Fehlerquelle nicht auszuschalten.

Solche "harmlosen" Hits sind z.B. Byte-, Word-, Long-Reads von Null. Zufälligerweise steht an der Adresse Null auch wirklich immer eine Null, so daß ein Test auf das Ergebnis des Lesevorgangs negativ ausfällt. Außer natürlich, es wird auf Ungleichheit getestet... Oder ein anderes fehlerhaftes Programm hat dort versehentlich etwas hingeschrieben... Für mich kann der Fehler also mitnichten als harmlos gelten.

Meditation

Ab und zu kann nicht einmal Enforcer helfen, denn es bekommt durch ein absolut blockiertes System keine Meldung mehr heraus. Dann ist es wichtig, sich die Gurus anzusehen und die Fehlermeldungen mit denen aus dem Include "exec/alerts.h" zu vergleichen.

Äußerst beliebt ist das Freigeben von falschen Speicherbereichen. Da gibt es mehrere Typen:

Nicht initialisierte Zeiger auf Speicherbereiche - man versucht, Speicher, den man sich noch gar nicht besorgt hat, wieder freizugeben. Der Zeiger hat irgendeinen zufälligen Wert und der Guru macht Hausbesuch.

Teilweises Freigeben von Speicher - irgendeine Operation führt den Zeiger auf einen Speicherbereich weiter, so daß die Länge des Blocks nicht mehr paßt. Wieder ein Guru.

Ziemlich profan, trotzdem oft erlebt, sind auch Division-durch-Null-Fehler. Da nimmt eine Variable plötzlich einen Wert an, den sie nicht haben dürfte, meistens verursacht durch das Nicht-Testen eines Rückgabewertes einer Funktion. Leicht passiert, aber auch leicht verbessert - wenn man den Fehler selbst findet, und nicht erst der Enduser...

Hoch stapeln und tief fallen

Der Stack ist ein leidiges Thema. Besonders rekursive Funktionen lassen den Stack leicht überlaufen. Wann immer möglich, vermeide ich solche Konstruktionen. Unglücklicherweise ist die Stackregulierung des Amiga viel zu sehr dem User überlassen. Dazu laufen z.B. Patches auch auf einem fremden Stack, was von dem Programm selbst, das die gepatchte Funktion benutzt, nicht bemerkt wird.

Zu guter Letzt lassen sich die Auswirkungen eines übergelaufenen Stacks gar nicht vorhersagen. Wenn irgend etwas im System sich nicht mehr richtig anfühlt, dann ist es meistens schon zu spät. Probieren Sie einfach einen Stack-Wächter aus. Ich für meinen Teil setze den Stack während der Programmierung ziemlich hoch und teste dann am Ende, bis zu welchem Punkt nichts passiert, wenn man den Stack wieder heruntersetzt. Braucht ein Programm mehr, als die standardmäßigen 4 KB, kommt ein Hinweis in die Anleitung, und ein Test für die Stackgröße in das Programm. Der Test ist ganz einfach:

Ein FindTask(NULL), dann das Feld tc_SPUpper minus tc_SPLower, fertig ist der verfügbare Stack. Er ist zwar um 2 Bytes ungenau, aber darauf darf es nicht ankommen. Sie müssen schon einen Spielraum in die Abfrage einbauen. Haben Sie festgestellt, daß Sie zuwenig Stack haben, müssen Sie das Programm sofort abbrechen lassen.

Zeigerln

Hinterhältig sind auch Zeiger, Indizes und Dereferenzen, besonders in Verbindung mit Strukturen. Ist das folgende Fragment korrekt?

   struct texthandle
   {
      ...
      UBYTE th_String[80];
      ...
   };

   struct texthandle th;

   th->th_String[0] = 0;
                           

Offensichtlich soll hier ein String gelöscht werden, indem das erste Zeichen zum Nullbyte gemacht wird. Nur funktioniert es nicht, bzw. wenn, dann nicht mit jedem Kompiler. Es ist nämlich nicht eindeutig, was hier Null gesetzt werden soll:

   (th->th_String)[0] = 0; 

oder

   th->(th_String[0]) = 0;

Der erste Fall funktioniert wie gewünscht, aber der zweite Fall setzt das erste Element eines Arrays der Struktur auf Null. Im folgenden ist das Element "th_String" nicht mehr die Adresse der 80 reservierten Bytes, sondern NULL!

Nun stellen Sie sich vor, es soll nicht das erste, sondern das 80ste Element gelöscht werden. Welche Speicherstelle Sie dann mit Null überschreiben, ist nicht mehr vorhersagbar.

Genauso unklar sind Weiterführungen

   th->th_String++;
   (th->th_String)++; /* OK */  
   th->(th_String++); /* Falsch */ 

und Dereferenzen

   *th->th_String = 0;

   *(th->th_String) = 0;  /* OK */ 
   (*th)->th_String = 0;  /* Extrem falsch, muß sogar der Kompiler merken */
    

Der einzig wirksame Tip: Klammern, Klammern und nochmals Klammern! In "C" können Sie nur zu wenige Klammern setzen, nie zuviel. Selbst wenn Sie durch probieren die Eigenheiten Ihres Kompilers kennen, spätestens wenn Sie ihn wechseln oder jemand den Code portieren will, geht es fürchterlich in die Hose. Auch wenn Sie alle Prioritäten der C-Operatoren auswendig kennen: es gibt viele Fälle, in denen gleich priorisierte Operatoren zusammentreffen. Man muß den Ärger ja nicht provozieren.

Blindflug

Auch vergessene Prototypen sind auf den ersten Blick harmlos. Der Kompiler beschwert sich zwar, daß er die Übergabeparameter nicht kennt, macht aber brav, was Sie eingetippt haben. Solange Sie keinen Fehler dabei gemacht haben, gibt es keine Probleme. Ein Beispiel:

   strncpy(str1, str2);  

Klar, hier fehlt die Längenangabe, wieviel Bytes denn nun kopiert werden sollen. Wenn der Prototyp nicht deklariert ist, nimmt die Funktion ohne Kenntnis des Kompilers für die Länge einfach das nächste Langwort vom Stack und was da drinsteht, entzieht sich jeder Kontrolle. Wenn Sie Pech haben, ist es die Rücksprungadresse eines Funktionsaufrufs, dann ist das eine sehr große Zahl an unkontrolliert kopierten Zeichen...

Taubheit

Es wurde schon sehr oft gesagt, genauso oft verhallt es ungehört: Warnungen sind Fehler! Wenn ein Kompiler murrt, dann haben Sie etwas falsch gemacht. "Kleine" oder "harmlose" Fehler gibt es nicht, zumindest nicht auf dem Amiga.

Viele Entwicklungssysteme haben auch einen Debugger. Setzen Sie ihn ein, aber wundern Sie sich nicht, wenn er nicht viel nützt. Unter günstigen Umständen werden mehrere Zeilen Sourcecode durch Optimierungen zu einer einzigen Assembleranweisung verdichtet, so daß sie gar nichts davon sehen.

Mir persönlich hilft an absturzverdächtigen Stellen ein ausführliches Zukleistern mit Printf() immer noch mehr, als ein noch so mächtiger Debugger. Das liegt wohl daran, daß ich noch keinen vernünftigen Amiga- Debugger gesehen habe.


Prev Inhaltsverzeichnis Next
© `99 Der AmZeiger