Computerprogramm und Programmiersprache
- 1 -
In dem Essay über Keller&Schleifen hatte ich - vor langer Zeit - versucht, einige Basisbegriffe um Computerprogramme und -programmierung wie Stack, Funktion etc. zu erklären, um dem Begriff der Rekursion näher zu rücken. Dieser Text ist in die Jahre gekommen und „klingt” mittlerweile reichlich komisch (was auch und gerade am sprachlichen Gestus liegt). Einige Grundbegriffe will ich noch einmal neu verhandeln, gerade auch, weil sie für eine kontruktivistische Sicht der Dinge ein hervorragendes Feld zum Erproben der Begriffe bieten.
Rolf Todesco schreibt:
Differenztheoretisch bezeichne ich als Computer-Programm eine "Difference" zwischen einem Text […] und einer Instanz des Programm-Objektes, das den schaltsinnmässigen Zustand des mit dem Programm gesteuerten Automaten bestimmt. Ein Computerprogramm ist differentiell ein spezieller Text.
Diese Definition geht von einem abstrakten Automaten aus, der zunächst als Black-Box erscheint, und durch die Instantiierung eines Programmtextes im Hauptspeicher gesteuert wird. Technisch gesehen, kann ich diese Abstraktion so stehen lassen, wobei aber einige interessante Übergänge verloren gehen.
Aus der Sicht des Programmierers ist ein Programm der Sourcecode, an dem er arbeitet. Das ist die textuelle Seite des Programms (dazu später mehr).
Für den Automaten spielt das keine Rolle, weil er nur aufgrund des aus dem Sourcecode generierten Binärcodes funktioniert.[1] Dieser binäre Code liegt als Programmdatei auf der Festplatte, und wird von einem Loader des Betriebssystems in den Hauptspeicher kopiert. Danach wird der Programmcounter des Prozessors auf den Beginn der Adresse des Codes im Speicher gesetzt. Ab dort wird der Speicher als Abfolge von Maschinenbefehlen interpretiert und abgearbeitet.[2] - Dabei findet man eine ganze Reihe rekursiver und selbstbezüglicher Eigenheiten.
Zunächst ist alles, was man auf einem Computer findet, immer nur Eines: Blöcke von Zahlen. Die Differenz zwischen den unterschiedlichen Datenformaten ist auf Maschinenebene völlig unerheblich - eine Textdatei, eine Bild-, Video-, Audio- oder auch Programmdatei sind in einem Editor, der sie als (hexadezimale oder binäre) Zahlen anzeigt, nicht unterscheidbar. Dasselbe gilt für den Unterschied zwischen Festplatte und Speicher: die Dateien, die man in das eine oder andere Medium kopiert, sind identisch (lediglich die Zugriffszeiten auf den Hauptspeicher sind deutlich kürzer als die auf die Festplatte).
Die entscheidende Differenz ist hier diejenige, die man von außen definiert und in das Computersystem als Programm hineinträgt: der Unterschied zwischen Daten und Programmen wird durch ein Programm gemacht.[3][4] Der Computer wird im Zweifelsfall versuchen, eine Foto-Datei als Programm auszuführen - es ist ein Programm, das die Informationen darüber enthält, an welchen Merkmalen man eine ausführbare Programmdatei erkennt (an den drei Zeichen am Ende des Dateinamens etwa), und entsprechend verfährt.
Innerhalb des Programmablaufs findet man diesen Unterschied als Differenz zwischen Daten und Anweisungen gleichsam gespiegelt wieder. Auch hier wird die Differenz dadurch deutlich, daß eine Anweisung erkennt, ob sie sich auf Daten bezieht, oder tatsächlich eine Anweisung im Sinne der Veränderung der Prozessorlogik darstellt. Der Prozessor „sieht sich” eine Zahl „an”, und „entscheidet”, was sie „bedeutet”. Er führt die Anweisung aus, der Programmcounter wird hochgezählt, und die nächste Zahl aus dem Speicher wird gelesen. Dabei gibt es neben jenen Anweisungen, die den Zustand des Prozessors selber verändern, solche, die Daten holen, oder diese verändern. Das kann wiederum selbstbezüglich geschehen: die Daten, die geholt oder verändert werden, können nicht nur von außerhalb, sondern sogar aus dem Programmcode selber stammen. Man kann ein Programm schreiben, das sich im Ablauf selber umprogrammiert (sog. selbstmodifizierende Programme - „gut” programmierte Viren bedienen sich dieser Technik). - Das ist nur ein knapper Überblick, der nicht den Anspruch hat, die Zusammenhänge komplett aufzurollen, sondern nur eine Ahnung vom Grad der Verzahnung zwischen Datenzustand und Programmlogik geben soll.
Der Unterschied zwischen Daten und Programmen wird von Programmen gemacht, was sich auf der Mikroebene des Prozessors wiederholt: der Unterschied zwischen Daten und Anweisungen wird durch Anweisungen vollzogen.
- [1] Dabei spielt es keine Rolle, ob das erst zur Laufzeit durch einen Interpreter geschieht, oder ob bereits im Vorfeld Binärcode von einem Compiler produziert wurde.
- [2] Zumindest ist dies dann der Fall, wenn man ein Compilat startet. Ein vom Interpreter gesteuertes Programm ist deutlich komplexer (und auch langsamer), weil immer wieder einzelne Schnipsel generiert und beim Prozessor angemeldet werden müssen.
- [3] Das erinnert mich unwillkürlich an Luhmann-Sprech: „Als Differenz ist die Differenz eine Einheit”.
- [4] Sehr aufschlußreich ist an dieser Stelle die Analyse des Bootvorgangs, den „ersten Schritten”, die ein Computersystem nach dem Einschalten macht, wenn noch kein Programm geladen ist, das irgendwelche Unterschiede machen könnte.
- 2 -
Aus Sicht der Maschine kann man sagen: ein Programm ist die Differenz zwischen einer Datei und einem Programm. Dabei kann man den Begriff des Programms (als re-entry im Sinne George Spencer Browns) rekursiv wieder in die Definition einspeisen: die Differenz zwischen Programm und Datei wird wieder von einem Programm realisiert.[1]
Aus der Sicht des Programmierers ist ein Programm aber keine binäre, maschinenlesbare Datei, sondern ein für Menschen lesbarer Text in Form von Sourcecode. Dabei gibt es eine Reihe unterschiedlicher Stufen von Abstraktionen vom Binärcode - und dann noch prinzipiell die Frage, was einen Text zum Sourcecode werden läßt.
Die erste Stufe, vom Binärcode zu abstrahieren und ihn für Menschen lesbar zu machen, nennt sich Maschinensprache, oder Assembler. Dabei wird jeder Befehl, den ein Prozessor verstehen kann, mit einem Namen versehen. Statt einer Zahl kann man jetzt ein Kürzel aufschreiben, und zwar in eine reine Textdatei, die ein Programm (ein sog. Assembler) hinterher in Binärcode übersetzt. Dabei ändert das nichts daran, daß der Programmierer direkt in Maschinenlogik denkt. Für jeden Assemblerbefehl gibt es ein konkretes Gegenstück in der Zahlenwelt; die Anweisungen werden in genau der Reihenfolge aufgeschrieben, in der sie der Prozessor hinterher „liest”.
Einen großen Schritt weiter gehen sog. Hochsprachen. Sie vergeben nicht nur Namen für Zahlen, sondern führen eigene logische Strukturen ein, die der Prozessor nicht mehr direkt umsetzen kann. Der Sourcecode muß erst durch ein Programm (einen sog. Compiler) übersetzt werden, welches teilweise äußerst komplexe Operationen durchführt, um binären Code zu erzeugen. Ein Konstrukt wie eine „if-else”-Anweisung, mit der ein Programmierer ausdrücken kann, daß bestimmte Codebereiche nur aufgrund von bestimmten Bedingungen durchlaufen werden sollen, ist – aus der Sicht der Prozessorlogik – schlicht nicht möglich. Der Compiler muß das erst in eine Folge von Sprungbefehlen zerlegen, die der Prozessor ausführen kann. Ähnliches gilt für eine ganze Reihe weiterer Konstrukte, die alle Hochsprachen mehr oder weniger ähnlich implementieren: der Sourcecode sieht erheblich anders aus, als der Text in Assembler, den der Compiler erzeugt.
Ein dritter Schritt führt zu den sog. objektorientierten Sprachen. Hochsprachen wie „C” oder „Pascal” definieren Abläufe, denen das Programm folgt. Das ist noch nicht allzu weit entfernt von der eigenen Logik des Prozessors, der zwar keine Abläufe kennt, wohl aber Sprünge. Wesentlich komplexer werden die Verhältnisse, wenn in „C++” oder „Lisp” sog. Klassen zum Grundbestand des Programmierens werden. Classes (ich benutze das englische Wort, wenn ich Klassen in objektorientierter Software meine) sind Konstrukte, mit denen sich Objekte der realen Welt virtuell nachbilden lassen. Der Programmierer definiert etwa eine Class, mit der sich ein Rechteck auf dem Bildschirm beschreiben läßt. Diese Definition umfaßt sowohl Daten, um Koordinaten, Breite und Höhe zu speichern, als auch Methoden, um diese Daten zu manipulieren - z.B. um ein Rechteck größer oder kleiner zu machen, oder zu plazieren. Eine andere Class könnte einen virtuellen Pinsel beschreiben, der Atribute wie z.B. eine Farbe bekommt, und mit dem man ein (durch erstgenannte Class beschriebenes) Rechteck auf dem Bildschirm „malen” kann. - Etc., es kommt mir hier nicht darauf an, objektorientiertes Programmieren zu beschreiben, sondern ich will deutlich machen, daß man sich von der Maschinenlogik hier ganz erheblich entfernt. In welchem Maß dies geschieht, kann man allein an der Komplexität ermessen, die die Compiler annehmen. Einen C-Compiler zu schreiben, ist eine nicht gerade triviale Aufgabe. Einen Compiler zu schreiben, der das objektorientierte Gegenstück C++ übersetzt, dürfte mehr Probleme bereiten als die Konstruktion einer Rakete zum Mars.[2]
Im letzten Schritt schließlich gibt es Texte, die unverzichtbar für die Programmierarbeit sind, die aber nur noch ansatzweise von Programmen les- oder übersetzbar sind, sondern sich direkt an die Menschen richten. Das betrifft zum einen die Dokumentation im und über den Sourcecode, ohne die sich bestimmte Konstruktionen und Designs kaum erschließen lassen. Sourcecode ist zwar für den Programmierer lesbar, muß aber teilweise mühsam entziffert und in einen Zusammenhang gebracht werden, um einen Sinn zu ergeben. Dokumentation ist dafür gedacht, diesen Prozeß abzukürzen. Zum anderen gibt es eine Reihe von Tools und Verfahren, die den Designprozeß unterstützen sollen, ohne daß die dort erstellten Texte direkt zum Computerprogramm werden. Dies sind z.B. Prototypen in Pseudo-Code, oder auch UML-Grafiken. Bei letzteren verläßt man bereits die rein textuelle Ebene und landet bei Grafiken, die mit einer stark formalisierten Symbolik die Vorbereitung von Code-Strukturen erleichtern oder - beginnend mit einer bestimmten Komplexität des Programmdesigns - überhaupt erst ermöglichen.
- [1] Den „Trick”, einen Begriff zu definieren, indem man ihn selbst verwendet, um ihn hinterher ein drittes Mal rekursiv auf sich selbst zu beziehen, habe ich jetzt hoffentlich richtig kapiert. - Mehr findet sich bei der Hyperkommunikation.
- [2] Das ist jetzt kein ironischer Seitenhieb in Richtung Microsoft, wo man es nach wie vor nicht hinbekommt, einen C++-Compiler auf den Markt zu werfen, der alle Standards einhält, sondern der Versuch, einen Satz aufzuschreiben, der die Realität beschreibt.
- 3 -
Wenn man Computerprogramme als speziellen Text (Sourcecode) definieren will, muß man erst einmal genauer hinsehen, wie sich Text im Computer überhaupt darstellt, um dann zu bestimmen, in welcher Weise sich „normaler” Text von Sourcecode unterscheidet.
Dabei muß man sich einmal mehr klar machen, daß jede Datei nur aus Zahlen besteht, also auch eine Textdatei. Text sieht man auf einem Computerbildschirm erst dann, wenn diese Zahlen von einem Programm als Text ausgedeutet werden. Im einfachsten Fall wird jedes Byte einer Datei als Index benutzt, der in eine Tabelle aus Buchstaben verweist (der ASCII-Zeichensatz ist die früheste Standardisierung solch einer Tabelle). In moderneren Standards, die auch außereuropäische Zeichen umfassen, kommt man mit einem Byte nicht aus. Um Speicherplatz zu sparen, werden Zeichen, deren Index mehr als ein Byte umfaßt, mit einem speziellen Steuercode eingeleitet, aus dem hervorgeht, wie viele Bytes dieses eine Zeichen definieren (Unicode, UTF-8 - die Einführung von Joel Spolsky ist sehr brauchbar). Schon an dieser Stelle wird klar, daß selbst Programme, die einfach nur eine Datei als Text anzeigen wollen, durchaus einige Intelligenz realisieren müssen.
Noch komplexer wird es, wenn man auch Formatierungen und Text-Atribute in die Ausgabe packen will. Die Datei beinhaltet jetzt nicht nur den reinen Text, sondern eben auch Formatierungen, und muß von einem entsprechenden Programm dechiffriert werden. Ein Beispiel wäre eine HTML-Datei, in der Absätze, Überschriften etc. definiert werden, was vom Parser eines Browsers interpretiert werden muß, bevor der Text auf den Bildschirm ausgegeben werden kann.
Man findet häufig die Auffassung, daß schon ein HTML-Dokument ein Programm darstellt. Demzufolge wäre aber auch ein „Word”- oder RTF-Dokument ein Programm, weil auch hier Metainformationen abgelegt sind, die von einer Engine[1] in der Textverarbeitung decodiert werden müssen. Aber schon die Ausgabe einer ASCII-Datei entspricht ja nicht dem Aufschlagen eines Buches, sondern ist die Entschlüsselung eines Codes.
Ich schlage vor, Sourcecode von übrigem Text zu trennen, indem man sagt, Sourcecode sei nicht nur lesbar, sondern für das Operieren jener Maschine verantwortlich, die (ihren eigenen) Text lesbar macht.
„Normaler” Text soll auf den Bildschirm gebracht werden, dann ist alles erledigt. Die Zahlen in der Datei werden lediglich decodiert. Sourcecode hingegen ist zwar auch auf den Bildschirm lesbar, muß aber in einem weiteren Schritt „zurück” auf die Hardware, um die Operationen des Computersystems zu bestimmen.
In beiden Fällen ist ein Programm nötig, das das Datenformat interpretiert, in dem der Text abgelegt ist (ASCII, HTML, etc.). Im Fall von Sourcecode wird ein weiteres Programm gebraucht, das Maschinencode generiert (der Compiler).
Wenn man diese Definition akzeptiert, hat man eine klare Trennung, spricht dabei allerdings all jenen Texten die Eigenschaft ab, Sourcecode zu sein, die bestimmte Aspekte lediglich steuern. Das betrifft z.B. HTML, aber auch Skripte, die das Aussehen von Websites definieren (CSS) und deren Autoren man normalerweise als Programmierer bezeichnet.
Aus der Sicht des Programmierers ist ein Programm die Differenz zwischen Programm und Sourcecode. Re-entry: ein Programm wird gebraucht, um ein Programm zu generieren.[2]
- [1] Ich verstecke mich mal hinter dem englischen Begriff. „Automat” paßt hier ebenso wenig wie „Programm”, weil mal es nicht mit einem System zu tun hat, das auf Autopoiesis beruht. In- wie Output definieren sich hier über eine klar definierte „Schnittstelle” - ein Begriff, den man in die Systemtheorie erst einfügen müßte (zumindest soweit ich das Thema überblicke).
- [2] Richtig lustig wird dieses re-entry übrigens, wenn man einen Compiler entwickelt - wenn man z.B. einen C-Compiler in C schreibt, ist der in der Lage, sich selber zu übersetzen.