Bernd Leitenbergers Blog

Die Seuche „C“

Zeit mich mal wieder unbeliebt zu machen, oder besser gesagt zu provozieren. Es geht um die für mich übelste Programmiersprache: „C“. Warum ist C so übel? Nun man könnte es auf den Punkt bringen, das hat dieser Aufsatz getan, der nicht von mir stammt, aber den ich übernommen habe. Fangen wir mal an mit einigen Mythen, die immer genannt werden, wenn es um die Vorteile von „C“ geht:

„C ist maschinennah und daher besonders gut geeignet für die Programmierung von hardwarenahen Programmen wie Betriebssystemen.“

Dieser Mythos mag in den siebziger Jahren gegolten haben, aber danach nicht mehr. Das hat nun auch weniger mit der Sprache zu tun, sondern mit dem Compiler, der den Code erzeugt. Für alle, die nicht so mit Assembler vertraut sind: die meisten Prozessoren haben Befehle die bei bestimmten Situationen geeignet sind und schneller ausgeführt werden als andere, sozusagen Spezialfälle. So verändern die Befehle INC und DEC einen Operanden um jeweils 1 und das Schieben eines Registers entspricht einer Division oder Multiplikation mit 2. Diese Operationen gehen schneller als eine Addition/Subtraktion oder Division/Multiplikation.

Derartige Operationen gibt es in C. Der Vorteil: Es ist so ein einfacher und effizienter Compiler erstellbar, da praktisch eine Arbeit des Compilers, nämlich den optimalen Code zu erstellen, auf den Programmierer ausgelagert wurde. Doch das ist keine Eigenschaft der Sprache, denn natürlich hindert niemand den Ersteller eines Compilers daran aus einer Instruktion wie a=a+1 den gleichen Code wie bei a++ zu generieren. Und das funktioniert nicht nur bei C so. Schon als in den achtziger Jahren Turbo Pascal herauskam schlug es viele etablierte C-Compiler, weil dieser Compiler so effizient war. Nur eben nicht mit der Sprache C, sondern Pascal.

Mit der Maschinennähe ist aber dann schon Schluss, denn wirklich maschinenah kann keine Hochsprache sein. Sie soll ja auf unterschiedlichen Prozessoren laufen. Erweiterungen, die es in C aber auch anderen Sprachen gibt, um Assemblercode einzufügen oder Betriebssystemroutinen aufzurufen sind nicht Bestandteil des Standards.

Mit der Geschwindigkeit von C ist es auch so, dass sie nicht an der Sprache fest machbar ist, sondern am Compiler. Microsoft reklamiert für sein .NET dass es unter Umständen schneller als C ist, weil diese Sprache erst zur Laufzeit übersetzt werden soll und dann die Befehle nutzt die der Prozessor unterstützt, während ein kompiliertes Programm sich immer an den kleinsten Standard halten muss. Wie ich schon erwähnte zeigte auch schon die Vergangenheit, dass andere Sprachen C leicht in Geschwindigkeit schlagen, wenn der Compiler entsprechend gut ist.

Diesen heute praktisch nicht mehr vorhandenen „Vorteilen“ stehen etliche Nachteile gegenüber.

Das erste ist, das C recht schlampig ausgelegt wurde, damit man wenig Aufwand mit dem Compiler hat. Es wurde auf fundamentale Sicherheitsüberprüfungen verzichtet, wichtige Funktionen wurden durch einfache, aber fehleranfällige Lösungen ersetzt und Verantwortung auf den Programmierer ausgelagert. Einige Beispiele:

Es ist in C, anders als in anderen Programmiersprachen bei if eine Zuweisung erlaubt also „if (c=5) “ anstatt „if (c==5)“. Das ist äußerst fehlerträchtig vor allem weil jeder von der Mathematik ja noch das „=“ als Vergleichsoperator kennt.

Die String-Bibliothek in C lagert praktisch die gesamte Verantwortung auf den Programmierer aus, denn es ist eigentlich keine Stringbibliothek: Es ist nur eine Konvention, dass ein String ein dynamisches Array ist der mit einer binären Null endet. Anders als wie bei vielen Gerüchten geglaubt wird, ist es auch nicht effizient. Denn bei allen Operationen die mit einem String nötig sind, muss man Zeichenweise das Array durchlaufen bis man die binäre Null findet. Ein System dass die Länge vor dem ersten Zeichen speichert wäre viel effizienter. Das erlaubt es Blockkopier/Verschiebebefehle von Prozessoren zu benutzen, die es schon bei 8-Bittern gibt.

Der Präprozessor ist im Prinzip eine programmgesteuerte Suchen/Ersetzfunktion. Ich bin beim Suchen/Ersetzen in meinem Quelltext sehr vorsichtig und nun soll ich das machen mit einem Text denn ich nicht mal, sondern nur der Compiler zu Gesicht bekommt? Vergiss es!

Das wohl problematischste und inzwischen auch allgemein bekannteste Sicherheitslücke von C: Der Buffer-Overflow. Er entsteht dadurch das Strings einer Routine nicht als Wertparameter, sondern als Zeiger übergeben werden. Die Routine muss dann in eine lokalen Variablen den String kopieren und wenn der Speicher hier kleiner als der String ist, dann überschreibt der String andere Variablen und die danach folgende Rücksprungadresse. Das wird ausgenutzt um verschiedene über das Internet erreichbare Dienste zu missbrauchen um Schadcode in Systeme einzuschleusen. So was ist nicht möglich bei Systemen die eine andere Stringverarbeitung haben wie z.B. Felder vor dem ersten Zeichen in denen die Länge steht und die es auch erlauben Strings anders zu übergeben.

Die Frage ist wie immer: was nützt es und was bringt es? Natürlich ist es von Vorteil, manchmal auf die niedrigste Ebene einer Maschine zu kommen. Doch dann halte ich es für besser eine definierte Schnittstelle zu Assembler einzubauen, weil dann der Benutzer weiß was er tut. So hat man eine Hochsprache kombiniert mit der Fehleranfälligkeit von Assembler. Der Nutzen für alle die nicht in Assembler programmieren wollen oder auf Betriebssystemebene gehen wollen ist aber nicht gegeben, sie müssen nur mit den Nachteilen leben. Mein früherer Informatikprofessor hat mal C als „Superassembler“ bezeichnet und das trifft es ganz gut. Nur ist leider C nicht so außer Mode gekommen wie Assembler….

Das verrückte ist, dass es einige Leute gibt, die so was lieben und die offensichtlich Einfluss haben, sonst hätte sich die Sprache nicht durchgesetzt. Das ist ein Kontrast zum täglichen Leben, wo wir in der Regel Sicherheit haben wollen und Leute die bewusst drauf verzichten, als Freaks ansehen. Also wenn jemand Fahrrad ohne Bremsen fährt oder ein Auto ohne Airbag, Gurte und Knautschzone herstellt, dann würde das wenig Verständnis bei den meisten anderen Leuten hervorrufen. 100 Stunden pro Wochen Programmierer mit Vorliebe für Pizza, Coke und Abneigung gegen Duschen und soziale Bindungen, also die die in Firmen rasch aufsteigen, scheinen aber genau das zu lieben und machen so Karriere und führen zum Einsatz von C den sie nun dank ihres Einflusses unternehmensweit durchsetzen können.

Okay, inzwischen hat man vieles entschärft. Compiler haben nun standardmäßig einige Optionen mit denen man die meisten Übel von C begegnen kann, aktiviert. Will man eigentlich standardkonforme Programme erstellen, muss man sie erst wieder deaktivieren. C hat sich auch weiter entwickelt, so diese unselige Definitionen der Funktionsköpfe im K&R Standard, die eigentlich keiner so richtig verstanden hat.

Das Problem ist natürlich das so ein System wie magisch alle Freaks anzieht und die dann darauf aufbauend noch schlimmere entwickeln, wie z.B. C++ (C erweitert um die Unverständlichkeit und Komplexheit), Perl (für alle die C Quelltexte zu verständlich und die Syntax zu leicht merkbar finden) oder Linux (ohne zwanzig Kommandozeilenoptionen für jedes Programm, die man sich nicht merken kann, nur halb so lustig) oder reguläre Ausdrücke (so einfach, dass es vom Entwickler ein ganzes Buch darüber gibt und natürlich ist die Standardeinstellung jedes RegEx Systems „gierig“, obwohl die Benutzer es meistens „nicht gierig“ haben wollen.

Ich denke anders: Entwicklungssysteme sollen mir das Leben einfach machen. Ich möchte schnell Programme entwickeln und nicht dauernd über Fehler stolpern, die man abfangen kann. Nennt mich Warmduscher oder Bequemlichkeitsfreak, aber ich bin zumindest kein Masochist. Wenn ich dran denke welche Probleme schon meine Studis mit einer Programmiersprache mit doppelten Airbags haben, ich wage nicht dran zu denken was passiert wenn die in C programmieren müssten…

Die mobile Version verlassen