Prozedurale Programmierung - Kapitel 8 - Getrennte Übersetzung
Inhalte von Dr. E. Nadobnyh
Der Quellcode eines Programms befindet sich oft in vielen Dateien.
Die Zerlegung in mehrere Dateien hat den selben Zweck wie die Zerlegung in mehrere Funktionen, z.B.:
1) Reduzierung der Komplexität eines Problems,
2) unabhängige Programmierung von Teilen,
3) Wiederverwendung des Codes,
4) Lesbarkeit des Codes,
5) Verbergen der Details von Anwendern.
Es gibt verschiedene Möglichkeiten mehrere Dateien zu einem Programm zusammenzufassen:
1) Präprozessor kann die Quellcode-Dateien binden;
2) Linker kann die übersetzten Dateien binden.
Übersetzungseinheit
Bei der Verarbeitung eines C/C++-Programms werden zunächst die Präprozessoranweisungen übersetzt, z.B. #include-Anweisungen. Das Ergebnis ist dann eine so genannte Übersetzungseinheit (translation unit, Modul).
Ein Compiler übersetzt jede Übersetzungseinheit, die zu einem Programm gehört, unabhängig von allen anderen. Dies wird als die getrennte Übersetzung (separate Compilierung) bezeichnet.
Aus jeder Übersetzungseinheit erzeugt der Compiler eine so genannte Object-Datei.
Aus mehreren Object-Dateien erzeugt der Linker das ausführbare Programm.
Informationen über die Modulen sind in einer Projektdatei zusammengestellt.
Gültigkeitsbereiche
Mit den Übersetzungseinheiten wird ein globaler Gültigkeitsbereich des Programms auf kleinere Bereiche ufgeteilt.
Ein Gültigkeitsbereich (scope, Verfügbarkeitsbereich, Geltungsbereich) ist ein Bereich, in dem auf einen Namen (z.B. Funktion, Variable, Feld) zugegriffen werden kann.
In C werden 4 Bereiche unterschieden :
1) Block,
2) Funktion,
3) Übersetzungseinheit (Modul, Datei),
4) gesamtes Programm (globaler Gültigkeitsbereich).
C++ hat weitere Bereich-Arten. Ein wichtiger Gültigkeitsbereich ist eine Klasse.
Sichtbarkeit
Gültigkeitsbereiche bestimmen eine Sichtbarkeit (Gültigkeit) einer Variable oder Funktion. Es wird zwischen lokaler, modulglobaler und programmglobaler Sichtbarkeit unterschieden.
1) Variablen und Felder sind lokal, wenn sie in einem Block oder in einer Funktion definiert werden.
2) Variablen, Felder und Funktionen sind modulglobal, wenn sie in einem Modul (in einer Übersetzungseinheit) und außerhalb jeder Funktion definiert werden.
3) Variablen, Felder und Funktionen sind programmglobal, wenn sie in einem Modul definiert werden und in anderen Modulen deklariert werden.
Programmglobale Namen werden durch den Linker unterstützt.
Definition und Deklaration
1. Mittels eines Namens können Variablen, Feldnamen, Funktionen, Datentypen und Objekte bezeichnet werden. In C/C++ müssen Namen vor der Verwendung definiert werden.
Eine Definition führt einen Namen ein und legt den Speicherplatz für die Daten oder den Code an. In einem Programm kann nur eine einzige Definition für einen Namen gegeben werden.
2. Soll ein Name vor seiner Definition verwendet werden, muss er zumindest deklariert sein.
Eine Deklaration führt einen Namen ein und gibt dem Namen eine Bedeutung. In einem Programm können mehrere Deklarationen für einen Namen gleichzeitig existieren.
extern
Das Schlüsselwort extern wird bei den Deklarationen verwendet.
1) Variablendeklaration:
Syntax: extern typ name;
Beispiel: extern int a;
extern int b[5];
Besonderheit: Globale Variablen haben automatisch die Speicherklasse extern. Deswegen können sie mit extern auch definiert werden.
Beispiel: extern int a=5;
2) Funktionsdeklaration (Prototyp):
Syntax: extern resulttype name (Parameterliste);
resulttype name (Parameterliste);
Beispiel: extern int sum(int n);
int sum(int n);
Beispiel
Legende:
Diese Verbindungen werden durch den Linker hergestellt.
⇒ Demo 1.
Überblick
Die folgende Tabelle stellt Definitionen und Deklarationen syntaktisch gegenüber:
Funktion | Variable | |
Definition | void f1(int x){...} | int a = 3; |
Deklaration | void f1(int x); //Prototyp | extern int a; |
Verwendung | f1(4); //Aufruf | a=5; //Zugriff |
8.2. Header-Dateien
1)Deklarationen von Funktionen, Variablen, Feldern müssen in allen Modulen gleich lauten.
2)Deklarationen müssen mit den Definitionen übereinstimmen.
3)Um diese Gleichheit sicherzustellen, werden alle solche Deklarationen üblicherweise in so genannten Header-Dateien zusammengefasst.
a) Eine Header-Datei hat die Namensendung (Dateinamenserweiterung) „h“, z.B.: xxxx.h
b) Die Deklarationen von Header-Dateien werden dann durch eine #include-Anweisung in die Quelltextdateien einkopiert.
4) Definitionen von benutzerdefinierten Datentypen, z.B. Strukturen, müssen in allen Modulen auch gleich lauten. Deswegen werden solche Definitionen auch in Header-Dateien zusammengefasst.
Inhalte der Header-Dateien
Üblicherweise nimmt man in eine Header-Datei folgende Deklarationen auf.
Deklaration | Beispiel |
Variablendeklarationen | extern int i; |
Funktionsprototypen | int next(int x); |
Konstanten | const int max=100; |
Include-Anweisungen | #include <stdio.h> |
Makro-Definitionen | #define MAX 100 |
Kommentare | // no comment |
Außerdem nimmt man auch Deklarationen mit typedef, Klassendefinitionen, Aufzählungstypen, bedingte Kompilation u.ä. auf.
Dagegen nimmt man Definitionen von Variablen und Funktionen nicht in eine Header-Datei auf.
Typische Modulstruktur
Ein Modul (Übersetzungseinheit) wird normalerweise als zwei Dateien hergestellt:
1) Eine Header-Datei bildet eine Schnittstelle des Moduls. Sie enthält Deklarationen von Funktionen, Deklarationen von Daten und ihre ausführliche Benutzeranleitung als Kommentar.
2) Eine Implementierungsdatei (Quelltextdatei) enthält Definitionen von entsprechenden Funktionen und Daten.
Anstelle einer Quelltextdatei kann man aber auch eine Objekt-Datei oder eine Bibliothek in ein Projekt aufnehmen.
Header-Datei (Schnittstelle, Interface) |
Quelltextdatei oder Objekt-Datei (Implementierung, Rumpf) |
Beispiel
Eine Übersetzungseinheit heißt eine Anwendung, wenn sie Daten oder Funktionen einer anderen Übersetzungseinheit verwendet.
Definition, Deklaration und Anwendung müssen übereinstimmen.
⇒ Demo 2
Inkonsistenz
Die getrennte Übersetzung hat auch Nachteile.
Wenn Deklaration und Definition einer Funktion in verschiedenen Übersetzungseinheiten stehen und z.B. wegen eine falschen Korrektur nicht zusammen passen, dann sind sie inkonsistent.
Eine Inkonsistenz bedeutet eine Widersprüchlichkeit zwischen den Daten.
Damit der Compiler die Konsistenz mancher Deklarationen prüfen kann, muss die entsprechende Header-Datei auch
in der Implementierungsdatei eingefügt werden.
⇒ Demo 3
CategoryProzeduraleProgrammierung