Der Umgang mit der Programmiersprache C will gelernt sein:

Vor "optimierten" Programmen wird gewarnt

04.08.1989

Neben positiven Eigenschaften hält "C" auch einige Fußangeln bereit. Erich Neuhauser* warnt unter anderem davor, durch "Optimierung" die Programme unlesbar und damit unwartbar zu machen. Mit seinen Hinweisen möchte er einen vor drei Wochen an dieser Stelle erschienen Artikel zum Thema "C" (siehe Kasten) ergänzen.

Zunächst zum Thema Performance: Die Ausführungszeiten von C-Programmen können in Einzelfällen sogar unter denen von Assembler-Lösungen liegen. Diese zunächst unwahrscheinlich klingende Aussage erklärt sich aus der Funktion der Post-Optimizer. Im Interesse schneller Ausführung wird dabei auch die Reihenfolge von Anweisungen verändert.

Den so entstehenden Assembler-Code könnte zwar auch ein Programmierer erzeugen; er müßte aber die Forderung, wartungsfreundliche (also lesbare) Programme zu schreiben, vollkommen zurückstellen. Somit ist ein verantwortlicher Assembler-Programmierer manchmal tatsächlich nicht in der Lage, die Geschwindigkeit eines C-Programms zu übertreffen.

So betrachtet wird klar, warum manchmal von C als einem "Maschinen-unabhängigen Assembler" gesprochen wird. Tatsächlich stellen einige Performace-Prozente kaum einen ausreichenden Grund dafür dar, auf die Vorteile der Maschinen-Unabhängigkeit einerseits und der deutlich höheren Wartungsfreundlichkeit andererseits zu verzichten.

Soweit es nicht um Hardware-spezifische Programmierung geht, sollte Assembler ausgedient haben. "Trost" findet der Assembler-Programmierer bei C einerseits in Sprachelementen, die eine Optimierung bereits durch den Programmierer erlauben, und andererseits in der noch zu besprechenden Möglichkeit, auch in C absolut unverständliche Programme zu schreiben.

Zur Hardware-nahen Programmierung gehören zunächst die nachfolgenden Befehlstypen (in Klammern jeweils der gleiche Befehl in Langform:

X++ (X = X+1)

X-- (X = X-l)

X + = Y (X =X+Y)

X* = Y (X = X*Y)

Selbstverständlich kann man solche Befehle auch in der üblichen "Iangen" Schreibweise angeben. Der Compiler kann aber die Kurzbefehle auf den meisten Maschinen in kürzeren Maschinencode umwandeln als die alllgemeineren Langbefehle.

Nicht alle Prozessoren haben genügend Register

Eine weitere Möglichkeit zur Optimierung stellt die Bezeichnung einer Variablen aIs "Register"-Variable dar. Derart gekennzeichnete Variablen versucht der Compiler möglichst in Hardware-Registern zu führen, was die Ausführungsgeschwindigkeit beträchtlich erhöhen kann. Voraussetzung ist freilich, daß ausreichend Register zur Verfügung stehen, was

beispielsweise bei den 80X86-Prozessoren nicht immer gegeben ist.

Darüber hinaus gibt es in C Optimierungsmöglichkeiten, die verboten gehören - ähnlich wie die Benutzung "unbekannter OP-Codes" (also vom Hersteller nicht definierter OP-Codes) in der Assembler-Programmierung. Zwei noch eher harmlose Bespiele sind

X = ++Y; und

X = Y++;

In beiden Fällen wird der Inhalt von Y um 1 erhöht. Dem X wird der Inhalt von Y im ersten Fall vor der Erhöhung und im zweiten Fall nach der Erhöhung zugeordnet. Zwar ist der menschliche Intellekt grundsätzlich in der Lage, derart geheimnisvolle Formulierungen zu entziffern; aber man schreibt ja heute auch Fachartikel in einer lebenden Sprache und nicht in Altgriechisch.

Wirklich abschreckende Beispiele werden oft von Leuten geliefert, die es mit C eigentlich gut meinen. Voller Stolz wird in einem C-Handbuch eine Quicksort-Routine vorgeführt, die in Fortran und Basic jeweils eine Seite benötigt, in C aber auf sechs Zeilen abgehandelt wird. Genies freuen sich anscheinend über den "kompakten Code", aber Normaldumme hätten lieber Software, die man, weil verständlich, auch warten kann.

Eine weitere Folge des Bemühens um "kompakten Code" hat sich in der Syntax der Vergleichsoperatoren niedergeschlagen. In den meisten Programmiersprachen wird die Abfrage auf Gleichheit zwischen zwei Operanden etwa so formuliert:

IF (A = B) . . .

Nicht so in C. Dort schreibt man: IF(A == B)...

Tragischerweise ist unter C auch die erste Formulierung legal, allerdings mit einer vollkommen anderen Bedeutung, nämlich: "Ordne den Inhalt von B der Variablen A zu - falls der Inhalt ungleich 0 ist, führe den nachfolgenden Befehl aus." Der echte C-Freak lächelt hier wissend - für Menschen, die auch mal in anderen Sprachen programmieren, kann diese Eigenheit von C den Verlust ganzer Arbeitsstunden bedeuten.

Auch auf dem Gebiet der Funktionsaufrufe gibt es gute Chancen, durch einfache Programmierfehler Befehle zu erzeugen, die in C als legal angesehen werden, aber eine völlig andere Bedeutung haben, als vom Programmierer erhofft.

Doch nun zurück zu den Möglichkeiten, unverständliche Programme zu erzeugen: Ein Textprozessor, der automatisch vor jeder Compilierung ausgeführt wird, ist bei C-Compilern Standard. Er ermöglicht es dem Programmierer, die Sprache um eigene (mächtigere) Konstruktionen zu erweitern - in ähnlicher Form, wie man dies von Makro-Definitionen in Assembler kennt.

Die Anwendung schlecht geplanter Makros in möglichst großer Zahl mit möglichst kurzen, symbolischen Namen erlaubt es, Programme zu schreiben, die der Programmierer selbst bereits während der Erzeugung nicht mehr versteht. Besonders wirksam ist auch die Anwendung von untereinander sehr ähnlichen Makros. Typischerweise entstehen diese aus Makros, die später laufend verbessert werden.

Andererseits ermöglicht es der Textprozessor, überlegt eingesetzt, die Sprache C um Aufgaben- oder sogar Unternehmens-spezifische Konstruktionen zu erweitern, was sowohl die Produktivität der Programmierer, als auch die Wartbarkeit der Programme verbessern kann.

Ähnlich flexibel, aber nur bei guter Planung nützlich, ist das Konzept der "Funktionen" in C. Funktionen sind Bibliotheksroutinen, deren Aufruf Parameter enthalten kann und die Werte zurückgeben können. Da Funktionen sowohl auf lokale (innerhalb der Funktion definierte) als auch globale Variablen zugreifen können, stellen sie ein flexibles und relativ sicheres Instrument zur modularen Programmierung dar. Bei sorgfältiger Planung erscheinen häufig gebrauchte Funktionsaufrufe wie zusätzliche C-Befehle. Vor allem in Verbindung mit den Möglichkeiten des Textprozessors können problemorientierte "C-Varianten" erzeugt werden.

Beispielsweise hat keine der gängigen Programmiersprachen Befehle im Bereich der Bedienerschnittstelle, die eine wirklich menschenfreundliche Programmierung bei vernünftigem Aufwand erlauben. Das offene Konzept von C ermöglichte die Aufnahme solcher Befehle, mit denen eine optimale Bedienerführung bei geringstem Aufwand programmiert werden kann. Ähnliche Erfolge wurden mit Erweiterungen um kommerziell orientierte Funktionen, wie zum Beispiel ASCII-Arithmetik, erzielt.

Insgesamt kann gesagt werden, daß es bei kompetentem Einsatz von C keine Alternative geben dürfte. Wer C "im Griff" hat, wird sich weiterhin weder mit anderen höheren Programmiersprachen noch mit Assembler abgeben wollen. Und ein Unternehmen, das C geplant einsetzt, wird auf die Vorteile bald nicht mehr verzichten wollen und können.

Aber: Vorbereitung ist alles! Gute Ausbildung der Programmierer, viel Zeit zum "Spielen" und zunächst vorsichtiger Einsatz von C sind nötig. Erst wenn ausreichend Fachkompetenz verfügbar ist, kann man einerseits die zum Überleben notwendigen Restriktionen beim Gebrauch von C festlegen und andererseits in Form geplanter Funktionen und Makros die Basis für die künftige Effektivität erzeugen.