Inhaltsverzeichnis
minikurs(8): Mehrere Quelldateien
Wir brauchen eine 360°-Sicht.— Manager-Sprech für: Ich habe keinen Überblick.
Aufteilen
Große Programme bestehen aus Millionen Quelltextzeilen. Es ist unvorstellbar, dass all diese Zeilen in einer Quelldatei stehen. Laden, Speichern, Suchen und Bearbeiten in großen Dateien dauert zu lange. Editoren beschränken die Dateigröße, ältere gar nur auf 64 Kilobyte. Mehrere Programmierer müssen gleichzeitig verschiedene Programmteile bearbeiten. Meist wird nur ein kleiner Teil des Programms verändert. Das Übersetzen großer Programme kann sehr lange dauern. Manche Teile eines Programms lassen sich auch für andere Programme als Bibliothek nutzen. Kopieren und Einfügen ist aber zu umständlich. Wichtige Änderungen würden nicht in die Kopien übernommen, es sei denn, jemand wird beauftragt, alle Kopien zu finden und zu ändern. Eine undankbare Aufgabe, bei der man so viel falsch machen kann…
Funktionen bündeln Anweisungen. Wir gehen nun eine Stufe höher. Funktionen lassen sich zu Programmbausteinen (Modulen) zusammenfassen. Ein Modul1) umfasst alle inhaltlich zusammengehörenden Funktionen. Wir nehmen die große Schere und zerschneiden den Papierausdruck vom vorigen Quelltext an geeigneten Stellen…
Header
Um die Funktionen eines Bausteins in anderen Quelltexten nutzen zu können, werden übergreifende Typdefinitionen und die Funktionsköpfe in Vorspanndateien (Kopfdateien) untergebracht. Üblich ist die Endung *.h wie Header für solche Dateien. Typdefinitionen dürfen nur einmal je Übersetzerlauf vorkommen. Deshalb werden die Inhalte von Headern von einem "Include-Wächter" eingeschlossen:
#ifndef HEADERNAME_H #define HEADERNAME_H ... #endif // HEADERNAME_H
Dieser sorgt dafür, dass bei versehentlicher zweiter Begegnung der Compiler diesen Inhalt nicht noch einmal verarbeitet. Ohne Include-Wächter müsste man für alle Quelldateien überlegen: Muss dieser Header jetzt eingebunden werden oder ist er durch andere bereits eingebunden?
Ein Header für das Parkplatzprogramm kann so aussehen:
- funktionen.h
//: funktionen.h : Autos und Zweiraeder - R.Richter 2007-01-07 ////////////////////////////////////////////////////////////// #ifndef FUNKTIONEN_H #define FUNKTIONEN_H int autos (int fahrzeuge, int reifen); int mopeds(int fahrzeuge, int reifen); #endif // FUNKTIONEN_H
Übersetzungseinheiten
Die einzelnen Funktionen des Programms können auf mehrere Quelltexte verteilt werden. Die Anmeldung der Funktionen erfolgt durch Einbinden der Vorspanndatei. Dort steht alles, was das Hauptprogramm über die aufzurufenden Funktionen wissen muss.
- parkplatz6.cpp
//: parkplatz6.cpp : Autos und Zweiraeder - R.Richter 2007-01-07 //////////////////////////////////////////////////////////////// #include <iostream> #include "funktionen.h" int main() { int fahrzeuge, reifen; std::cout << "Fahrzeuge: "; std::cin >> fahrzeuge; std::cout << "Reifen: "; std::cin >> reifen; std::cout << "Autos: " << autos (fahrzeuge, reifen) << '\n'; std::cout << "Motorraeder: " << mopeds(fahrzeuge, reifen) << '\n'; return 0; }
Die Implementierung der Funktionen wird in einer anderen Übersetzungseinheit untergebracht:
- funktionen.cpp
//: funktionen.cpp : Autos und Zweiraeder - R.Richter 2007-01-07 //////////////////////////////////////////////////////////////// #include "funktionen.h" int autos (int fahrzeuge, int reifen) { return fahrzeuge - mopeds(fahrzeuge, reifen); } int mopeds(int fahrzeuge, int reifen) { return (4*fahrzeuge - reifen) / 2; } //~
Hier wäre es nicht unbedingt erforderlich, den Header einzubinden. Es erspart aber, darüber nachdenken zu müssen, welche der Funktionen zuoberst stehen muss. So bleibt der Kopf frei für andere Aufgaben. Bevor der Quelltext weiter entwickelt werden kann, muss noch eine Aufgabe gelöst werden.
Ein Programm aus mehreren Dateien kann nicht mehr so einfach übersetzt werden.
Jede Funktion darf im Projekt nur genau einmal definiert sein.
Eine der Quellen muss die Funktion main()
enthalten.
Wie wird dem Compiler mitgeteilt,
welche der vielen Quelldateien zusammen ein Programm bilden?
Diese Verwaltungsinformation ist nicht im Quelltext abgelegt.
Projektverwaltung
Makefile
Eine Makefile
genannte Textdatei ohne Endung(!) enthält Regeln für das make-Werkzeug in der Form
Ziel: Voraussetzungen Befehle
Beachte, dass vor den Befehlen ein Tabulatorzeichen muss. (Legt Dein Editor wirklich Tabulatoren als ein Tab-Zeichen in der Datei ab oder ersetzt er sie durch 8 Leerzeichen?) Beim Aufruf
make
sucht das Werkzeug im aktuellen Verzeichnis nach dem Makefile
.
Es versucht dann, das angegebene Ziel zu erreichen.
Für das Parkplatzprogramm könnten die Regeln so beschaffen sein:
all: parkplatz parkplatz: funktionen.h funktionen.cpp parkplatz6.cpp $(CXX) -o parkplatz funktionen.cpp parkplatz6.cpp
Ist dieser Text für Dich verständlich?
Alles, was zu tun ist, ist, das Programm parkplatz
zu bauen.
Dafür sind die 3 angegebenen Quelldateien notwendig. Fehlt eine, schlage Krach.
Sind die Quellen aktueller als das Ziel parkplatz
,
dann übersetze die beiden Dateien mit der Endung *.cpp.
Das make-Werkzeug kennt den Compilernamen als Zeichenkette $(CXX)
.
Getrennte Übersetzung und Testautomatisierung
Vor allem in großen Projekten ist es günstig, mit Zwischendateien zu arbeiten. Nicht bei jeder Änderung müssen alle Dateien neu übersetzt werden. Die Übersetzung mit der Option -c prüft einen einzelnen Quelltext. Bei korrekter Syntax entsteht eine gleichnamige Objektdatei. Diese Zwischendateien werden im zweiten Schritt zum lauffähigen Programm verbunden ("gelinkt").
funktionen.o: funktionen.h funktionen.cpp $(CXX) -c funktionen.cpp parkplatz6.o: funktionen.h parkplatz6.cpp $(CXX) -c parkplatz6.cpp parkplatz: funktionen.o parkplatz6.o $(CXX) -o parkplatz funktionen.o parkplatz6.o
Leider ist das Makefile jetzt plattformabhängig: Die Dateinamen *.o ist bei Unix-Compilern üblich, *.obj bei Windows-Compilern.
Nimm kleine Veränderungen an der *.h-Datei oder an einer *.cpp-Datei vor. Rufe make auf und beobachte an den Ausschriften, welche Dateien neu übersetzt werden. Beachte dabei: Unter DOS / Windows tragen die ausführbaren Programme die Endung *.exe. Möglicherweise stellt sich make komisch an, wenn diese Endung fehlt.
Ein Makefile enthält meist noch ein Ziel clean
,
das Zwischendateien löscht. Hier ein Beispiel für Unixe:
clean: -rm -f parkplatz *.o
Auch Testprogramme für einzelne Bausteine und das Komplettsystem lassen sich im Makefile unterbringen. Regressionstests stellen sicher, dass ein Programm auch nach einer Quelltextänderung wieder stabil läuft. Ein automatisierter Test startet dann zum Beispiel mit
make test
Entwicklungsumgebung
Integrierte Entwicklungsumgebungen verwalten die Informationen, was zu einem Programm gehört, in einem "Projekt". Die Benennungen sind bei jedem Produkt etwas anders. Suche nach einem Projektfenster oder Menüeintragen wie
- Datei | Neu | Projekt
- Projekt | Datei hinzufügen
Meist muss man sich hier durch mehrere Bildschirmfenster klicken. Wie übersichtlich und zuverlässig ist dagegen ein Makefile…
Weiter: Teil 9.