Inhaltsverzeichnis

Historie

Der Beginn der Programmierung wird mit Lady Ada, Countess of Lovelace, datiert. Sie lebte in der ersten Hälfte des 19. Jahrhunderts und war Assistentin von Charles Babbage. Für Babbages Rechenmaschine, die zu Lady Adas Lebzeiten (1815-1852) jedoch noch nicht realisiert werden konnte, entwickelte sie theoretische Programmabläufe.

Mitte der 40er Jahre des 20. Jahrhunderts erschien mit Plankalkül die erste Programmiersprache von Konrad Zuse. Als erster Compiler entstand A-0 von Grace Hopper. Fortran (erstmals 1954) etablierte sich als erste höhere Programmiersprache mit hoher Akzeptanz. Sie wird auch heute noch verwendet - wenn auch vorrangig aus historischen Gründen, um vorhandene Programme zu warten.

Das folgende Kapitel vermittelt einen - in Anbetracht der Vielzahl der existierenden Programmiersprachen - sehr kurzen Überblick über die Entwicklung der Programmiersprachen zu den heutigen Sprachen. Einen umfassenderen Überblick über einen Großteil der existierenden Programmiersprachen zu erhalten, empfiehlt sich die 99-Bottles-Of-Beer-Website[12], auf der ein Programm in aktuell 701 Variationen zu finden ist.

Um einen Algorithmus zu formulieren, bedarf es einer eindeutigen Syntax bzw. Semantik.
Programmiersprachen können nicht mehr kurzfristig geplant werden, da eine erfolgreiche Sprache nach ihrer Veröffentlichung nicht mehr ohne den Protest ihrer Nutzer kurzfristig geändert werden kann.

Die Entwicklung einer neuen Programmiersprache stützt sich in der Regel auf vorhergehende Programmsprachen. Entsprechend lohnt sich ein Überblick über die grundlegenden Erfahrungen, die mit Programmiersprachen in 60 Jahren gesammelt wurden, seit Programmiersprachen in Gebrauch sind.

Assembler

Als „Mutter aller Programmiersprachen“ erscheint „Assembler“, nach der Programmierung über Schalter, Lochkarten und Hexadezimalen-Zahlen, als erste Vereinfachung in der Programmierung. Assembler übersetzt dabei einfache Befehle in die entsprechende Maschinencodedarstellung (Opcode genannt). Als Beispiel belegt der (IA32-)Prozessor den Wert des AX-Registers mit dem Wert 58 (0x3A), wenn er im laufenden Programm auf die hexadezimale Zahl „B03A“ interpretieren soll. Ein Programm besteht aus langen Zahlenkolonnen dieser Art. Es kann von Hand mittels eines Hex-Editors eingegeben werden, per Schalter am Computer eingestellt oder eben durch eine Textdatei „assembliert“ werden. Anstelle der Zahl „B03A“, kann der Programmierer nun in Assembler „MOV ax, 3a“ (Schreibweise für Intel-Assembler) formulieren, der Assembler schreibt dafür den passenden Opcode.

Dieser Assemblerbefehl ist - wie alle Assemblerbefehle - ein für sich stehendes Konstrukt, das jeweils aus einem Befehl besteht. Jeder Befehl kann zusätzlich ein oder mehrere Parameter besitzen.

Assemblerbefehle wirken sich - logischerweise - auf den Zustand des Prozessors aus. Auf diese Zustandsänderung nach einem Befehl, werden die nachfolgenden Befehle angesetzt. Assemblerbefehle können weder verschachtelt werden, noch stehen sie in irgendeiner Beziehung zueinander. Sie werden lediglich der Reihe nach ausgeführt. Sprünge entsprechen einer Zustandsänderungen im PC-Register (Programm Counter, auch IP-Register für Instruction Pointer genannt).

Ein „Hello World“ in Assembler:

        JMP     start
 
msg     DB      'Hello, World!$', 13, 10
 
start:
        LEA     DX, msg
        MOV     AH, 09h
        INT     21h
 
        RET

Moderne Prozessoren verfügen über zusätzliche Sprungbefehle, um Prozeduren vereinfacht zu realisieren. (CALL/RET beim IA32, JSR/RTS beim MC680x0). Als Beispiel wird das Hello-World-Programm vom Betriebsystem als Unterprogramm gerufen und durch RET wieder verlassen.

In Assembler kann man definitiv alles realisieren, was ein Computer leisten kann, da Assembler eine 1:1 Darstellung des Computerprogramms ermöglicht. Was immer ein Prozessor kann, kann mit Assembler beschrieben werden. Obwohl mit Assembler alles programmierbar ist, bietet Assembler nur wenige Möglichkeiten, sich elegant auszudrücken. Es gibt keine Variablen, lediglich Adressen an die Registerinhalte kopiert werden können. Diese Adressen können ebenfalls mitten im Programm liegen; ein Feature, das findige Programmierer in früheren Tagen dazu nutzen, selbstmodifizierenden Code zu produzieren, so dass Programmabschnitte zur Laufzeit geändert wurden, um ähnliche Aufgaben zu übernehmen. Diese Möglichkeit war bei Rechnern mit wenig Arbeitsspeicher sehr beliebt, heutzutage, wo die Möglichkeit besteht Programme mehrfach gleichzeitig auszuführen, ist diese Art zu programmieren nicht mehr gewünscht, da sich eine Änderung des Codes auf alle laufenden Instanzen auswirken würde. Adresspositionen stellen daher keine Variablen im Sinne einer Hochsprache dar, allerdings können sie zum Speichern von Daten verwendet werden.

Assemblerbefehle haben keine Möglichkeit Ergebnisse zu liefern, Ergebnisse werden lediglich dadurch erzielt, dass sich der Zustand des Prozessors ändert.

Fortran

John W. Backus entwarf 1954 mit Fortran die erste höhere Programmiersprache, die allerdings erst 1957 als Einsatzfähig angesehen wurde. Durch die implizite Variablendeklaration (Variablen mussten also nicht vom Programmierer angemeldet werden), werden alle Bezeichner, die mit i,j,k,l,m,n beginnen, als Integer-Variablen deklariert. Alle anderen Variablen werden als Gleitkommazahl implizit deklariert. Es gibt weitere Datentypen (z.B. Complex), wichtiger ist hier jedoch die Tatsache, dass überhaupt Typen unterschieden werden - auch wenn diese nicht grundsätzlich geprüft werden, sondern dem Compiler lediglich beschreiben, wie ein Bitmuster interpretiert werden soll.
Die Zuweisung einer Real auf ein Integer erzeugt so eine unsinnige Zahl, weil eine Typprüfung oder Konvertierung nicht vorgenommen wird. Fortran brachte erste Ausdrücke in die Programmierung ein, so dass man Berechnungen nicht mehr in vielen einzelnen Befehlen auflisten musste, sondern in einer eher mathematischen Form darstellen kann.

Folgendes Beispiel habe ich auf der Website von Paul Graham[3] gefunden.

C      PROGRAMM FOR FINDING THE LARGEST VALUE
C  X   ATTEINED BY A SET OF NUMBERS
       DIMENSION A(999)
       FREQUENCY 30(2,1,10), 5(100)
       READ 1, N, (A(1), 1*1, N)
     1 FORMAT(I3/12F6.2)
       BIGA = A(I)
     5 DO 20 I = 2, N
    30 IF( BIGA=A(I) ) 10,20,20
    10 BIGA = A(I)
    20 CONTINUE
       PRINT 2, N, BIGA
     2 FORMAT (22H1THE LARGEST OF THESE 13, 12H NUMBERS IS F7,2)
       STOP 77777

Interessanterweise benutzt Fortran das Leerzeichen nicht als Seperator, das Leerzeichen darf also zum Beispiel in Variablennamen enthalten sein, was zu entsprechender Kritik führte.

     5 DO 20 I = 2.4 
    30 IF( BIGA=A(I) ) 10,20,20
    10 BIGA = A(I)
    20 CONTINUE

Die hier beschriebene Schleife verläuft nicht von 2 bis 4, sondern es existiert überhaupt keine Schleife. Der Variablen „D 20 I“ wird der Wert 2.4 zugewiesen, die korrekte Form lautet:

     5 DO 20 I = 2,4 

Allgemein enthält Fortran eine Reihe unglücklicher Definitionen, die aus den zu dieser Zeit noch fehlenden Erfahrungen resultierte. So muss Programmtext war zwischen Spalte 7 und 72 erlaubt, Labels in den Spalten 1 bis 5. Dies wurde durch die Verwendung von Lochkarten vorgegeben.

Fortran war dennoch eine deutliche Vereinfachung von Assembler. Fortran unterstützte Fließkomma- und komplexe Zahlen, eine FPU (Floating-Point-Unit, eine Fließkommazahlen-Erweiterung zum Prozessor) wurde Mitte der 90er noch als zusätzlicher Chip (Intel 80×87, Motorola 6888x) geliefert, bevor sie in die CPUs integriert wurde. Bei Computern ohne FPU mussten Fließkommaberechnungen über die Programmiersprache geregelt werden, eine Arbeit, die der Assemblerprogrammierer selber leisten musste.

Die Sprache stellt damit und mit zusätzlichen Befehlen, wie hier „Print“, also einen konkreten Mehrwert zu Assembler und vereinfacht damit die Programmierung deutlich.
Auffällig ist allerdings auch die Ähnlichkeit zwischen Assembler und Fortran: auch Fortran verlangt eine Einrückung der Befehle, Labels werden - wie in Assembler - links geschrieben.
Der Vergleichsoperator auf Gleichheit, heißt in Assembler JEQ (‚Jump if EQual’), diese Kürzel (‚eq’, ‚ne’, …) wurden in Fortran als Operatoren übernommen.
Zu Beginn wurde Fortran vorrangig mit Labels und Gotos programmiert, da zunächst wie in Assembler keine Möglichkeiten existierten Fortran in irgendeiner Form zu strukturieren.
Eine wichtige Neuerung gegenüber Assembler ist die Verwendung von Variablen, die nicht mehr als nur als einfache Adressreferenzen realisiert wurden, sondern denen Ausdrücke zugewiesen werden können.

Weiterhin existiert ein immer noch aktuelles Problem in C/C++, C# und Java, dessen Ursprung sich offenbar hier findet: Der Vergleich auf Gleichheit wird mit dem Operator „.eq.“ getätigt, die Zuweisung wird hingegen mit dem „=“-Operator geleistet.
Die Zuweisung über den ‚=’-Operator führt in C/C++ auch heute noch gerne zu ungewünschten Ergebnissen beim Vergleichen innerhalb eines if-Konstrukts.

Fortran setzte sich aus einem einfachen Grund durch: Fortran war die erste Programmiersprache, es gab keine Alternativen. Erst 1966 entstand der erste Standard, 1977 der erste Standard, der die einfachsten strukturierten Konstrukte regelte.
Bemerkenswert ist hier, dass keine Klammerung benötigt wird, sondern ein Konstrukt mit einem entsprechenden Schließwort beendet wird:

      if (a .eq. 0) then
        b = min(N+1,N1)
      else
        b = min(N  ,N1)
      endif

Mit dem Standard von 1990 wurde die Formatierung für Lochkarten aufgegeben, weiterhin wurde die Groß-/Kleinschreibung eingeführt und Namen statt Zeilennummern für Sprünge verwendet.

Algol 60

Als Meilenstein der Programmiersprachen gilt Algol 60:
Als erste Sprache führte sie den Systemstack ein[vgl. 17], so dass Programme ermöglicht wurden, die auch rekursiv aufgerufen werden konnten. Die Assembler-ähnliche-Formatierung wurde zugunsten einer strukturierten Schreibweise aufgegeben und somit Bedingungs- und Schleifenkonstrukte möglich, wie sie heutzutage üblich sind.

Ein Hello World in Algol[18]:

program HiFolks;
begin
   print "Hello world";
end;

Viele Algol- Implementationen erwarteten die Schlüsselwörter in einfachen Hochzeichen:

'BEGIN'
   'COMENT' FACULTY;
   'INTEGER' I, N, F;
   READ(N);
   NF:= 1; I:= 0;
   PRINT(I, NF);
   'FOR' I:= 1 'STEP' 1 'UNTIL' N 'DO'
      'BEGIN'
         NF:= NF*I;
         PRINT(I, NF);
     'END';
'END' FACULTY

Der Vergleich auf Gleichheit geschah in Algol mit dem „=“-Operator, die Zuweisung mit dem „:=“-Operator. Alles in allem kann man Algol 60 allerdings bereits Zukunft ansehen: Pascal (1971).

BASIC

Bevor Pascal sich auf der Ahnentafel der Programmiersprachen verewigte, erschien jedoch „Beginner's All-purpose Symbolic Instruction Code“ (BASIC) gegen 1964. BASIC wurde als Einsteigerprogrammiersprache entwickelt, aber auch von Experten gerne genutzt.

Das nachfolgende BASIC ist noch nicht strukturiert aufgebaut, obwohl Algol diese Möglichkeit bereits 4 Jahre zuvor demonstrierte.

10 INPUT "Geben Sie bitte Ihren Namen ein"; A$
20 PRINT "Guten Tag "; A$
30 INPUT "Wieviele Sterne möchten Sie?"; S
40 FOR I = 1 TO S
50 S$ = S$ + "*"
55 NEXT I
60 PRINT S$
70 INPUT "Möchten Sie noch mehr Sterne?"; Q$
80 IF LEN(Q$) = 0 GOTO 70
90 L$ = LEFT$(Q$, 1)
100 IF (L$ = "J") OR (L$ = "j") THEN GOTO 30
110 PRINT "Auf Wiedersehen";

Spätere BASIC-Dialekte ermöglichen ebenfalls strukturierte Programmierung und ähneln mit Strukturen und teilweise sogar Unterstützung für objektorientierte Programmierung eher Pascal oder Object-Pascal.
Auffällig sind hier die Labels in Form von Zeilennummern, die an Fortran erinnern, sowie die Tatsache, dass der Vergleich auf Gleichheit und die Zuweisung nun in beiden Fällen mit dem Operator „=“ durchgeführt wird.
Basic unterscheidet Zahlen-Variablen (Buchstaben) und String-Variablen (Buchstaben mit nachfolgenden $-Symbol) und bietet ebenfalls eingebaute, grundlegende Funktionalität zur Stringverarbeitung.

Simula

Der Idee eine Programmiersprache zu entwickeln wurde häufig mit dem Hinweis auf Simula begegnet. Simula selbst erwachte 1964 zum Leben gewann allerdings als Sprache selber keine große Bedeutung. Als erste objektorientierte Programmiersprache, hat sie jedoch eine geschichtliche Bedeutung errungen und ist direkter Vorgänger von C++.
Die Syntax orientiert sich noch an Algol 60.

Pascal

Als direkte Weiterentwicklung von Algol erlaubt Pascal ebenfalls strukturierte Programmierung. Von Niklaus Wirth 1971 als Lehrsprache, konzipiert war zwar die Syntax einfach verständlich und sauber - allerdings unbrauchbar, um kommerziellen Erfolg zu haben. Die Bibliotheken waren schwach, Zeichenketten wurden nur über Umwege unterstützt. Ein Vorteil der Sprache war die Möglichkeit sie über einen One-Pass-Compiler übersetzen zu können, so dass sie auch den damaligen Rechnern verhältnismäßig schnell kompiliert wurde.
Erfolgreich wurde Pascal über Weiterentwicklung, z.B. mit TurboPascal von Borland, die Pascal stark erweiterte. Grade bei Schulen und Hochschulen war Pascal als ursprüngliche Lehrsprache erfolgreich, obwohl die Pascaldialekte nicht unbedingt kompatibel waren.

Der Versuch eine plattformunabhängige, assemblerähnliche Zwischensprache zu erzeugen wurde mit P-Code erstmals in Pascal umgesetzt. Diese Idee wurde allerdings erst mit Java erfolgreich umgesetzt.

BCPL, B, C, AWK

1963 trennt sich aus der Algol-Familie die Sprache CPL ab, die 1967 zu BCPL weiterentwickelt wurde. Um das Betriebsystem Unix zu entwickeln versuchte Ken Thomson die leicht portierbare Sprache BCPL auf eine PDP-7 zu portieren. Mit einigen selbst definierten Änderungen, entstand so die Sprache B, aus der Dennis Ritchie zusammen mit Ken Thomson zunächst „B mit Strukturen“ entwickelten, später umbenannt in C[vgl. 11]. Einfache B Programme lassen sich weiterhin mit C Compilern kompilieren.
Die Sprachbeschreibung von C wurde zuerst 1972 veröffentlicht. Brian W. Kerningham, seinerzeit vorrangig für die Dokumentation zuständig, schrieb zusammen mit Dennis Ritchie das erste Buch zu C, so dass diese erste Version als K&R-C (Kerningham und Ritchie C Dialekt) bekannt wurde. Seine Erfahrungen bei der Entwicklung mit C finden sich auch in der Programmiersprache AWK wieder, die er 1968 zusammen mit Alfred V. Aho und Peter J. Weinberger („AhoWeinbergerKerningham“ ⇒ AWK) entwarf. AWK verfügt ebenfalls über eine C ähnliche Syntax.

C besteht eigentlich aus zwei Programmiersprachen, dem CPP (C Prä-Prozessor), der zur Übersetzerzeit ausgeführt wird und der eigentlichen Sprache C, die kompiliert wird.
Durch die Verwandtschaft mit Algol ist C strukturiert. Durch die Nähe zur Maschine eignet sich C sehr gut zur Systemprogrammierung. Eine der größten Stärken von C ist die Freiheit, die es dem Programmierer lässt - was von vielen auch als die größte Schwäche angesehen wird.
Die Zuweisung wurde leider nicht von Algol übernommen, sondern von Fortran (‚=’-Operator). Allerdings existiert im Gegensatz zu BASIC eine Unterscheidung zum Vergleichsoperator (‚==’-Operator)

Dennoch wird C auch heute noch erfolgreich in Low-Level-Programmierung verwendet, da die meisten C-Compiler effizienteren Code und kleineren schreiben als C++ Compiler mit demselben C-Programm.

C mit Klassen, Cfront und C++

1980 begann Bjarne Stroustrup mit der Erfahrung, dass Simula für seine Simulationen als Programmiersprache zwar hervorragend geeignet war, allerdings leider viel zu langsam ist. Aus diesem Grund war er gezwungen sein Projekt auf C zu portieren, obwohl er objektorientierten Code hatte und C über keinen objektorientierten Aufbau verfügt. So entstanden zunächst mit Hilfe des C-Präprozessors Makros, um die aus Simula bekannte objektorientierte Programmierung zu übernehmen. Das Resultat nannte Stroustrup „C mit Klassen“.
Nachdem der Präprozessor nicht mehr ausreichte, schrieb Stroustrup den „Cfront“-Compiler, der „C mit Klassen“ kompilierte und herkömmlichen C-Quellcode produzierte. Cfront stellte ein zusätzliches Frontend für einen beliebigen C-Compiler dar, so dass „C mit Klassen“ auf beliebigen Architekturen über den dort installierten C Compiler kompilierbar war.
1983 erschienen die ersten Versionen von „C mit Klassen“ als Compiler, später C++ genannt, auch um hervorzuheben, dass „C mit Klassen“ keine Erweiterung zu C mehr darstellt, sondern eine neue Programmiersprache ist, die trotz der großen Ähnlichkeit semantische Unterschiede zu C besitzt.

Der wichtigste Schritt, den C++ Mitte der 80er Jahre leistete, war die Übertragung des objektorientierten Konzeptes in die beliebte, anerkannte und als einfach geltende Sprache C. Das Überladen von Funktionen und Operatoren, sowie virtuelle Funktionen innerhalb von Klassen gelten als die wichtigsten Neuerungen für eine populäre Programmiersprache.

Mehrfachvererbung, Namensräume, Ausnahmebehandlung und Templates entstanden erst in späteren Versionen. Templates sind bereits seit Ada (1979) bekannt, aber - wie die objektorientierte Programmierung - erst mit C++ einem größeren 'Publikum' bekannt geworden. Templates haben gute Chancen unter dem Begriff „generische Programmierung“ einen ähnlichen „Hype“ zu erfahren, wie es die objektorientierte Programmierung in den 1990ern erlebt hat. Jüngere Sprachen wie Java (als 'generic', ab Version 5) oder C# (ebenfalls als 'generic', für Version 2.0 geplant) tragen diesem kommend Trend Rechenschaft.

Java und C#

Java (1996, oder zuvor als Oak 1991) stammt von einer Vielzahl Sprachen ab, die Syntax wurde jedoch vornehmlich von C++ übernommen, darunter auch die Zuweisungs- und Vergleichsoperatoren. Java selbst schränkt die Freiheiten des Programmierers wieder deutlich ein. Darunter fällt auch, dass der Programmierer nach Möglichkeit nicht mit Zeigern in Berührung kommt.
Dennoch zeigt sich Java, vor allem durch die große Verfügbarkeit und einfache Verwendung von Java-Komponenten, sehr erfolgreich und bietet als erste erfolgreich JIT-kompilierende Sprache die Möglichkeit Reflection zu verwenden. Im Jahr 2000 zieht Microsoft mit einer ähnlichen Sprache nach, die - wie Java - auf Zeiger größenteils verzichtet, allerdings über diverse Schlüsselwörter Hintertüren anbietet, um doch mit Referenzen arbeiten zu können (z.B. Schlüsselwörter: „ref“, „unsafe“). Auch die .NET Sprache unterstützt wie Java die Verwendung von Komponenten direkt aus der Sprache heraus. Ebenfalls neu ist die Verwendung von Metadaten (Attribute, in eckigen Klammern vor dem zu beschreibenden Objekt angegeben), die z.B. Klassen mit Metadaten weitergehend beschreiben können und deren Informationen zur Laufzeit über Reflection verfügbar sind.