Zuerst werden die beiden Module vorgestellt. Das Modul db
simuliert die ,,Datenbank`` nur, indem es einen Typ für die Datensätze
definiert (record_t
), und davon einfach ein Feld anlegt. Die
einzige Funktion lesedb() füllt das Feld ganz stumpf mit lauter
gleichen Werten:
// Time-stamp: "(21.11.01 20:24) db.cpp [Klaus Wachtler (aw38)]" // // Implementationsdatei (Funktionsdefinitionen) für Modul db // Damit der Compiler die Deklarationen in db.h mit den Definitionen // in dieser Datei vergleichen kann, sicherheitshalber die zugehörigen // Deklarationen einfügen (hier sind der Typ record_t und das Feld // arr[] deklariert): #include "db.h" // Das Feld wird hier auch definiert: record_t arr[N_RECORDS]; // könnte Werte aus einer Datenbank in das Feld arr[] lesen; setzt // hier aber nur ein paar fiktive Werte: void lesedb( void ) { for( int i=0; i<N_RECORDS; i++ ) { arr[i].i = 12; } }
Die zugehörigen Deklarationen für die Anwender des Moduls stehen in db.h:
// Time-stamp: "(21.11.01 20:31) db.h [Klaus Wachtler (aw38)]" // // Funktionsdeklarationen für Modul up // Damit mehrfaches #includen nicht zu Problemen führt: #ifndef _DB_H_ #define _DB_H_ #define N_RECORDS (1000) // soviele Datensätze typedef struct { int i; } record_t; // Datentyp für einen Record extern record_t arr[N_RECORDS]; // alle Records in einem Feld void lesedb( void ); #endif /// ifndef _DB_H_
Das Modul up enthält nur ein Unterprogramm, um das Feld von lesedb() füllen zu lassen; hier die Implementationsdatei:
// Time-stamp: "(21.11.01 20:26) up.cpp [Klaus Wachtler (aw38)]" // // Implementationsdatei (Funktionsdefinitionen) für Modul up // Damit der Compiler die Deklarationen in up.h mit den Definitionen // in dieser Datei vergleichen kann, sicherheitshalber die zugehörigen // Deklarationen einfügen: #include "up.h" void up( void ) { lesedb(); // in Modul db.cpp/db.h }
Und wieder die zugehörigen Deklarationen für die Anwender von up:
// Time-stamp: "(21.11.01 20:27) up.h [Klaus Wachtler (aw38)]" // // Funktionsdeklarationen für Modul up // Damit mehrfaches #includen nicht zu Problemen führt: #ifndef _UP_H_ #define _UP_H_ // Deklarationen für Modul db, weil up() eine der db-Funktionen // benötigt: #include "db.h" void up( void ); #endif /// ifndef _UP_H_
Das Hauptprogramm benötigt Deklarationen aus beiden Modulen (weil es
erst up() aufruft, sowie das Feld arr[]
verwendet), und bindet
deshalb auch beide Headerdateien ein:
// Time-stamp: "(21.11.01 20:31) haupt.cpp [Klaus Wachtler (aw38)]" // // Hauptprogramm eines kleinen Beispielprojekts #include <iostream> using namespace std; #include "up.h" // Deklarationen für Modul up #include "db.h" // Deklarationen für Modul db int main( int nargs, char **args ) { up(); // in Modul up.cpp/up.h cout << " Wert = " << arr[0].i // arr ist in db.h deklariert << endl; return 0; }
(Daß dadurch versehentlich db.h doppelt eingebunden wird, soll
nicht weiter stören; hier greift der Mechanismus mit
#ifndef _DB_H_
....)
Theoretisch könnte man jetzt das fertige Programm mit einem einzigen Compiler- und Linkeraufruf übersetzen und linken lassen:
g++ -Wall haupt.cpp up.cpp db.cpp -o haupt
Das ist zwar soweit korrekt, aber bei größeren Projekten nicht wünschenswert. Wenn es nicht wie hier nur um wenige Zeilen Quelltext geht, sondern um Dutzende Quelldateien mit jeweils einigen Tausend Zeilen, dann benötigt das Übersetzen doch eine gewisse Zeit. Wenn man aber nur an wenigen Stellen etwas geändert hat, wäre es interessant, auch nur die zugehörigen Module zu kompilieren und dann alles zu linken.
Dazu übersetzt man beim ersten Mal nicht mit dem einen obigen Aufruf, sondern Modul für Modul:
g++ -Wall -c haupt.cpp g++ -Wall -c up.cpp g++ -Wall -c db.cppDadurch werden die beiden Module und das Hauptprogramm nur jeweils einzeln kompiliert (wegen der Option -c), und in je einer Objektdatei abgelegt (haupt.o, up.o, db.o).
Erst anschließend fügt man diese Objektdateien mit dem Linker zu einem ausführbaren Programm zusammen:
g++ -Wall haupt.o up.o db.o -o haupt
Insofern hat man bisher nichts gewonnen; mit mehr Kommandos hat man das selbe Ergebnis wie in einfachen Fall zu Beginn. Der Vorteil ist aber, daß die Objektdateien (haupt.o, up.o, db.o) nach dem Linken noch erhalten bleiben, und bei Bedarf wieder verwendet werden können.
Das ist der Fall, wenn man jetzt an einzelnen Stellen im Quelltext etwas ändert.
Dafür ein paar einfache Beispiele:
g++ -Wall -c haupt.cpp g++ -Wall haupt.o up.o db.o -o haupt
g++ -Wall -c up.cpp g++ -Wall haupt.o up.o db.o -o haupt
g++ -Wall -c db.cpp g++ -Wall haupt.o up.o db.o -o haupt
Insoweit ist die Lage recht übersichtlich. Was passiert aber, wenn man an den Headerdateien Änderungen vornimmt?
AnyWare@Wachtler.de