Volkers Editorial

Aus LinuxUser 02/2002

Volkers Editorial

Eine saubere Sache

Wenn Ordnungsansprüche verlangen, dass der Schreibtisch nicht nur aufgeräumt sein soll, sondern das Lineal parallel zur Tischkante und das restliche Büromaterial in Reih und Glied angeordnet gehört, mag das übertrieben sein.

Der 08/15-Programmierer steht allerdings einem ganz anderen Anspruch gegenüber. Selbst wenn sich bei ihm Chipstüten neben Bergen von Konzeptnotizen türmen, hat er oft unter dem Ordnungsanspruch der Qualität und Portierbarkeit zu arbeiten. Aber wie soll das gehen, wenn man schon seine Maus unter den Papierhaufen suchen muss?

Richtig initialisiert?

Es soll ja Leute geben, die aufräumen, bevor die Putzfrau kommt. Ich gehöre nicht dazu, und dennoch schaffe ich es, meinen Schreibtisch so weit in Ordnung zu halten, dass sich Sachen wiederfinden lassen. Ein kleines Problem stellen vielleicht Notizzettel dar, auf denen ich mir wichtige Telefonnummern notiert habe und die unter dem Papier wieder zum Vorschein kommen.

Diese Zettel haben erstaunlich viel mit meiner Schwiegermutter gemeinsam: Sie geben einem nie die volle Information. Es scheint der Lieblingssport meiner Schwiegermutter zu sein, sich ihrer Umwelt mit Halbsätzen mitzuteilen. Das hat alles seine gute und seine schlechte Seite. Schlecht ist, dass man mit “Das willst Du doch sicherlich in braun …” nicht viel anfangen kann, wenn es der erste Satz ist, den sie zu mir sagt.

Mein Verstand exerziert dann immer Dinge der Art “Braun? Was in braun? Bananen vielleicht?” durch. Klare Vorteile hat es bei Erstsätzen wie “Habe ich nicht recht, wenn ich so etwas zu ihr sage.” Ein einfaches “Ja.” erspart einem das 4653298475 Stunden dauernde Lamento über die Nachbarin, die dies gesagt hat, um jenen eines auszuwischen etc. etc. etc.

So sind dann meine Notizzettel: Ich habe vergessen, einen Namen zur Nummer zu schreiben – und den Mut, einfach diese Nummer anzurufen um herauszufinden, wer sich dahinter verbirgt, habe ich nicht immer.

Syntax-Verbrechen

Eines der größten Syntax-Verbrechen der deutschen Sprache ist sicherlich der Halsnasenohrenarzt. Die n-fach-Formulierung von Nasen und Ohren steht in klarem Missverhältnis zur anatomischen Realität. Ich habe – wie so ziemlich alle – nur eine Nase. Wäre es da nicht richtiger, die Nase in die Einzahl zu setzen und HalsNaseOhrenarzt zu sagen? Gerade noch erträglich hätte ich den Bezug zu seinem Behandlungsfeld gefunden. Ein schlichter HälseNasenOhrenarzt wäre somit numerisch korrekt gewesen, widerspräche jedoch wieder anderen Docktoren, denn dann zählten wir Zähnearzt, Füßeorthopäde und Häuserarzt zu den korrekten Bezeichnungen. Nee, lieber nicht.

Ohne Sinn

Ich glaube, dass zu den sinnlosesten Gestalten, die auf dieser Erde wandeln, die Kunstkritiker gehören. Sie lassen sich nur in tiefschürfender Art und Weise über Kunstobjekte aus, um deren Transzendenz, Mattigkeit, Lebensfreude oder was sonst zu beschreiben. Mir scheint, es ist eher eine Kunst zu verstehen, was so ein Kritiker von sich gibt, als der ganze Rest.

Glauben schenken darf man so einem Menschen ohnehin nur darin, ob ihm das Essen während einer Vernissage geschmeckt hat. Sein Urteilsvermögen ist nämlich eher künstlich und geleitet durch sein persönliches Engagement in Kunst als Wertanlage, und deren Kurs wird nur an der Tratschbörse notiert.

Es gibt einen schönen Ausdruck aus der Welt der Computer, der mir immer wieder im Zusammenhang mit Kunstkritik einfällt: Es ist der Begriff Glitsch (engl. glitch). Vor vielen Jahren durfte ich eines der Beispiele für Glitsch selbst an einem bootenden PC beobachten: “Tastatur nicht gefunden – Drücken sie F4”. Woher der Ausdruck kommt, weiß ich nicht. Vielleicht hat ihn ein Kunstkritiker erfunden…

Endlosschleife

Zu den nervigen Dingen im echten Leben gehören Endlosschleifen. Unglaublich aber wahr – es gibt sie auch hier. Da braucht man nur auf den Kinderspielplatz zu gehen und die Lütten zu beobachten: “Mein Pappa fährt aber ein größeres Auto als Deiner.” “Nein.” “Doch.” “Nein.” “Doch.” “Nein.” “Doch.” “Nein.” “Doch.” “Nein.” “Doch.” … Oder: Wer kennt nicht die Art von Telefongesprächen, in dem jeder der Teilnehmer das letze Wort haben will und man sich immer wieder Erwiderungen in die Ohren brüllt und zäh versucht, dem anderen das Ende des Gesprächs abzuringen.

Meister Propper

Natürlich bietet Linux dem Programmierer etwas gegen fehlende Grundinformation, Syntaxverbrechen, Sinnlosigkeit und Endlosschleifen. Es ist der Befehl lint. Nicht zu verwechseln mit Lindt – das ist ein Süßwarenfabrikant und alles andere als sinnlos.

  • -a: Reklamation bei Zuweisung von long-Werten an Variablen die nicht long sind unterdrücken
  • -u: Reklamationen bei Funktionen und externen Variablen unterdrücken, die verwendet und nicht definiert oder definiert und nicht verwendet werden
  • -v: Reklamation von unbenutzen Argumenten in Funktionen unterdrücken
  • -x: Keine Variablen melden, auf die externe Deklarationen verweisen, die jedoch niemals verwendet werden

Mit Lint ist der Programmierer mit einem Werkzeug ausgestattet, das seinen Quellcode auf viele Probleme hin untersucht. Dabei meckert Lint soviel an, dass es dem fortgeschrittenen Programmierer schon wieder zu lästig sein könnte. Damit die Software aber nicht allzuviel meckert, wartet eine ganze Reihe von Optionen auf ihren Einsatz.

Mit Sicherheit ist der beste Weg, Unix-Befehle zu erlernen, derjenige, sie zu benutzen. Das ist mit Lint nicht anders. Aber mehr noch: Der Befehl lint hilft einerseits dem Programmieranfänger, Fehler in seiner Denkweise zu finden, und andererseits dem etwas fortgeschritteneren Programmierer, seinen Kodierungsstil zu verbessern.

Für die ersten Schritte schaut man sich am Besten ein kleines Programm an, dem es hier und da an Sauberkeit mangelt, und jagt es durch Lint.

Ein C-Programm mit Macken

Das folgende C-Programm (Listing 1, sample.c) soll keine schlaue Aufgabe übernehmen, es dient nur der Verdeutlichung, was man alles richtig und falsch machen kann und wie Lint einem dabei unter die Arme greifen kann.

Listing 1

sample.c

01 #include <stdio.h>
02
03 int f(void)
04 {
05   return 0;
06 }
07
08 int g(void)
09 {
10   return 1;
11 }
12
13 int rueckgabe(void)
14 {
15   return 2;
16 }
17
18 int main()
19 {
20   int unbenutzt;
21   long lang;
22   short kurz;
23   long undefiniert;
24   long effektlos;
25
26   lang=3;
27   kurz=lang;
28   lang=undefiniert;
29   effektlos=1;
30   lang==effektlos;
31   if(1==0)
32   {
33     lang=4;
34   }
35   while(lang > f()) g();
36   printf("LinuxUser!\n");
37   rueckgabe();
38 }

Ruft man nun lint sample.c auf, erhält man folgende Ausgabe für den geschriebenen Code (wenn Lint Ausgaben für Bibliotheken ausgibt, so können diese fürs erste ignoriert werden).

volker@maus> lint sample.c
sample.c:
sample.c(28): warning: undefiniert may be used before set [158]
sample.c(20): warning: unbenutzt unused in function main [192]
sample.c(27): warning: kurz set but not used in function main [191]
sample.c(38): warning: function main falls off bottom without returning value [217]

Es werden verschiedene Warnungen ausgegeben:

  • Die erste Warnung bezieht sich auf die Zeile 28 und stellt korrekterweise fest, dass die Variable undefiniert nicht initialisiert wurde, aber dennoch der Variablen lang zugewiesen wird. Hier wurde der Speicherinhalt, der sich zufällig hinter undefiniert verbirgt, weitergereicht. So etwas ist nicht nur ein schlechter Programmierstil, das ist einer der Hauptgründe dafür, dass Software einfach abstürzt.
  • Die zweite Warnung verweist auf die Zeile 20 und stellt fest, dass ich im Programm die Variable unbenutzt deklariert habe, ohne sie an irgendeiner weiteren Stelle wieder zu benutzen. Das führt zwar nicht zu Abbrüchen des Programms, verbraucht aber sinnlos Speicher und verwirrt nur, wenn man das Programm später noch einmal liest. “Wozu habe ich die Variable denn nochmal gebraucht???”
  • Die dritte Warnung erkennt in der Zeile 27, dass die Zuweisung der long-Variablen lang auf die short-Variable kurz zu einem Informationsverlust führen wird. Denn long-Variablen verwenden vier Byte Speicher und können Zahlen von -2147483648 bis +2147483647 beherbergen, short-Variablen belegen aber nur ein Byte Speicher und reichen daher nur für -128 bis 127 aus. Eine Zuweisung ist daher äußerst gefährlich.
  • Die vierte Warnung hat sich die Struktur der Funktion main() genauer angesehen. Für diese Funktion wurde bei der Funktionsdeklaration der Rückgabewert int angegeben. Die Funktion selbst macht eine ganze Menge. Nur was sie nicht macht, ist einen Wert mit return zurückzugeben. Das habe ich leider schon oft bei C-Programmierern beobachten können. Sie schludern das return in der main()-Funktion. Das ist nicht nur schlechter Stil, sondern verhindert zudem, dass die aufrufende Instanz (in der Regel die Shell oder ein Skript), das Erfolgsverhalten des C-Programms kontrollieren kann. Stattdessen ist die Rückgabe des C-Programms nicht definiert.

LCLint – lint für die, die es wirklich besser machen wollen

Nachdem sich Lint schon früh zu einem Standard zur Qualitätsuntersuchung von C-Quellcode entwickelt hat, stiegen die Ansprüche an ein solches Werkzeug. Es gibt eine Reihe von Fehlerkategorien, die man in seinem C-Programm erzeugen kann, die Lint jedoch nicht findet.

Daher wurde das bessere Lint, LCLint, geboren, welches heute im NASA Langley Research Center weiter entwickelt wird. Dem Programmierer steht das Kommando lclint zur Verfügung, welches nicht nur eine ganze Reihe der traditionellen Lint-Überprüfungen wie Typüberprüfung, Benutzung undefinierter Variablen, ignorierte Funktionsergebnisse oder unbenutzte Deklarationen zur Verfügung stellt, sondern dem Profi Funktionalitäten für Macros, Aliasing, Namenskonventionen, Speicher-Sharing etc. bietet.

Um sich mit LCLint vertraut zu machen, versucht man am besten die gleiche Taktik wie mit Lint: Man probiert es aus. Der erste Versuch bringt bereits eine ganze Reihe von Information:

> lclint sample.c

Für den Anfänger ist es aber einfacher, LCLint mit der Option -weak (für schwache Überprüfung) aufzurufen, denn die meisten C-Programme produzieren derart viele Meldungen, dass man vor lauter Text zurückschreckt. Das hat LCLint aber nicht verdient und soll daher zuerst mit der Beginner-Option betrachtet werden:

>lclint -weak sample.c

Was dabei herauskommt, ist schon weitaus überschaubarer und erinnert nicht von ungefähr an die Ausgabe des Klassikers Lint:

sample.c: (in function main)
sample.c:27,3: Assignment of long int to short int: kurz = lang
To ignore type qualifiers in type comparisons use +ignorequals.
sample.c:20,7: Variable unbenutzt declared but not used
A variable is declared but never used. Use /*@unused@*/ in front of
declaration to suppress message. (-varuse will suppress message)

Erster Bonus zu Lint: LCLint gibt die Spalte aus, in der es etwas gefunden hat. Hier bemerkt LCLint in der Zeile 27 ab der Spalte 3 die Zuweisung der vier Byte langen long-Variablen auf die ein Byte lange short-Variable. Mit der Option -weak wird zusätzlich erkannt, dass es eine Variable gibt, die zwar deklariert wurde aber nicht benutzt wird.

Optionen

Was uns dieses erste kleine Beispiel lehrt, ist, wie man beim Aufruf von LCLint Optionen setzen oder deaktivieren kann. Sollen Fehlermeldungen zum Typkonflikt nicht mehr ausgegeben werden, so geht das einfach – wie schon der Fehlertext beschreibt – mit:

> lclint -weak +ignorequals sample.c

Hier sehen wir eine etwas Unix-untypische Vorgehensweise beim Setzen der Optionen: Während mit einem Pluszeichen eine Option gesetzt wird, deaktiviert ein Minus sie wieder. Die Plus/Minus-Optionen sollen Klarheit verschaffen, können aber aufgrund ihrer vom Unix-Standard abweichenden Form Verwirrung stiften. Daher ist es sehr angenehm, dass Fehlermeldungen sagen, wie eine Option zu setzen ist (im obigen Beispiel mit -varuse will suppress message).

Es ist noch zu bemerken, dass LCLint über 300 Optionen kennt, die man nicht alle eintippen möchte, wenn man LCLint aufruft. Daher können die Standardoptionen in der Datei .lclintrc im aktuellen Verzeichnis dauerhaft gesichert werden. Die hier gesetzten Optionen überschreiben die allgemeinen Einstellungen in der Datei ~/.lclintrc im Home-Verzeichnis des Anwenders. Letztendlich ist die Angabe einer Option beim Aufruf natürlich maßgeblich und überschreibt die vorbestimmten Einstellung der Datei .lclintrc.

In Listing 2 (using.c) sieht man die Möglichkeit, stilisierte Kommentare (engl. stylized comments) zu benutzen. Sie sind einer der ganz großen Vorteile von LCLint gegenüber dem klassischen lint-Befehl, da sie es ermöglichen, LCLint-Anweisungen und Zusatzinformationen in den Quelltext einzubauen.

Listing 2

using.c

1 int main()
2 {
3  int ungebraucht;
4  /*@unused@*/
5  int braucheIchNoch;
6
7   return 0;
8 }

Auf dieses Listing reagiert LCLint mit der folgenden Ausgabe:

using.c: (in function main)
using.c:3,7: Variable ungebraucht declared but not used
  A variable is declared but never used. Use /*@unused@*/ in front of
  declaration to suppress message. (-varuse will suppress message)

Der Nichtgebrauch der Variablen braucheIchNoch wurde LCLint durch die Kommentierung mit /*@unused@*/ als gültig gemeldet. Es erfolgt keine Fehlermeldung. Gleichzeitig ist die Variable ungebraucht nicht entsprechend markiert; demzufolge wird sie von LCLint angemeckert.

Doch nun zum besseren Lint. Dazu lassen wir einfach die Option -weak einmal weg und sehen uns an, was LCLint standardmäßig ausgibt, wenn wir es auf unser Listing 1 losjagen (Kasten “Ausgabe von lclint sample.c“).

Ausgabe von

lclint sample.c

sample.c: (in function main)
sample.c:27,3: Assignment of long int to short int: kurz = lang
  To ignore type qualifiers in type comparisons use +ignorequals.
sample.c:28,8: Variable undefiniert used before definition
  An rvalue is used that may not be initialized to a value on some execution
  path. (-usedef will suppress message)
sample.c:30,3: Statement has no effect: lang == effektlos
  Statement has no visible effect — no values are modified. (-noeffect will
  suppress message)
sample.c:35,21: Return value (type int) ignored: g()
  Result returned by function call is not used. If this is intended, can cast
  result to (void) to eliminate message. (-retvalint will suppress message)
sample.c:37,3: Return value (type int) ignored: rueckgabe()
sample.c:38,2: Path with no return in function declared to return int
  There is a path through a function declared to return a value on which there
  is no return statement. This means the execution may fall through without
  returning a meaningful result to the caller. (-noret will suppress message)
sample.c:20,7: Variable unbenutzt declared but not used
  A variable is declared but never used. Use /*@unused@*/ in front of
  declaration to suppress message. (-varuse will suppress message)

  • Die erste und die letzte Meldung kennen wir bereits aus dem Beispiel mit der Option -weak; auf diese brauchen hier nicht nochmals eingehen.
  • Die zweite Meldung erklärt, dass die Variable undefiniert uninitialisiert zugewiesen wird (das hat der Lint schon herausbekommen).
  • Die dritte Meldung ist neu, denn hier erkennt LCLint, dass das Ergebnis der Zeile 30 keinen Effekt hat. Da hier mit dem doppelten Gleichheitszeichen gearbeitet wird, ist das Resultat dieses Vergleichs der Wert false, denn lang hat den Wert 3 und effektlos den Wert 1, womit aus dem Vergleich 1==3 das Ergebnis false berechnet wird. Dieses wird jedoch nicht weiter gebraucht oder zugewiesen, also hat LCLint schon ganz recht, wenn er diese Zeile als effektlos erkennt.
  • Die vierte Meldung ist ebenfalls neu; sie wurde vom Lint nicht ausgegeben. LCLint bemerkt, dass die Funktion g() einen Wert vom Typ int zurückgibt, dieser jedoch nicht weiter verwendet wird. Der häufigste Fehler dieser Art wird bei der C-Programmierung beim Öffnen einer Datei mit der Systemfunktion fopen() gemacht. Diese gibt nämlich einen Returncode zurück, welcher dokumentiert, ob das Öffnen der Datei überhaupt geklappt hat. Oft wird dieser Returncode gar nicht erst überprüft, sondern einfach nur die Funktion alleine aufgerufen, so wie in unserem Beispiel sample.c die Funktion g() aufgerufen wird.
  • Die fünfte Meldung ist gleichbedeutend mit der vierten; das Beispiel soll nur noch einmal verdeutlichen, dass der Fehler nicht etwa an der While-Schleife liegt.
  • Die sechste Fehlermeldung ist aus der Betrachtung von Lint bekannt. Die Funktion main() gibt keinen Wert zurück, obwohl sie es nach ihrer Deklaration machen sollte.

ANSI

Dass LCLint tatsächlich noch tiefgründigere Sachen kann, zeigt das folgende Listing 3 (reihenfolge.c):

Listing 3

reihenfolge.c

01 extern int glob;
02
03 extern int aenderglob(void)
04  /*@globals glob@*/
05  /*@modifies glob@*/;
06
07 int main(void)
08 {
09   int i=1;
10   int x;
11
12   x = i++ * i;
13   i+=aenderglob() * glob;
14
15   return 1;
16 }

Bei der Prüfung mit LCLint bekommt man folgendes Ergebnis:

$ lclint reihenfolge.c
reihenfolge.c: (in function main)
reihenfolge.c:12,13: Expression has undefined behavior (value of right operand
                        modified by left operand): i++ * i
  Code has unspecified behavior. Order of evaluation of function parameters or
  subexpressions is not defined, so if a value is used and modified in
  different places not separated by a sequence point constraining evaluation
  order, then the result of the expression is unspecified. (-evalorder will
  suppress message)
reihenfolge.c:13,21: Expression has undefined behavior (value of right operand
                        modified by left operand): aenderglob() * glob
  • Was mit der Berechnung von x++ * x gemeint sein könnte, ist im ersten Augenblick nicht ganz klar. Es könnte bedeuten, dass zuerst x * x gebildet, dann dem Ergebnis zugewiesen, und erst anschließend die Variable x inkrementiert wird. Andererseits könnte es bedeuten, dass erst Variable x genommen, dann inkrementiert, und dann mit dem inkrementierten x multipliziert wird. Eingefleischte C-Profis mögen wissen, was ihr Compiler daraus macht, es ist allerdings nicht nur schlechter Stil, C-Programme auf diese Art und Weise zu schreiben, es verstößt überdies gegen ANSI C; dort ist vereinbahrt, dass alle Seiteneffekte in einer Sequenz bereits beendet sein müssen, bevor dieser Punkt erreicht wird, und keine Seiteneffekte auftreten dürfen, bevor er beendet ist.
  • Interessant ist auch die zweite Meldung von LCLint, die darauf hinweist, dass die Variable glob innerhalb der Funktion aenderglob() geändert wird und damit hier die Reihenfolge der Verarbeitungsschritte nicht definiert ist. Dies konnte LCLint entdecken, obwohl die Funktion aenderglob() selbst eine externe Funktion ist – zu diesem Zweck wurde der stilisierte Kommentar /*@modifies glob@*/ in das Listing eingefügt.

Der Herr der endlosen Ringe

LCLint kann ganz gut Endlosschleifen erkennen. Dazu betrachten wir folgendes kleines Beispiel (Listing 4, endlos.c):

Listing 4

endlos.c

01 extern int f(void)
02   /*@modifies nothing@*/;
03
04 int main(void)
05 {
06   int i=1;
07
08   while(0 < f()) i++;
09
10   return 1;
11 }

LCLint gibt dann aus:

endlos.c: (in function main)
endlos.c:8,9: Suspected infinite loop.  No value used in loop test ([result of
                 f]) is modified by test or loop body.
  This appears to be an infinite loop. Nothing in the body of the loop or the
  loop test modifies the value of the loop test. Perhaps the specification of a
  function called in the loop body is missing a modification. (-infloops will
  suppress message)

LCLint erkennt am stilisierten Kommentar /*@modifies nothing@*/, dass die Funktion keine Variable ändert, und nimmt daher an, dass der Rückgabewert für die Funktion konstant ist. Das riecht förmlich nach einer Endlosschleife, und LCLint erkennt diese.

Das Programm LCLint ist unter http://lclint.cs.virginia.edu/ zu finden. Dort gibt es eine ausführliche Dokumentation. Das zugehörige RPM-Paket gehört zum Umfang der meisten Distributionen (meine SuSE hat es jedenfalls). Insbesondere gilt für den LCLint: Probieren, probieren und weiter probieren.

Wer LCLint richtig anwendet, der findet bereits im Vorfeld viele Fehler. Ich jedenfalls habe meiner Firma unlängst vorgeschlagen, die C-Programme, die wir von außerhalb einkaufen, vorher durch LCLint qualitätszusichern. Und das beweist schon, dass sich LCLint nicht nur für den kleinen Mann, sondern auch für die ganz großen Firmen eignet…

Glossar

Docktoren

Docktor [m] – ein alberner Werftarbeiter

lint

(Syntax: lint [-abceghprvwxzHF]) Das lint-Kommando kennt eine ganze Reihe von Optionen. Hier sind die wichtigsten aufgeführt.

ANSI C

ANSI, American National Standard Institute. Ein 1918 gegründeter gemeinnütziger Zusammenschluss von Unternehmer- und Industriegruppen, der sich mit der Entwicklung freiwilliger Standards beschäftigt. ANSI C korrigierte Unzulänglichkeiten der Sprache C.

LinuxUser 02/2002 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben