Einleitung

Geschichte und Einordnung der Programmiersprache C

C ist eine all purpose language, eine Programmiersprache für sehr viele verschiedene Anwendungsbereiche. Bereits in einer Statistik über mehrere Tausend Stellenangebote in Deutschland im Jahre 1990 hatte C die allseits sehr beliebte Programmiersprache COBOL überrundet, der Trend geht seitdem ungebrochen stark in Richtung C sowie der von Bjarne Stroustrup hervorgebrachten objektorientierten Erweiterung, C++.

C ist sehr eng mit dem Betriebssystem UNIX verbunden, für das die Sprache sogar eigens entwickelt worden ist. Der Unterricht wird demzufolge naturgemäß auf der Grundlage des Betriebssystems UNIX stattfinden.

Wichtige Ideen für C entstammen den beiden Programmiersprachen BCPL, die Martin Richards, und B, die Ken Thompson 1970 für das erste UNIX-System auf einer DEC-Anlage entwickelt hat. C ist eine typisierte Sprache, wobei die Datentypen jedoch bei weitem nicht so streng überwacht werden wie bei Pascal. Weiterhin finden sich die klassischen Datenstrukturen wie Felder, Zeiger, Strukturen (records) und Variante Strukturen (unions); C verfügt über die aus Pascal bekannten Kontrollstrukturen: Blockung (mit geschweiften Klammern { und }), Alternativen (if, if-else, switch) und Schleifen (while, for, do-while). Daneben gibt es Möglichkeiten des vorzeitigen Verlassens einer Kontrollstruktur (z.B. mit break). Funktionen können Ergebniswerte beliebiger Datentypen besitzen und auch rekursiv aufgerufen werden. (Viel Spaß.) Im Gegensatz zu Pascal können in C Funktionen jedoch nicht verschachtelt definiert werden. Variablen können lokal in einem Block, in einer Funktion, in einer Datei oder in einem gesamten Programm, das aus mehreren Quelltextdateien besteht, verfügbar sein. Lokale Variablen sind defaultmäßig automatic, was bedeutet, daß sie bei Betreten des Gültigkeitsbereiches neu angelegt und bei Verlassen desselben wieder zerstört werden.

C verwendet einen Preprocessor, der Makros im Quelltext ersetzt, andere Quelldateien (via #include) einfügt und bedingte Compilation erlaubt. Auch durch dieses Konzept kann C sehr leicht auf sich ändernde Verhältnisse, z.B. andere Systeme, angepaßt werden.

Der eigentliche Kern der Programmiersprache C ist sehr klein. Er umfaßt zum Beispiel noch nicht einmal Routinen für Terminal-Ein- und -Ausgabe! Die Leistungsfähigkeit erhält C durch die sogenannten Bibliotheken (Libraries), die jeweils konkret eingebunden werden müssen.

C ist einerseits sehr maschinennah, beispielsweise stehen Operationen zur bitweisen Verarbeitung zur Verfügung und über Zeiger sind Speicheradressen direkt ansprechbar. Andererseits ist C sehr portabel, wenn man sich nur an die allgemeinen Richtlinien von ANSI C hält. (1989 wurde der Standard vom amerikanischen Normungsinstitut ANSI verabschiedet.)

Durch das liberale Umgehen mit z.B. Datentypen und die vielfältigen Möglichkeiten, sehr kompakten (sprich: schwer lesbaren) Code zu schreiben, wird C oft nachgesagt, daß es extrem fehleranfällig sei, die Programme schwerer als babylonische Keilschriften zu entziffern seien und Hackermentalitäten begünstigt würden. Kernighan und Ritchie betonen daher, daß C auf der grundlegenden Annahme basiert, daß Programmierer wissen, was sie tun, und daß sie ihre Absichten eben nur explizit kundtun müßten.

Ein erstes Beispielprogramm

Das minimale, leere C-Programm sieht aus wie folgt.

main()
{
}

Trotzdem erkennt man schon einiges:

Das Hauptprogramm ist (nur) eine Funktion mit dem festgelegten Namen main.

Hinter dem Funktionsnamen folgt die formale Parameterliste, hier ist sie leer, daher stehen die runden Klammern einfach so einsam umher.

Der Anweisungsteil der Funktion (des Hauptprogramms) ist ebenfalls leer; zusammengehalten werden die Anweisungen durch das in erster Näherung dem Pascal-begin-end entsprechenden geschweiften Klammerpaar { und }.

Etwas interessanter wird dann schon das zweite Beispiel: das Standardprogramm "Hello, world!".

/* hello.c */
#include <stdio.h>
void main(void)
{
   printf("Hello, world!\n");
} /* end main */

Nun ist schon mehr zu erkennen:

In der ersten Zeile steht ein Kommentar. Kommentare, die über eine beliebige Strecke im Quelltext gehen können, beginnen mit den Zeichen "/*" und enden mit "*/".

Die nächste Direktive (#include) geht an den Preprocessor: er wird hier aufgefordert, die gesamten Deklarationen der sogenannten Headerdatei einzubinden. (Dateinamen von Headerfiles enden üblicherweise auf .h.)

Vor dem Funktionsnamen main ist nun das Wort void zu lesen. Dieser seltsame Datentypname steht nur dafür, daß diese Funktion nichts zurückliefern soll. Im Prinzip wurde damit einem Pascal-Programmierer gesagt, daß main hier wie eine Prozedur verwendet wird.

Ein kurzer, dennoch wichtiger Hinweis: Streng formal ist das "Hauptprogramm" (bzw. die Funktion main) deklariert als int main(), d.h. sie liefert einen int-Wert zurück. Sehr häufig wird dieser Rückgabewert jedoch ignoriert, und wir werden im folgenden im Sinne einer akademischen Klarheit void main(...) schreiben, wenn wir keinen Rückgabewert von main verwenden wollen. Auf manchen Compiler-Systemen, v.a. im Großrechnerbereich, führt die Compilation von void main(...) eventuell zu Fehlermeldungen, die durch die Deklaration int main() und ein Einfügen von return 0; vermieden werden können.

Innerhalb der formalen Parameterliste steht nun nicht mehr nichts, sondern das Wörtchen void um anzudeuten, daß da nichts stehen soll. So macht das Spaß.
Auch wenn das paradox anmutet: so signalisiert ein Programmierer, daß die Funktion (hier: main) tatsächlich keine Parameter erhalten soll.

Der Funktionsname printf ist das write von C. Wie oben bereits gesagt wurde, ist die Funktion printf nicht im eigentlichen C enthalten, sondern eine Bibliotheksfunktion. Über die obige Direktive #include <stdio.h> wurde gesagt, daß die Standard-Ein- und -Ausgabe-Routinen (standard input output) mit eingebunden werden sollen.

Die Funktion printf hat hier einen Parameter: eine Zeichenkette, die in doppelten Hochkommata notiert wird. Darin steht zum einen der Klartext "Hello, world!", zum anderen folgt danach jedoch noch etwas Besonderes: "\n" ist eines von mehreren zur Verfügung stehenden Ersatzsymbolen, "\n" steht speziell für einen Zeilenvorschub.

Das Semikolon hinter dem Aufruf von printf muß übrigens sein, anders als bei entsprechenden Situationen in Pascal. Das Semikolon in C schließt (u.a.) eine Anweisung ab! Nachstehend noch der Vergleich zum funktionsäquivalenten Pascal-Programm.

{ hello.p bzw. hello.pas }
program HelloWorld(output);
begin
writeln('Hello, world!')
end.

Reservierte Worte (Schlüsselwörter)

Die Programmiersprache ANSI C kennt eine Reihe reservierter Worte (Schlüsselwörter), die in den nächsten Abschnitten sortiert aufgelistet werden sollen.

Befehlsschlüsselwörter

Die folgenden Schlüsselwörter stehen für Befehle der Sprache C.

break case continue default do else (entry)

for (goto) if return sizeof switch while


Anmerkungen:

Das Schlüsselwort "entry" ist ein Überbleibsel aus den Pioniertagen von C; es ist in ANSI C nicht inhaltlich implementiert.

Das Schlüsselwort "goto" existiert leider auch noch; mit ihm werden die im Bereich der strukturierten Programmierung als unmoralisch gebrandmarkten Sprünge gekennzeichnet.

Das Schlüsselwort "sizeof" steht streng genommen nicht für einen Befehl, sondern für einen Operator. Dieser liefert die Größe (in Bytes) einer Variablen oder eines Datentyps zurück.

Schlüsselwörter für Speicherklassen

Auf Speicherklassen wird in einem späteren Kapitel eingegangen. Hier nur bereits übersichtsartig die dazugehördenen Schlüsselwörter.

auto kennzeichnet eine Variable, deren Allokation beim Eintritt in den Gültigkeitsbereich und Deallokation beim Austritt daraus erfolgt;

extern sind globale Variablen, die für die gesamte Programmlaufzeit allokiert werden;

register entspricht auto, nach Möglichkeit wird jedoch statt eines Speicherplatzes ein Register belegt; bei modernen Compilern ist dieses Schlüsselwort relativ obsolet, da bei der Codegenerierung in der Regel sowieso optimiert wird.

static kennzeichnet Variablen, deren Allokation für die gesamte Programmlaufzeit erfolgt, deren Gültigkeit jedoch block- oder modullokal festgelegt ist.

Schlüsselwörter für Datentypen

Für verschiedene vordefinierte Datentypen stellt C reservierte Worte bereit.

char: Zeichen (1 Byte)

double: Gleitkommazahl, doppelte Genauigkeit

enum: Kennzeichnung für Aufzählungstyp

float: Gleitkommazahl

int: Ganzzahl

long (oder long int): Ganzzahldatentyp

unsigned: Kennzeichnung zur Interpretation eines Datentyps ("ohne Vorzeichen"), z.B. "unsigned int" oder "unsigned char"

short: Ganzzahl

signed Kennzeichnung zur Interpretation eines Datentyps als vorzeichenbehaftet, z.B. signed int (=int) oder signed char

struct: Struktur (=Record)

typedef: Eigendefinition von Datentypen

union: Variante Struktur (=varianter Record)

void: "leerer" Datentyp

Weitere Schlüsselwörter

Daneben gibt es noch zwei weitere Schlüsselwörter:

const: kennzeichnet einen Datentyp für einen nicht veränderbaren Speicherplatz.

volatile: Typattribut für eine Variable, die durch externe Einflüsse (von außerhalb des Programmes) verändert werden kann, beispielsweise die Systemuhr.

Grundlegender Programmaufbau

Ein C-Programm besteht generell aus den folgenden Komponenten:

Den Preprocessor-#include-Anweisungen, mit denen externe Dateien (z.B. die bereits erwähnten Headerfiles) in den Quelltext einbezogen und Schnittstellen zu den Bibliotheksroutinen der C Library geschaffen werden.

Den Preprocessor-#define-Anweisungen, mit denen symbolische Konstante und Makros definiert werden können.
(Beispiel: #define MAXIMUM 100 )

Datentypen und Variablen, die vor dem Hauptprogramm deklariert werden; hier werden globale bzw. externe Variablen und globale Datentypen festgelegt, die allen Teilen des C-Programms zugänglich sein können.

Das Hauptprogramm main (genau genommen: die Funktion main()): hier findet der Programmeinstieg statt. main() darf und muß in jedem C-Programm genau einmal vorkommen; häufig steht es als erste Funktion in dem Quelltext, sofern das Programm nicht sowieso mehrere Quelltextdateien umfaßt, bei denen dann main() oft eine eigene Datei (z.B. main.c) spendiert bekommt.

Es folgen dann ggf. weitere Funktionen.

Dabei ist es im Prinzip gleichgültig, ob main() die erste oder die letzte Funktion oder irgendeine zwischen den anderen Funktionen ist; es ist lediglich guter Stil, main() entweder stets am Anfang oder stets am Ende eines Ein-Datei-Programmes zu plazieren.

Kurzer Vergleich zwischen Pascal und C

Nachfolgend sollen übersichtsartig (für einen ersten Eindruck und zum späteren Nachschlagen) einige Dinge von Pascal und C gegenüber gestellt werden. (Diese Aufstellung wurde dem Buch von Müldner und Steele entlehnt.) Auf die einzelnen Details wird später in diesem Script bzw. im Unterricht näher eingegangen.

          ANSI-C                                              Pascal             
/* ... */                    Kommentare             { ... }                      
short, int, long char        Datentypen             integer char real            
float, double                                                                    
#define PI 3.14 const        Konstanten             const PI=3.14;               
float PI=3.14;                                                                   
int i;                       Variablen              var i: integer;              
void main(void) {  }         Hauptprogramm          program                         
                                                    xy(input,output);
begin      
                                                    end.                         
+ - * / + -                  Grundrechenarten       + - * / + -                  
                             Vorzeichen                                          
/   %                        Ganzzahldivision       div,  mod                    
scanf("%d",&i);              Eingabe Ausgabe        read(i) write(i)             
printf("%d",i);                                                                  
< <= > >= == !=              Vergleichsoperatoren   < <= > >= = <>               
&&   ||   !                  logische Operatoren    and,  or,  not               
{    ... }                   Verbundanweisung       begin ... end                
                             (Block)                                             
if (x<1)    y=2; else        if-Konstrukt           if x<1 then    y:=2 else     
y=3;                         (Alternative)          y:=3;                        
while (x<100)    x += 1;     kopfgesteuerte         while x < 100 do    x := x   
                             Schleife               + 1                          
do {    ... } while          fußgesteuerte          repeat    ... until x>=100   
(x<100);                     Schleife                                            
for (i=1; i<=100; i++)       for-Schleife           for i:=1 to 100 do    ...    
...                          (entsprechen sich                                   
                             nicht ganz)                                         
switch(i) {   case 1:        Mehrfachauswahl        case i of    1:              
printf("eins");     break;                          write('eins');    2:         
case 2:                                             write('zwei');               
printf("zwei");     break;                          otherwise     { oder else    
default:  ... }                                     } ... end                    
char a[100]; char b[6][11];  Felder                 var a: array[0..99]          
                                                    of char; var b:              
                                                    array[0..5,0..10]            
                                                    of char;                     
struct {    float x, y; }    Strukturen             var r: record           x,   
r;                                                  y: real;        end;         
typedef enum {    ROT,       Aufzählungstyp         type farbe=                  
GRUEN, BLAU } farbe;                                (rot,gruen,blau);