Das Typkonzept in C


Was ist ein Datentyp?

Die bisher begonnene Diskussion kann mit Hilfe des Typkonzeptes vertieft werden. Ein Datentyp ist ein Denkmodell, das festlegt, wie Daten beschaffen sein sollen und was man damit tun kann. Der Datentyp “int” legt in ANSI-C fest, daß es sich um ganze Zahlen mit Vorzeichen handelt. Die Größe entspricht zumeist der Größe der Register der verwendeten CPU. Die erlaubten Operationen sind u.a. die Grundrechenarten oder die Boole’schen Verknüpfungen. Wichtig ist, daß der Datentyp beides festlegt, die Größe der Daten und die erlaubten Operationen. Unabhängig von der tatsächlichen Größe einer int-Variablen sind die damit erlaubten Operationen für alle Maschinen gleich.

Mit Hilfe des Datentyps führt der Compiler auch die Syntaxprüfungen aus. Denken Sie nur an die Prüfung der Parameter beim Aufruf eines Unterprogramms.

Ein einfaches Beispiel für eine Typprüfung sehen wir, wenn wir den modulo-Operator (Rest der Ganzzahl-Division) auf Fließkomma-Variablen anwenden. Natürlich muß der Compiler erkennen, daß bei Fließkomma-Zahlen ein Rest unsinnig ist. Die Überprüfung zeigt, daß Daten und Operationen nicht zusammenpassen. Dies führt zur Fehlermeldung. Der Compiler erkennt den Fehler bei der Übersetzung (siehe Bild 2-1 / mit Borland Turbo-C++ Compiler)

Datentyparten

Es gibt zwei unterschiedliche Arten von Datentypen: vordefinierte und private. In der Sprache sind die vordefinierten Datentypen enthalten. Dies sind die Grundbausteine zur Beschreibung von Daten. Sie können durch Attribute näher beschrieben werden.



Das Typkonzept in C    Seite 19

ctypfehler Die Attribute legen die Größe (, long) und die Behandlung des Vorzeichens fest (signed, unsigned). Weiter gibt es Attribute, die die Speicherklasse festlegen (static, register, auto). Und letztlich dienen Attribute auch dazu, dem Compiler Hinweise über spezielle Behandlungen zu geben (extern, const, volatile).

Die andere Art der Datentypen dient dazu, allgemeine Modelle zu beschreiben. Die privaten Datentypen werden vom Programmierer selbst entwickelt und definiert. In C gibt es dazu die Struktur als Hilfsmittel. Bei der Definition einer Struktur wird ein der Sprache bisher unbekanntes Datenmodell entwickelt. Das Gesamtmodell setzt sich dabei aus den “Atomen” der vordefinierten Datentypen zusammen. Neu und einzigartig ist die Zusammenstellung.


Seite 20    Einsteigerseminar C++

Definition privater Typen in C

Strukturen definieren einen Namen für eine bestimmte Zusammenstellung von Daten. Die Struktur beschreibt ein Modell, das komplexer ist, als es die Grunddatentypen zulassen.

Eine Struktur kann man als Modell auffassen, das eine Sache oder eine Person der Wirklichkeit beschreibt. Das Modell umfaßt immer nur einen Ausschnitt. Nur das, was für die Bearbeitung einer Aufgabe wichtig ist, wird in das Modell aufgenommen.


Die Struktur reserviert keinen Speicherplatz. Sie wirkt wie eine Mustervorlage. Erst wenn später damit eine Variable angelegt wird, wird auch Speicherplatz vergeben. Zuerst einmal ist die Strukturdefinition nur eine Information für den Compiler.

Rahmen3

Ein Element der Struktur darf nicht vom gleichen Struktur-Typ sein. Sonst würde versucht, eine Struktur rekursiv zu definieren. Zeiger auf Variable der gleichen Struktur sind erlaubt.



Das Typkonzept in C    Seite 21

Definitionen und Deklarationen

In C und C++ unterscheidet man deutlich zwischen Definitionen und Deklarationen. Eine Definition liegt vor, wenn dem Compiler die Bedeutung eines Namens mitgeteilt wird. In unserem Falle definieren wir eine Struktur. Eine Deklaration ist dagegen die Bekanntgabe eines anderswo definierten Namens für die momentane Übersetzung. Die Datenelemente in unserer Struktur gibt es noch gar nicht. Erst wenn es einmal eine strukturierte Variable geben wird, dann wird es auch Datenelemente geben. Die Elemente der Struktur werden somit deklariert.

Bei Funktionen wird die Schnittstelle in der Informationsdatei (header) deklariert. Schreibt man dagegen den Programmcode, definiert man die Funktion.

Typdefinition mit Strukturen

In C ist der Strukturname allein noch keine Typdefinition. Entweder man benutzt immer die Kombination aus dem Schlüsselwort “struct” und dem selbst vergebenen Strukturnamen oder man benutzt die Anweisung “typedef”. Mit “typedef” geben wir den neuen Typ dem Compiler bekannt. Den können wir genauso verwenden wie “int” oder “float”.

Rahmen5

Auch diese Anweisung reserviert noch keinen Speicherplatz. Sie sagt dem Compiler, daß ab jetzt “artikel” ein Datentyp ist und so benutzt werden kann wie ein vordefinierter Datentyp.

Die “typedef”-Anweisung wirkt wie eine Abkürzungsvereinbarung. Anstelle von “struct Artikel” kann man nun auch “artikel” verwenden.


Seite 22    Einsteigerseminar C++

Definition von strukturierten Variablen

Mit der Struktur hat der Programmierer beschrieben, wie eine strukturierte Variable aussehen müßte, die das gewünschte Modell nachbildet. Da es sich um einen Datentyp handelt, können Variable nach den gleichen Spielregeln angelegt werden, wie einfache Variable auch.

Rahmen7

Operationen mit strukturierten Variablen

Es gibt zwei unterschiedliche Arten, mit strukturierten Variablen zu arbeiten. Entweder wir betrachten die ganze Variable, das ganze Modell, oder wir greifen auf die einzelnen Datenelemente zu. Zur Bearbeitung ganzer Strukturen kennt C nur eine vordefinierte Operation, die Zuweisung. Alle anderen Operationen müssen vom Programmierer selbst in der Form von Funktionen geschrieben werden. Diese Funktionen nennen wir Operator- Funktionen.

Datenelemente, die mit Hilfe der C- Grundtypen deklariert wurden, können auch mit den normalen C- Operationen bearbeitet werden. Für den Zugriff auf die Elemente kennt C zwei Operatoren: den Punkt und den Pfeil (Verweis).

Der Punkt wird benutzt, wenn der Name der strukturierten Variablen bekannt ist. Mit Hilfe des Verweisoperators (“->”) kann mit der Adresse einer Struktur zugegriffen werden. Der Name der strukturierten Variablen muß dabei nicht bekannt sein.


Das Typkonzept in C    Seite 23

Rahmen9
Ist ein Datenelement wieder eine Struktur, so kann ein Zugriff mit einer Kette aus Strukturnamen und Operatoren (Punkt/Verweis) geschehen.

Rahmen11


Seite 24    Einsteigerseminar C++

Operatorfunktionen

Da die Zuweisung die einzige vordefinierte Operation ist, müssen wir alle anderen nachbilden. Dies geschieht mit Operatorfunktionen. Für die Verwendung als Operatorersatz ist ein bestimmter Aufbau sinnvoll.

Rahmen13

Der erste Parameter einer Operatorfunktion, in unserem Beispiel der einzige, ist zumeist ein Zeiger auf eine strukturierte Variable. Wir benutzen diese Parameterübergabe, damit die Funktion für alle möglichen Variable anzuwenden ist, die mit Hilfe der zugehörigen Struktur angelegt wurden. Und wir benutzen einen Zeiger, da Adressen zumeist wesentlich schneller an ein Unterprogramm zu übergeben sind, als ganze strukturierte Variable.

Der Programmierer, der die Struktur definiert hat, müßte im voraus alle zulässigen Operationen mit Hilfe der Operatorfunktionen bereitstellen. Da der Programmierer, der Strukturen und die zugelassenen Funktionen schreibt, zumeist nicht derselbe ist, der sie auch anwendet, wollen wir zwischen dem Spezialisten für eine Sache oder eine Strukturdefinition auf der einen Seite und dem Anwender auf der anderen unterscheiden.

Ein Beispiel finden Sie in der Datei "stdio.h”. Der Hersteller des Compilers hat in dieser Informationsdatei (“header file”) die Struktur “FILE” definiert. Mit Hilfe der Struktur wurden dann eine ganze Reihe von Funktionen geschrieben. Dazu gehören “fopen()”, “fclose()”, “fread()”, “fwrite()” oder “fprintf()”. Diese Funktionen stehen Ihnen in der Standardbibliothek zur Verfügung. Mit Ausnahme von “fopen()” erhält jede Funktion als Parameter u.a. einen Zeiger auf eine strukturierte Variable des Typs “FILE”. Der Wert für den Zeiger wird von “fopen()” geliefert. Leider konnte man sich nicht über die Position in der Parameterliste einigen. So findet sich der Zeiger bei “fprintf()” an erster Stelle, bei “fread()” und “fwrite()” an letzter.


Das Typkonzept in C    Seite 25

Informationsdateien

Im obigen Beispiel wurde eine typische Arbeitstechnik benutzt. Die benötigten Informationen über die Typdefinition und die zugehörigen Funktionen konnten wir einer Informationsdatei entnehmen. Der Spezialist faßt in einer Informationsdatei die Strukturdefinition, die Deklarationen der Operatorfunktionen und etwaige Konstante zusammen. Im Englischen heißen diese Dateien “header files” (Vorspann Dateien).

Der Anwender kann diese Informationsdatei mit Hilfe der Präprozessoranweisung “#include” während der Übersetzung seiner eigenen Datei mit einlesen. Der Präprozessor ist ein Programm, das dem eigentlichen Compiler vorgeschaltet ist. Er liest andere Dateien ein, kann bedingt übersetzen und symbolische Konstante definieren. Schauen wir uns zuerst den Aufbau einer Informationsdatei an (Bild 2-8).

Ein Kommentar gibt Auskunft über den Zweck und die Version der Datei. Bei kommerziellen Programmen findet sich noch ein Vermerk über die Rechte des Autors. Die Datei wird mit Hilfe einer Präprozessoranweisung nur bedingt bearbeitet. Ein mehrfaches Einlesen wird damit verhindert. Die Abfrage “#ifndef” (if not defined / falls nicht definiert) sucht nach dem Namen “RATIOHEADER”. Wird er nicht gefunden, wird der Rest der Datei bearbeitet. Wurde die Datei während einer Übersetzung bereits einmal eingelesen, dann ist der Name bekannt und der Präprozessor sucht nach dem Ende der Bedingung bei “#endif”. Ein mehrfaches Einlesen kann bei verschachtelten “#include”- Anweisungen auftreten.

Danach kommt die Definition der Struktur. Zumeist folgt der Strukturdefinition die Definition der Abkürzung mit “typedef”. Ab jetzt kann der mit “typedef” neu vergebene Name bei der Definition von Variablen genauso verwendet werden wie ein vordefinierter Datentyp.


Seite 26    Einsteigerseminar C++

Rahmen15

Die restlichen Zeilen sind Deklarationen von Operatorfunktionen. Zwar kann man bei der Deklaration auf die Namen der Parameter verzichten. Der Compiler benötigt zum Prüfen nur die Anzahl und die Reihenfolge der Typen. Aber man sollte gerade hier lieber aussagekräftige Namen verwenden. Schließlich benötigt ein Anwender die Informationsdatei. Wenn die Namen der Parameter ihre Funktion beschreiben, ist das eine gute Grundlage für die Dokumentation.

Der Spezialist muß noch die Implementierungsdatei schreiben. Hier stehen die Definitionen der Operatorfunktionen. In der Informationsdatei hat der Spezialist sozusagen mit den Deklarationen ein Versprechen abgegeben, welche Operationen er unterstützt. In der Testphase kommt es aber häufig vor, daß mehr Funktionen deklariert werden, als bereits geschrieben wurden. Dies ist zulässig, solange die Funktionen noch nicht von einem Testprogramm aufgerufen werden.


Das Typkonzept in C    Seite 27

Interessant ist in unserem Beispiel die Rückgabe einer ganzen Struktur als Wert (und nicht mit einem Zeiger). In ANSI-C können Strukturen übergeben werden.


Rahmen17

Und schließlich schreibt der Anwender noch das Hauptprogramm. Anwender und Spezialist können natürlich ein und dieselbe Person sein. Die Bezeichnungen sollen nur die Rolle wiederspiegeln, in der sich der Programmierer in Bezug auf das Beispiel befindet.

Im Anwenderprogramm (im Bild 2-10), das hier der Einfachheit halber ein Hauptprogramm ist, holt der Programmierer sich die Informationsdatei mit Hilfe einer “#include”- Anweisung. Weiter vergibt er Speicherplatz für Variable. Hier werden die Variable A,B und C definiert. Interessant sind die ersten Zeilen des Hauptprogramms, in denen die Anfangswerte für die strukturierten Variablen gesetzt werden. Dies ist notwendig, da der Spezialist keine Funktion vorgesehen hat, die Anfangswerte setzt oder Zuweisungen ermöglicht.


Seite 28    Einsteigerseminar C++

Rahmen19

Beachten Sie auch, daß die Funktion “main()” mit einem Rückgabewert definiert wird. Damit wird der Programmstatus zurückgegeben. In unserem Fall melden wir mit einer “0" den Erfolg. Jeder Wert ungleich ”0" wird als Fehler interpretiert. Die Rückgabe ist für Batch-Dateien notwendig. In DOS findet sich der Rückgabewert in der Batch-Variablen ERRORLEVEL, unter Linux/Unix in der Umgebungsvariablen “?”.


Das Typkonzept in C    Seite 29

Übersetzen und Linken

Im Beispiel verwendeten wir zwei Dateien, die übersetzt und gebunden werden mußten. Binden oder linken bedeutet hier die Bearbeitung von Objektdateien mit einem Linker. Um später Mißverständnisse zu vermeiden, soll hier der englische Begriff “Linker” verwendet werden.

Der Aufruf des Linkers mit den verschiedenen Bibliotheken, die zu durchsuchen sind, und den Objektdateien, die immer einzubinden sind, kann mühsam sein. Je nach Programmierumgebung steht eine graphische Projektverwaltung oder “make” zur Verfügung. In UNIX und vielen anderen Umgebungen wird das textbasierte “make”-Programm benutzt. Dieses Programm benötigt eine Textdatei, deren Name oft “Makefile” ist. Darin beschreibt der Programmierer die Abhängigkeiten der Dateien eines größeren Programms. Daneben gibt er an, wie übersetzt oder gelinkt werden muß. Das “make”- Programm liest nun diese Datei, ermittelt die notwendigen Programmaufrufe und führt sie aus.

Im Beispiel (im Bild 2-11) sehen Sie eine Steuerdatei (Makefile) für Linux/Unix.

Rahmen21


Seite 30    Einsteigerseminar C++

Eine Steuerdatei kennt drei unterschiedliche Arten von Zeilen oder Einträgen. Ein Kommentar beginnt mit einem “#”. Die Zeile oder der Rest der Zeile wird ignoriert. Die nächste Zeilenart beginnt immer in der ersten Spalte. Dies beschreibt eine Abhängigkeit. Im Beispiel finden Sie derartige Einträge u.a. in den Zeilen 6,8 und 10. Das Ziel “p27" hängt von zwei Objektdateien ab. Die dritte Art findet sich z.B. in den Zeilen 7,9 und 11. Was ist zu tun, wenn ”make" feststellt, daß die Ausgangsdatei verändert wurde? Diese Aktion oder diese Aktionen werden durch eingerückte Kommandozeilen beschrieben. In Zeile 7 wird der GNU-Compiler gerufen. Er erkennt am “-o”-Befehl, daß er linken muß. Der Parameter “-c” in den Zeilen 9 und 11 gibt an, daß nur übersetzt werden muß.

Für viele “make”-Programme gilt, daß die erste Abhängigkeit das Endergebnis beschreibt. Weiter kann man einzelne Marken angeben, um ein bestimmtes Programm zu erzeugen, wenn, wie im Beispiel, in einem “Makefile” mehrere Programme verwaltet werden. Der Aufruf ist dann z.B.: “make p27".

Projekte bei IDE's (Integrierte Entwicklungsumgebung)

Entwicklungsumgebungen bieten oft eine graphische Oberfläche, von der alle Tools erreicht werden können (IDE - integrated development environment). Die Projektverwaltung ist hier integriert. Der Entwickler kann über Menüs Dateien einem Projekt hinzufügen oder davon entfernen. Die Grundidee bleibt jedoch die gleiche wie beim Programm “make” mit seiner Textdatei. Man stellt neben den eigentlichen Programmdateien eine Projektdatei bereit, die die Abhängigkeiten der Dateien untereinander beschreibt und eine automatische Erstellung des Gesamtprogramms mit minimalem Aufwand ermöglicht. Wieder werden nur die Dateien neu übersetzt oder gelinkt, deren abhängige Dateien geändert wurden. Die verwendeten “#include”-Dateien werden üblicherweise dabei automatisch erkannt.

Die Überprüfung ist in beiden Fällen eine Frage des Zeitstempels der Dateien. Ist die Zieldatei jünger als die Datei, von der sie abhängt, dann muß übersetzt werden.


Das Typkonzept in C    Seite 31

projekt1 projekt2

Seite 32    Einsteigerseminar C++

Im Gegensatz zu einem “makefile”, wie die Beschreibungsdatei der Abhängigkeiten standardmäßig heißt, kann man mit einer Projektdatei nur eine Übersetzung steuern. “make” kann völlig universell eingesetzt werden. Beim Bücherschreiben hilft eine “makefile” z. B. die Ausdrucke und Beispiele zu formatieren, wenn sich eine Quelle verändert hat.

Probleme und Gefahren bei C

Das vorgestellte Arbeitsverfahren mit Spezialisten und Anwendern hat sich in der Praxis durchgesetzt. Nur leider erfüllt es eine ganz entscheidende Forderung nicht: der Compiler kann den privaten Typ nicht vollständig überprüfen. In werden zwar die Schnittstellen korrekt geprüft, aber niemand verhindert, daß es Seiteneffekte durch unerlaubte Manipulationen geben kann. Ganz bewusst wurde im Hauptprogramm in die Strukturen hinein Werte geschrieben. Und das obwohl es ausgemacht war, daß der Spezialist alle Bearbeitungsfunktionen für die strukturierten Variable schreibt.

  

Rahmen27

Vielleicht kann das Problem an Hand der erwähnten Dateiverwaltungsstruktur “FILE” näher untersucht werden. Dazu noch ein (katastrophales) Beispiel: Ein Programmierer liest in der Datei “stdio.h” nach und findet den Aufbau des Dateisteuerblockes “FILE”. In dem Moment, wo er eine Datei anlegt, erhält er einen Zeiger mit der Adresse des benutzten Steuerblockes von “fopen()” geliefert. Dieser Block darf nur von den Standardfunktionen benutzt werden. Trotzdem kann er mir Hilfe des Zeigers und der Typinformation aus “stdio.h” zugreifen und jederzeit für ein vollendetes Chaos in der Dateiverwaltung sorgen.


Das Typkonzept in C    Seite 33

Was hier als Fehler einleuchtet, kann bei unbeabsichtigten Fehlern zu schweren Problemen führen. Seiteneffekte und sporadische Fehler, die von ganz bestimmten Konstellationen abhängen, sind bekanntlich extrem schwer zu finden.

Hinweise zur Weiterarbeit

1) Jeder Compiler hat einen Satz von "header".Dateien. Suchen in der "stdio.h" Die Strukturdefinition für "FILE" und einige Operatorfunktionen, die als Parameter einen Zeiger auf "FILE" erwarten.

2) Welche weiteren Strukturen werden in den "header"-Dateien definiert? So verwendet z.B. time.h Strukturen. Suchen Sie zwei Strukturen und die zugehörigen Operatorfunktionen.

3) Erstellen Sie mit Strukturen Modelle von wirklichen Dingen oder Personen. Wie könnte eine Struktur aussehen, die die Blumen einer Gärtnerei oder einen Autor beschreibet? Was wären passende Operatorfunktionen dazu? In welchen Programmen könnten die Operatorfunktionen sinnvoll verwendet werden?

4) Erstellen Sie für einen Gegenstand mehrere Strukturen zur Beschreibung aus verschiedenen Sichtweisen. Eine einfache Plastikflasche z.B.: wie wird sie aus der Sicht des Produzenten, des Verpackers, des Recycling-Unternehmens aussehen?

Im nächsten Kapitel

Diese Sicherheitslücke, die durch mangelnde Prüfung entsteht, werden wir mit C++ elegant schließen. Mit dieser kurzen Einführung in die Arbeitsweise mit Strukturen in C wollen wir C beenden und uns den geschützten Kollegen der Strukturen, den Klassen in C++, zuwenden.



Seite 34    Einsteigerseminar C++