he sc e ut ab De usg A
Beispiele und Lösungen für C++-Programmierer
C++
Kochbuch
TM
D. Ryan Stephens, Christopher Diggins, Jonathan Turkanis & Jeff Cogswell Deutsche Übersetzung von Andreas Heck und Thomas Demmig
C++ Kochbuch
D. Ryan Stephens, Christopher Diggins, Jonathan Turkanis & Jeff Cogswell
Deutsche Übersetzung von Andreas Heck & Thomas Demmig
Beijing
· Cambridge · Farnham · Köln · Paris · Sebastopol · Taipei · Tokyo
Die Informationen in diesem Buch wurden mit größter Sorgfalt erarbeitet. Dennoch können Fehler nicht vollständig ausgeschlossen werden. Verlag, Autoren und Übersetzer übernehmen keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene Fehler und deren Folgen. Alle Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt und sind möglicherweise eingetragene Warenzeichen. Der Verlag richtet sich im Wesentlichen nach den Schreibweisen der Hersteller. Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen. Kommentare und Fragen können Sie gerne an uns richten: O’Reilly Verlag Balthasarstr. 81 50670 Köln Tel.: 0221/9731600 Fax: 0221/9731608 E-Mail:
[email protected] Copyright der deutschen Ausgabe: © 2006 by O’Reilly Verlag GmbH & Co. KG 1. Auflage 2006 Die Originalausgabe erschien 2005 unter dem Titel C++ Cookbook bei O’Reilly Media, Inc. Die Darstellung eines Collie im Zusammenhang mit dem Thema C++ ist ein Warenzeichen von O’Reilly Media, Inc.
Bibliografische Information Der Deutschen Bibliothek Die Deutsche Bibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.ddb.de abrufbar. Übersetzung und deutsche Bearbeitung: Andreas Heck, Gundelsheim & Thomas Demmig, Mannheim Lektorat: Christine Haite, Köln Korrektorat: Friederike Daenecke, Zülpich Satz: DREI-SATZ, Husby Umschlaggestaltung: Edie Freedman & Karen Montgomery, Boston Produktion: Andrea Miß, Köln Belichtung, Druck und buchbinderische Verarbeitung: Druckerei Kösel, Krugzell; www.koeselbuch.de ISBN-10 3-89721-447-4 ISBN-13 978-3-89721-447-7 Dieses Buch ist auf 100% chlorfrei gebleichtem Papier gedruckt.
Inhalt
Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . XI 1 C++-Programme kompilieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.1 GCC beschaffen und installieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.2 Ein einfaches »Hallo Welt«-Programm von der Kommandozeile aus kompilieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 1.3 Eine statische Bibliothek von der Kommandozeile aus kompilieren . . . . . . 26 1.4 Eine dynamische Bibliothek von der Kommandozeile aus kompilieren . . . 27 1.5 Eine komplexe Anwendung von der Kommandozeile aus kompilieren . . . 36 1.6 Boost.Build installieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 1.7 Ein einfaches »Hallo Welt«-Programm mit Boost.Build kompilieren . . . . . 44 1.8 Eine statische Bibliothek mit Boost.Build kompilieren . . . . . . . . . . . . . . . . 48 1.9 Eine dynamische Bibliothek mit Boost.Build kompilieren . . . . . . . . . . . . . 50 1.10 Ein komplexes Programm mit Boost.Build kompilieren . . . . . . . . . . . . . . . 51 1.11 Eine statische Bibliothek mit einer IDE kompilieren . . . . . . . . . . . . . . . . . . 55 1.12 Eine dynamische Bibliothek mit einer IDE kompilieren . . . . . . . . . . . . . . . 59 1.13 Ein komplexes Programm mit einer IDE kompilieren . . . . . . . . . . . . . . . . . 63 1.14 GNU make beschaffen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 1.15 Ein einfaches »Hallo Welt«-Programm mit GNU make kompilieren . . . . . 72 1.16 Eine statische Bibliothek mit GNU make kompilieren . . . . . . . . . . . . . . . . 80 1.17 Eine dynamische Bibliothek mit GNU make kompilieren . . . . . . . . . . . . . . 85 1.18 Ein komplexes Programm mit GNU make kompilieren . . . . . . . . . . . . . . . 87 1.19 Ein Makro definieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 1.20 Einen Kommandozeilen-Parameter in einer IDE angeben . . . . . . . . . . . . . . 94 1.21 Einen Debug Build erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 1.22 Einen Release Build erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 1.23 Eine Version der Laufzeitbibliothek auswählen . . . . . . . . . . . . . . . . . . . . . 102 1.24 Die strikte Einhaltung des C++-Standards erzwingen . . . . . . . . . . . . . . . . 106
| V
1.25 Eine Quelldatei automatisch gegen eine bestimmte Bibliothek linken lassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 1.26 Exportierte Templates verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
2 Code organisieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 2.1 Headerdateien nur einmal einbinden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 Eine Instanz einer Variablen über mehrere Quelldateien hinweg verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3 Die Zahl der #includes mit Hilfe vorgezogener Klassendeklarationen reduzieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4 Namenskollisionen mit Hilfe von Namensräumen verhindern . . . . . . . . . 2.5 Eine Inline-Datei einbinden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
119 121 123 125 132
3 Zahlen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 3.1 3.2 3.3 3.4
Einen String in einen numerischen Typ umwandeln . . . . . . . . . . . . . . . . . Zahlen in Strings umwandeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Herausfinden, ob ein String eine gültige Zahl enthält . . . . . . . . . . . . . . . . Fließkommazahlen innerhalb einer beschränkten Genauigkeit vergleichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5 Einen String mit einer Zahl in wissenschaftlicher Notation parsen . . . . . . 3.6 Zahlen zwischen verschiedenen numerischen Typen konvertieren . . . . . . 3.7 Den größten und den kleinsten möglichen Wert eines numerischen Typs bestimmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
134 136 140 143 145 147 150
4 Strings und Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 4.10 4.11 4.12 4.13 4.14
Einen String mit Füllzeichen auffüllen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 Einen String trimmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 Strings als Sequenz ablegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 Die Länge eines Strings ermitteln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 Einen String umkehren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Einen String in Teilstrings aufspalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 Einen String in Tokens zerlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 Eine Sequenz von Strings zu einem String vereinen . . . . . . . . . . . . . . . . . . 176 Suchen in Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 Das n-te Vorkommen eines Teilstrings ermitteln . . . . . . . . . . . . . . . . . . . . 182 Einen Teilstring aus einem String entfernen . . . . . . . . . . . . . . . . . . . . . . . 183 Alle Zeichen eines Strings in Klein- oder Großbuchstaben umwandeln . . 185 String-Vergleiche ohne Beachtung von Groß- und Kleinschreibung . . . . . 188 Einen String ohne Beachtung von Groß- und Kleinschreibung durchsuchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 4.15 Tabulatoren und Leerzeichen in einer Textdatei ineinander konvertieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 VI |
Inhalt
4.16 Zeilen in einer Textdatei umbrechen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.17 Die Anzahl der Zeichen, Wörter und Zeilen in einer Textdatei ermitteln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.18 Die Häufigkeit jedes in einer Textdatei vorkommenden Wortes zählen . . 4.19 Eine Textdatei mit Rändern versehen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.20 Eine Textdatei links- oder rechtsbündig formatieren . . . . . . . . . . . . . . . . 4.21 Whitespace in Textdateien durch einzelne Leerzeichen ersetzen . . . . . . . 4.22 Tippfehler in einem sich ändernden Text automatisch korrigieren . . . . . . 4.23 Eine Textdatei mit kommaseparierten Einträgen einlesen . . . . . . . . . . . . . 4.24 Einen String mit Hilfe regulärer Ausdrücke in Tokens zerlegen . . . . . . . .
196 198 201 204 207 210 211 214 217
5 Datumswerte und Uhrzeiten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 5.1 5.2 5.3 5.4 5.5 5.6
Das aktuelle Datum und die Uhrzeit ermitteln . . . . . . . . . . . . . . . . . . . . . Datum und Uhrzeit als String formatieren . . . . . . . . . . . . . . . . . . . . . . . . . Mit Datums- und Uhrzeitwerten rechnen . . . . . . . . . . . . . . . . . . . . . . . . . Zwischen Zeitzonen konvertieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Tagesnummer in einem bestimmten Jahr ermitteln . . . . . . . . . . . . . . Typen mit begrenztem Wertebereich definieren . . . . . . . . . . . . . . . . . . . .
220 222 225 227 228 230
6 Daten mit Containern verwalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9
Vektoren statt Arrays verwenden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Vektoren effizient einsetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen Vektor kopieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zeiger in einem Vektor speichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objekte in einer Liste speichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . String auf andere Dinge abbilden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gehashte Container nutzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objekte sortiert abspeichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Container in Containern speichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
236 240 245 247 249 254 260 265 269
7 Algorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 7.10 7.11
Über einen Container iterieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objekte aus einem Container entfernen . . . . . . . . . . . . . . . . . . . . . . . . . . Daten zufällig mischen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bereiche vergleichen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Daten verschmelzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen Bereich sortieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen Bereich aufteilen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mengenoperationen mit Sequenzen ausführen . . . . . . . . . . . . . . . . . . . . . Elemente in einer Sequenz transformieren . . . . . . . . . . . . . . . . . . . . . . . . . Schreiben Sie Ihren eigenen Algorithmus . . . . . . . . . . . . . . . . . . . . . . . . . Einen Bereich in einen Stream ausgeben . . . . . . . . . . . . . . . . . . . . . . . . . .
273 280 283 285 289 292 295 297 301 303 306
Inhalt |
VII
8 Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310 8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8 8.9 8.10 8.11 8.12 8.13 8.14 8.15
Datenelemente einer Klasse initialisieren . . . . . . . . . . . . . . . . . . . . . . . . . . Objekte mit einer Funktion erstellen (Fabrikmuster) . . . . . . . . . . . . . . . . Mit Konstruktoren und Destruktoren Ressourcen verwalten (RAII) . . . . . Einem Container automatisch neue Klasseninstanzen hinzufügen . . . . . . Sicherstellen, dass nur eine einzelne Version eines Datenelements existiert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Den Typ eines Objekts zur Laufzeit ermitteln . . . . . . . . . . . . . . . . . . . . . . Bestimmen, ob die Klasse eines Objekts eine Unterklasse eines anderen Objekts ist . . . . . . . . . . . . . . . . . . . . . . . . . . . . Jeder Instanz einer Klasse eine eindeutige Identifizierung geben . . . . . . . . Eine Singleton-Klasse erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eine Schnittstelle mit einer abstrakten Basisklasse erstellen . . . . . . . . . . . Ein Klassen-Template schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein Elementfunktions-Template schreiben . . . . . . . . . . . . . . . . . . . . . . . . Die Inkrement- und Dekrementoperatoren überladen . . . . . . . . . . . . . . . Arithmetische und Zuweisungsoperatoren für ein intuitives Verhalten einer Klasse überladen . . . . . . . . . . . . . . . . . . . . . . . . Eine virtuelle Funktion der Superklasse aufrufen . . . . . . . . . . . . . . . . . . .
311 314 316 319 321 323 325 326 329 331 336 341 344 347 354
9 Ausnahmen und Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356 9.1 9.2 9.3 9.4 9.5
Eine Ausnahmeklasse erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen Konstruktor ausnahmefest machen . . . . . . . . . . . . . . . . . . . . . . . . . Eine Initialisierungsliste ausnahmefest machen . . . . . . . . . . . . . . . . . . . . . Elementfunktionen ausnahmefest machen . . . . . . . . . . . . . . . . . . . . . . . . Ein Objekt sicher kopieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
356 361 364 368 373
10 Streams und Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378 10.1 10.2 10.3 10.4 10.5 10.6 10.7 10.8 10.9 10.10 10.11 10.12 10.13 10.14
Text bei der Ausgabe ausrichten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gleitkommawerte formatieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schreiben Sie Ihre eigenen Stream-Manipulatoren . . . . . . . . . . . . . . . . . . Eine Klasse in einen Stream schreiben können . . . . . . . . . . . . . . . . . . . . . Eine Klasse aus einem Stream lesen können . . . . . . . . . . . . . . . . . . . . . . . Informationen über eine Datei erhalten . . . . . . . . . . . . . . . . . . . . . . . . . . . Eine Datei kopieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eine Datei löschen oder umbenennen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Temporäre Dateinamen und Dateien erstellen . . . . . . . . . . . . . . . . . . . . . Ein Verzeichnis erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein Verzeichnis löschen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Den Inhalt eines Verzeichnisses lesen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Dateierweiterung aus einem String extrahieren . . . . . . . . . . . . . . . . . . Einen Dateinamen aus einem kompletten Pfad extrahieren . . . . . . . . . . .
VIII | Inhalt
379 383 386 391 394 396 398 402 404 406 408 411 413 415
10.15 Den Pfad aus einem vollständigen Pfad mit Dateinamen ermitteln . . . . . . 416 10.16 Eine Dateierweiterung ersetzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418 10.17 Zwei Pfade in einem einzelnen Pfad zusammenführen . . . . . . . . . . . . . . . 419
11 Wissenschaft und Mathematik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 11.1 Die Anzahl der Elemente in einem Container ermitteln . . . . . . . . . . . . . . 11.2 Den größten oder kleinsten Wert in einem Container finden . . . . . . . . . . 11.3 Die Summe und den Durchschnitt der Elemente in einem Container berechnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.4 Werte außerhalb eines bestimmten Bereichs ausfiltern . . . . . . . . . . . . . . . 11.5 Varianz, Standardabweichung und andere statistische Funktionen berechnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.6 Zufallszahlen erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.7 Einen Container mit Zufallszahlen initialisieren . . . . . . . . . . . . . . . . . . . . 11.8 Einen numerischen Vektor mit dynamischer Größe darstellen . . . . . . . . . 11.9 Einen numerischen Vektor mit fester Größe darstellen . . . . . . . . . . . . . . . 11.10 Ein Skalarprodukt berechnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.11 Die Norm eines Vektors berechnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.12 Den Abstand zwischen zwei Vektoren berechnen . . . . . . . . . . . . . . . . . . . 11.13 Einen Stride-Iterator implementieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.14 Eine Matrix mit dynamischer Größe implementieren . . . . . . . . . . . . . . . . 11.15 Eine Matrix mit konstanter Größe implementieren . . . . . . . . . . . . . . . . . . 11.16 Matrizen multiplizieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.17 Die Fast-Fourier-Transformation berechnen . . . . . . . . . . . . . . . . . . . . . . . 11.18 Mit Polar-Koordinaten arbeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.19 Berechnungen mit Bitsets durchführen . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.20 Große Integer-Werte mit fester Länge darstellen . . . . . . . . . . . . . . . . . . . . 11.21 Zahlen mit festen Nachkommastellen implementieren . . . . . . . . . . . . . . .
424 425 429 431 433 436 438 440 441 445 446 447 449 452 456 459 461 463 465 469 473
12 Multithreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 476 12.1 12.2 12.3 12.4 12.5
Einen Thread erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eine Ressource thread-sicher machen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen Thread von einem anderen aus benachrichtigen . . . . . . . . . . . . . . . Gemeinsam genutzte Ressourcen einmal initialisieren . . . . . . . . . . . . . . . Einer Thread-Funktion ein Argument übergeben . . . . . . . . . . . . . . . . . . .
477 481 489 493 494
13 Internationalisierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497 13.1 13.2 13.3 13.4 13.5
Einen Unicode-String im Quelltext eingeben . . . . . . . . . . . . . . . . . . . . . . Zahlen lesen und schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Datumswerte und Uhrzeiten lesen und schreiben . . . . . . . . . . . . . . . . . . . Währungsbeträge lesen und schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . Lokalisierte Strings sortieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Inhalt
498 499 503 509 514 | IX
14 XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516 14.1 14.2 14.3 14.4 14.5 14.6 14.7 14.8 14.9
Ein einfaches XML-Dokument parsen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mit Xerces-Strings arbeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein komplexes XML-Dokument parsen . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein XML-Dokument verändern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein XML-Dokument gegen eine DTD validieren . . . . . . . . . . . . . . . . . . . . Ein XML-Dokument gegen ein Schema validieren . . . . . . . . . . . . . . . . . . Ein XML-Dokument mit XSLT umwandeln . . . . . . . . . . . . . . . . . . . . . . . Einen XPath-Ausdruck auswerten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eine Collection von Objekten mit XML speichern und laden . . . . . . . . . .
517 526 529 540 545 549 553 559 566
15 Verschiedenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 572 15.1 15.2 15.3 15.4 15.5 15.6
Funktionszeiger für Callbacks nutzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zeiger auf Elemente einer Klasse nutzen . . . . . . . . . . . . . . . . . . . . . . . . . . Sicherstellen, dass eine Funktion ein Argument nicht verändert . . . . . . . . Sicherstellen, dass eine Elementfunktion ihr Objekt nicht verändert . . . . Einen Operator schreiben, der keine Elementfunktion ist . . . . . . . . . . . . . Eine Sequenz mit kommaseparierten Werten initialisieren . . . . . . . . . . . .
572 574 577 580 582 584
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 587
X
| Inhalt
Vorwort
C++ existiert auf nahezu jeder Plattform, und es gibt unzählig viele Anwendungen, die in dieser Programmiersprache geschrieben sind. Wenn Sie dieses Buch gekauft haben oder darüber nachdenken, es zu kaufen, sind Sie vielleicht sogar ein Software-Entwickler oder Wissenschaftler, der an einer dieser Anwendungen programmiert. Aber unabhängig davon, was Sie programmieren und für welche Plattform Sie programmieren, ist es sehr wahrscheinlich, dass Sie es dabei mit denselben Problemen zu tun bekommen, mit denen sich schon viele andere C++-Programmierer vor Ihnen abgemüht haben. In diesem Buch haben wir Lösungen für viele dieser häufig vorkommenden Probleme sowie eine Erklärung für jede dieser Lösungen zusammengetragen. Egal ob Sie nun schon seit Jahren in C++ programmieren oder erst seit kurzem mit dieser Sprache arbeiten – Ihnen sind höchstwahrscheinlich schon einige der Dinge bekannt, die Sie in jedem Projekt erneut programmieren müssen. Dazu gehören u.a. das Parsen von Datums- und Zeitangaben und das Rechnen mit Datums- und Zeitangaben, String- und Textmanipulation, Dateiverarbeitung, das Parsen von XML und die Verwendung der Standard-Container. Dies ist die Art von Problemen, für die dieses Buch Ihnen Lösungen anbietet. Bei einigen dieser Problemstellungen (wie z.B. dem Rechnen mit Datums- und Zeitangaben) unterstützt die Standard-Bibliothek Sie nur sehr dürftig. Für andere Probleme (wie z.B. die Manipulation von Strings) bietet die Standard-Bibliothek mächtige Klassen. Leider lässt sich damit auch in diesem Fall nicht jedes Problem lösen, und die Lösung mancher sehr häufig vorkommender Probleme ist trotzdem recht umständlich. Dieses Buch ist einfach und geradlinig aufgebaut. Jedes Rezept besteht aus einer Problembeschreibung und einem Code-Fragment zur Lösung des Problems. Die meisten Rezepte haben noch einen zusätzlichen Abschnitt, in dem die Lösung genauer besprochen wird. Wir haben immer versucht möglichst pragmatisch an die Probleme heranzugehen und nicht zu weit abzuschweifen. Allerdings gibt es in vielen Fällen verwandte Themen, die so nützlich (oder einfach nur so interessant) sind, dass wir ein oder auch zwei Seiten an Erklärungen benötigen, um darauf näher einzugehen.
| XI
In diesem Buch geht es darum, wie man in C++ häufig vorkommende Probleme löst. Zum Erlernen von C++ ist dieses Buch dagegen nicht gedacht. Wir gehen davon aus, dass Sie zumindest über ein grundlegendes Verständnis von C++ und objektorientierter Programmierung verfügen. Es wäre hilfreich, wenn Sie mit folgenden Themen zumindest einigermaßen vertraut sind: • Vererbung und virtuelle Methoden in C++ • Die Standard-Bibliothek • Teile der Standard Template Library (Container, Iteratoren und Algorithmen) • Templates Die Kenntnis dieser Themengebiete ist nicht zwingend erforderlich, um dieses Buch zu lesen. Dennoch ist es hilfreich, wenn Sie damit zumindest teilweise vertraut sind.
Über die Beispiele Beim Schreiben unserer Code-Beispiele haben wir besonders auf Einfachheit, Portabilität und auf die Performance des Codes geachtet. Beim Design jeder dieser Problemlösungen sind wir nach folgendem Muster vorgegangen: Wenn möglich sollte ein Standard-Feature von C++ (der Sprache oder der Bibliothek) verwendet werden. Wenn das nicht möglich ist, sollte ersatzweise ein De-facto-Standard verwendet werden. So verwenden beispielsweise viele der Rezepte, die sich mit Strings beschäftigen, die standardmäßig vorhandene Klasse String, und die meisten der mathematischen und wissenschaftlichen Rezepte verwenden standardmäßig vorhandene numerische Typen, Container und Templates. Die Standard-Bibliothek bietet für derartige Aufgaben eine ausgezeichnete Unterstützung, weshalb diese Standard-Features von C++ hier überaus nützlich sind. Dagegen existieren für C++ nur wenige oder gar keine Standardmechanismen für Dinge wie Multithreading oder das Parsen von XML. Deshalb verwenden wir die Multithreading-Unterstützung, die von der Boost Threads-Bibliothek bereitgestellt wird, und für das Parsen von XML den Xerces Parser. In C++ gibt es oftmals mehrere Wege, um ein bestimmtes Problem zu lösen. Das gibt dem Entwickler zwar eine gewisse Flexibilität, kann aber auch zu Meinungsverschiedenheiten über die jeweils beste Vorgehensweise führen. In den meisten Beispielen präsentieren wir Ihnen die beste Lösung, die uns für das jeweilige Problem eingefallen ist. Das heißt aber nicht, dass es sich dabei um die beste Lösung überhaupt handelt. Falls es alternative Lösungen gibt, die in mancher Hinsicht besser und in anderer Hinsicht schlechter, sind präsentieren wir Ihnen die alternative Lösung dennoch (wenn beispielsweise die Lösung, die die Standard Template Library verwendet, hässlich oder unintuitiv, ist präsentieren wir Ihnen eventuell eine Alternative, die die Boost-Bibliotheken verwendet). Dadurch sollen Sie ein Gefühl für die verschiedenen Möglichkeiten bekommen, die es zur Lösung des jeweiligen Problems gibt. Viele der Beispiele verwenden Templates. Falls Sie bisher noch nicht viel Erfahrung im Schreiben von Templates gesammelt haben, sollten Sie das schleunigst nachholen. In dieXII |
Vorwort
sem Buch gibt es mit Ausnahme der zwei Rezepte in Kapitel 8 (8.11 und 8.12) sehr wenig einführendes Material zum Thema Templates. Die meisten der interessanten Entwicklungen rund um C++ finden in den Bereichen der Metaprogrammierung mit Templates und des Policy Based Designs statt. Zum Zeitpunkt, als dieses Buch geschrieben wurde, war in der C++-Gemeinde einiges in Bewegung. Der erste Technical Report (abgekürzt TR1) ist mehr oder weniger fertig. Bei diesem Report handelt es sich um eine standardisierte Liste von Features, die in die nächste Version des C++-Standards einfließen sollen. Zwar ist die Unterstützung durch die Implementierungen der Standard-Bibliothek nicht zwingend, dennoch haben viele Hersteller mit der Implementierung von TR1 bereits begonnen, und man kann davon ausgehen, dass bald erste Compiler mit Unterstützung für TR1 erscheinen werden. Viele der in TR1 spezifizierten Bibliotheken sind zuerst als Teil des Boost-Projekts entstanden. Wir werden häufig auf Bibliotheken des Boost-Projekts zurückgreifen. Bei Boost handelt es sich um eine Sammlung von portablen Bibliotheken, die als Open Source und unter den Augen vieler Programmierer entwickelt werden und Funktionalität bereitstellen, die in der Standard-Library oft vermisst wird. Zur der Zeit, als diese Zeilen geschrieben wurden, war Version 1.32 aktuell und Version 1.33 sollte demnächst freigegeben werden. In den Beispielen werden Sie häufig auf Verweise zu speziellen Boost-Bibliotheken stoßen. Weitere Informationen über Boost finden Sie auf der Projekt-Website unter www.boost. org.
In diesem Buch verwendete Konventionen In diesem Buch werden folgende typographische Konventionen verwendet: Kursivschrift Hebt neue Begriffe, URLs, E-Mail-Adressen, Dateinamen, Dateierweiterungen, Pfade, Verzeichnisse, Unix-Utilities, Befehle und Kommandozeilenargumente hervor. Größer- und Kleinerzeichen umgeben Platzhalter, an deren Stelle Sie einen Kommandozeilen-Parameter angeben müssen. Erscheinen solche Platzhalter im Text, dann werden sie kursiv dargestellt. Konstante Breite
Wird für Code und Code-Fragmente verwendet. Auch Klassen- und Methodennamen, die im Text vorkommen, werden in konstanter Breite dargestellt. Konstante Breite und Fettdruck
Kennzeichnet Benutzereingaben in Beispielen, in denen Ein- und Ausgaben zusammen vorkommen. Konstante Breite und Kursivschrift
Kennzeichnet vom Benutzer anzugebende Teile in Syntax-Beispielen.
Vorwort
| XIII
KAPITÄLCHEN Werden für Elemente von Benutzeroberflächen wie Menüpunkte und Buttons verwendet. Kennzeichnet einen Tipp, einen Vorschlag oder eine allgemeine Anmerkung.
Kennzeichnet eine Warnung.
Verwendung von Code-Beispielen Dieses Buch wurde geschrieben, um Sie bei Ihrer Arbeit zu unterstützen. Im Allgemeinen dürfen Sie die Code-Beispiele aus diesem Buch auch in Ihren eigenen Programmen und in Ihrer Dokumentation verwenden. Sie müssen uns nicht nach unserer Einwilligung fragen, solange Sie nicht eine große Menge Code eins zu eins übernehmen. Wenn Sie beispielsweise ein Programm schreiben und dazu mehrere Code-Teile aus diesem Buch verwenden, benötigen Sie dazu von uns keine Erlaubnis. Wenn Sie dagegen eine CDROM mit Beispielen aus O’Reilly-Büchern verkaufen, benötigen Sie sehr wohl unsere Erlaubnis. Beantworten Sie eine Frage, indem Sie dieses Buch zitieren, und verwenden Sie dazu auch ein Code-Beispiel aus diesem Buch, dann benötigen Sie keine Erlaubnis. Wenn Sie aber eine größere Menge von Beispiel-Code aus diesem Buch in Ihre eigene Dokumentation einfließen lassen, benötigen Sie wieder unsere Einwilligung. Wir schätzen es zwar sehr, wenn Sie unsere Arbeit durch einen Hinweis auf unsere Urheberschaft würdigen, verlangen das aber nicht ausdrücklich. Eine solche Nennung besteht für gewöhnlich aus dem Buchtitel, dem Namen des Autors und des Verlags sowie aus der ISBN. Beispiel: »C++ Kochbuch von D. Ryan Stephens, Christopher Diggins, Jonathan Turkanis und Jeff Cogswell. Verlag O’Reilly, 2006. ISBN 3-89721-447-4«. Falls Sie sich nicht sicher sind, ob die obige Erlaubnis auch auf Ihren jeweiligen Verwendungszweck zutrifft, können Sie uns gerne unter
[email protected] kontaktieren.
Danksagungen Von D. Ryan Stephens Die wichtigsten Menschen, denen ich hier danken möchte, sind meine Frau Daphne und meine Kinder Jesse, Pascal und Chloe. Ein Buch zu schreiben ist harte Arbeit und kostet sehr viel Zeit, und meine Familie hat mich immer hervorragend unterstützt und es toleriert, wenn ich wieder einmal bis spät in die Nacht im Büro saß.
XIV | Vorwort
Des Weiteren möchte ich den technischen Sachverständigen danken, durch deren Arbeit dieses Buch deutlich verbessert wurde. Wie bei so vielem anderen ist es auch hier sehr hilfreich, wenn zwei, drei oder auch vier andere Menschen das Buch auf eine klare Ausdrucksweise und Korrektheit hin überprüfen. In diesem Sinne also vielen Dank an Dan Saks, Uwe Schnitker und David Theese. Schließlich muss ich noch meinem Lektor Jonathan Gennick für seine Ratschläge danken. Diese bezogen sich meist auf die Grammatik, oftmals auf den Stil, waren gelegentlich erbauend, aber ganz bestimmt immer nützlich.
Von Christopher Diggins Ich möchte an dieser Stelle Kris Unger, Jonathan Turkanis, Jonathan Gennick und Ryan Stephens für ihre hilfreichen Vorschläge und für ihre konstruktive Kritik danken, durch die ich zu einem besseren Autor geworden bin. Mein besonderer Dank gilt meiner Frau Mélanie Charbonneau, die mein Leben erhellt.
Von Jonathan Turkanis Da sich die von mir geschriebenen Kapitel mit so vielen kommerziellen Produkten und Open Source-Projekten befassen – und weil ich zu jedem einzelnen dieser Programme so viele Fragen hatte –, gilt mein Dank einer ungewöhnlich großen Zahl von Menschen. Lassen Sie mich zuerst Ron Liechty, Howard Hinnant und den Entwicklern von Metrowerks dafür danken, dass sie mir jede nur erdenkliche Frage beantwortet und mir mehrere Versionen von CodeWarrior zur Verfügung gestellt haben. Als Nächstes möchte ich den Entwicklern von Boost.Build und ganz besonders Vladimir Prus, Rene Rivera und David Abrahams danken. Ich danke ihnen aber nicht nur für die Beantwortung meiner Fragen, sondern auch dafür, dass sie das Build-System von Boost entwickelt haben, da es für mich die mit Abstand wichtigste Informationsquelle für Kapitel 1 darstellte. Weiterhin danke ich Walter Bright von Digital Mars; Greg Comeau von Comeau Computing; P. J. Plauger von Dinkumware; Colin Laplace von Bloodshed Software; Ed Mulroy und Pavel Vozenilek von den borland.public.* Newsgroups; Arnaud Debaene und Igor Tandetnik von microsoft.public.vc.languages; Earnie Boyd, Greg Chicares, Adib Taraben, John Vandenberg und Lennart Borgman von der MinGW-/MSYS-Mailingliste; Christopher Faylor, Larry Hall, Igor Pechtchanski, Joshua Daniel Franklin und Dave Korn von der Cygwin-Mailingliste; Mike Stump und Geoffrey Keating von der GCC-DevelopersListe; Mark Goodhand von DecisionSoft und David N. Bertoni von apache.org. Außerdem gilt mein Dank Robert Mecklenburg, dessen Buch Managing Projects with GNU make, 3. Auflage, (O’Reilly) mir die Grundlage für meine Behandlung von GNU make lieferte.
Vorwort
| XV
Zusätzlich muss noch erwähnt werden, dass Vladimir Prus, Matthew Wilson, Ryan Stephens und Christopher Diggins frühe Versionen des Manuskripts probegelesen und mich durch ihre detaillierte Kritik unterstützt haben. Schließlich danke ich noch meinem Lektor Jonathan Gennick, meiner Frau Jennifer und meinem Großvater, Louis S. Goodman, der mich wichtige Dinge über das Schreiben gelehrt hat.
XVI
| Vorwort
KAPITEL 1
C++-Programme kompilieren
1.0
Einführung
Die Rezepte in diesem Kapitel beschäftigen sich damit, wie man C++-Quellcode in ausführbare Programme und Bibliotheken übersetzt. Wenn Sie sich durch diese Rezepte durcharbeiten, lernen Sie, welche elementaren Programme es gibt, um C++-Programme zu kompilieren, welche unterschiedlichen Arten von Binärdateien während des Übersetzungsprozesses eingesetzt werden und welche System existieren, um die Übersetzung größerer C++-Applikationen überschaubar zu halten. Wenn Sie sich die Überschriften zu den Rezepten in diesem Kapitel anschauen, könnten Sie den Eindruck bekommen, dass ich hier immer wieder dieselben Probleme löse. Das ist auch tatsächlich so. Der Grund ist einfach der, dass es viele verschiedene Möglichkeiten gibt, um C++-Applikationen zu kompilieren, und ich hier, wenn ich auch nicht alle dieses Möglichkeiten aufzeigen kann, doch zumindest die wichtigsten behandeln möchte. Im ersten Dutzend der Rezepte behandle ich drei fundamentale Aufgaben – das Kompilieren statischer Bibliotheken, das Kompilieren dynamischer Bibliotheken und das Kompilieren ausführbarer Programme – und zeige verschiedene Wege, um diese Probleme zu lösen. Die Rezepte sind nach der gewählten Vorgehensweise gruppiert: Zuerst verwende ich zum Kompilieren die Kommandozeile, dann das Boost Build-System (Boost.Build), anschließend eine integrierte Entwicklungsumgebung (Integrated Development Environment, kurz IDE) und schließlich GNU make. Bevor Sie die eigentlichen Rezepte lesen, sollten Sie die folgenden einführenden Abschnitte lesen. Dort erkläre ich einige Grundbegriffe, gebe Ihnen einen Überblick über die in diesem Kapitel behandelten Kommandozeilen-Tools, Build-Systeme und IDEs und stelle schließlich die ebenfalls in diesem Kapitel verwendeten Code-Beispiele vor. Selbst wenn Sie vorhaben, ein Build-System oder eine IDE zu verwenden, sollten Sie zuerst die Rezepte lesen, die sich mit dem Kompilieren über die Kommandozeile beschäftigen. In diesen Rezepten werden einige wichtige Konzepte vorgestellt, deren Verständnis weiter hinten in diesem Kapitel vorausgesetzt wird. | 1
Grundbegriffe Die drei wichtigsten Programme, die beim Kompilieren eines C++-Programms verwendet werden, sind der Compiler, der Linker und der Archiver (oder Librarian). Eine Sammlung dieser Programme und möglicher weiterer Tools wird als Toolset bezeichnet. Der Compiler verarbeitet als Eingabe C++-Quelldateien und produziert als Ausgabe Objektdateien, die eine Mischung aus Maschinencode und symbolischen Referenzen auf Funktionen und Daten enthalten. Der Archiver erhält als Eingabe eine Sammlung von Objektdateien und erzeugt daraus eine statische Bibliothek (auch als Archiv bezeichnet), bei der es sich lediglich um eine Sammlung von Objektdateien handelt, die zur einfacheren Verwendung in einem Archiv zusammengefasst wurden. Der Linker erwartet als Eingabe eine Zusammenstellung von Objektdateien und Bibliotheken und löst deren symbolische Referenzen auf, um daraus entweder ein ausführbares Programm oder eine dynamische Bibliothek zu erzeugen. Grob gesagt ersetzt der Linker jedes Vorkommen eines Symbols durch seine Definition. Wenn ein ausführbares Programm oder eine dynamische Bibliothek erzeugt wird, sagt man auch: sie wird gelinkt; von den dazu verwendeten Bibliotheken sagt man, dass sie gegen das ausführbare Programm oder die dynamische Bibliothek gelinkt werden. Ein ausführbares Programm oder eine Applikation ist ganz einfach jedes Programm, das vom Betriebssystem ausgeführt werden kann. Eine dynamische Bibliothek (auch als Shared Library bezeichnet) ist mit einem ausführbaren Programm vergleichbar, nur dass sie nicht allein ausgeführt werden kann. Eine solche Bibliothek hat einen Rumpf mit Maschinencode, der beim Start einer Anwendung in den Speicher geladen wird und von mehreren Programmen gemeinsam verwendet werden kann. Unter Windows werden dynamische Bibliotheken auch als Dynamic Link Libraries (DLLs) bezeichnet. Die Objektdateien und statischen Bibliotheken, die von einem Programm benötigt werden, müssen nur während der Kompilierung dieses Programms vorhanden sein. Dagegen müssen die von einem Programm benötigten dynamischen Bibliotheken auf dem System des Benutzers vorhanden sein, wann immer das Programm ausgeführt werden soll. In Tabelle 1-1 sehen Sie die Dateierweiterungen, die für diese vier Arten von Dateien normalerweise unter Windows und Unix verwendet werden. Wenn ich eine Datei erwähne, die unter Windows und Unix eine andere Dateiendung hat, werde ich diese Endung manchmal weglassen, falls sie sich aus dem Kontext bereits ergibt. Tabelle 1-1: Dateierweiterungen unter Windows und Unix Dateityp
Windows
Mac OS X
Andere Unixe
Objektdateien
.obj
.o
.o
Statische Bibliotheken
.lib
.a
.a
Dynamische Bibliotheken
.dll
.dylib
.so
Ausführbare Programme
.exe
Keine Erweiterung
Keine Erweiterung
2 | Kapitel 1: C++-Programme kompilieren
Wann immer ich in diesem Kapitel Unix sage, ist damit automatisch auch Linux gemeint.
Wenn Sie die Beispielprogramme aus diesem Kapitel kompilieren, werden Ihre Tools eine gewisse Zahl von Hilfsdateien erzeugen, deren Dateiendungen nicht in Tabelle 1-1 aufgeführt sind. Sofern ich nicht ausdrücklich etwas anderes sage, können Sie diese Dateien getrost ignorieren. Wenn Sie aber unbedingt erfahren möchten, wozu diese Dateien gut sind, sollten Sie dazu am besten die Dokumentation Ihres Toolsets konsultieren.
IDEs und Build-Systeme Der Compiler, der Linker und der Archiver sind Kommandozeilen-Tools, d.h., sie werden von einer Shell wie der bash unter Unix oder cmd.exe unter Microsoft Windows aus aufgerufen. Die Namen der Eingabe- und Ausgabedateien werden jedem dieser Tools zusammen mit allen anderen nötigen Konfigurationsparametern auf der Kommandozeile in Form von Text übergeben. Es ist jedoch recht umständlich, diese Tools von Hand aufzurufen. Selbst bei kleinen Projekten ist es nicht immer leicht, sich die KommandozeilenParameter aller Tools und die Reihenfolge zu merken, in der die Quellcode- und Binärdateien des Projekts kompiliert und gelinkt werden müssen. Sobald eine Quellcode-Datei geändert wird, müssen Sie herausfinden, welche Dateien neu übersetzt, welche statischen Bibliothek aktualisiert und welche ausführbaren Programme und dynamischen Bibliotheken neu gelinkt werden müssen. Wenn Sie dabei mehr Dateien als nötig neu erzeugen, haben Sie nur Ihre Zeit verschwendet. Erzeugen Sie dagegen nicht alle benötigten Dateien neu, erhalten Sie als Ergebnis einen fehlgeschlagenen Kompiliervorgang oder vielleicht sogar ein fehlerhaftes Kompilat. Bei größeren C++-Projekten – die aus Tausenden separater Quellcode-, Objektdateien, Bibliotheken und ausführbarer Programmdateien bestehen können – ist das direkte Kompilieren über die Kommandozeile schlicht und einfach unmöglich. Es gibt grundsätzlich zwei verschiedene Ansätze, um große C++-Anwendungen zu kompilieren: • Eine IDE stellt eine grafische Oberfläche bereit, mit der eine Zusammenstellung von Quellcode-Dateien verwaltet werden kann und die daraus zu erstellenden Binärdateien spezifiziert werden können. Sobald Sie diese Informationen angegeben haben, können Sie die Binärdateien generieren lassen, indem Sie einfach den entsprechenden Menüpunkt oder das entsprechende Icon einer Toolbar anklicken. Die IDE kümmert sich dabei darum, in welcher Reihenfolge welche Binärdateien zu erzeugen sind, und weiß von sich aus, welche Tools sie dazu mit welchen Parametern aufrufen muss. Wann immer Sie eine oder mehrere Quelldateien verändern, können Sie die IDE anweisen, dass sie nur die nicht mehr aktuellen Dateien neu kompiliert.
Einführung | 3
IDEs gruppieren Quelldateien in so genannten Projekten. Zu einem IDE-Projekt gehören meistens eine einzige Binärdatei oder mehrere Varianten einer Binärdatei, wie z.B. ein Debug und ein Release Build einer bestimmten Anwendung. Die meisten IDEs ermöglichen es dem Benutzer darüber hinaus, mehrere Projekte in so genannten Projekt-Gruppen oder auch Solutions zusammenzufassen, wobei dann die Abhängigkeiten zwischen den Projekten einer solchen Gruppe vom Benutzer spezifiziert werden können. • Ein Build-System besteht aus einem Textdatei-Format, in dem eine Zusammenstellung von Quelldateien und die daraus zu erzeugenden Binärdateien angegeben werden können, sowie aus einem Build Tool, das diese Dateien liest und die dort spezifizierten Binärdateien erzeugt, indem es die entsprechenden KommandozeilenTools aufruft. Für gewöhnlich werden diese Textdateien mit einem Texteditor erstellt, und das Build Tool wird meistens von der Kommandozeile aufgerufen. Manche Build-Systeme bieten jedoch auch eine grafische Oberfläche an, über die die Textdateien geändert und das Build Tool aufgerufen werden kann. Während IDEs die Quelldateien zu Projekten zusammenfassen, fassen Build-Systeme die Quelldateien zu so genannten Targets zusammen. Die meisten Targets entsprechen den zu erzeugenden Binärdateien, während andere Targets einer vom Build Tool auszuführenden Aktion entsprechen, wie beispielsweise der Installation des fertigen Programms. Das am weitesten verbreitete Build Tool ist das Programm make; die von ihm verwendeten Textdateien werden als Makefiles bezeichnet. Obwohl es viele unterschiedliche Versionen von make gibt, werde ich in diesem Kapitel nur GNU make besprechen, da es die mächtigste und portabelste Version von make darstellt. GNU make ist ein überaus flexibles Werkzeug, das für weitaus mehr Dinge als nur für das Kompilieren von C++-Anwendungen verwendet werden kann. Dieses Tool hat außerdem den gewichtigen Vorteil, dass es sehr häufig eingesetzt wird und sich viele Entwickler bereits damit auskennen. Leider kann es manchmal auch eine wahre Herausforderung sein, GNU make dazu zu bringen, dass es genau das macht, was man von ihm will. Besonders oft tritt dieses Problem bei komplexen Projekten auf, die mehrere verschiedene Toolsets verwenden. Aus diesem Grund werde ich hier ebenfalls Boost.Build, ein mächtiges und erweiterbares Build-System, behandeln, das von Grund auf speziell für das Kompilieren von C++Anwendungen entwickelt wurde. Eine vollständige Behandlung von GNU make finden Sie im Buch GNU make, 1. Auflage, von Robert Mecklenburg (O’Reilly).
Boost.Build wurde von denselben Programmierern entwickelt wie auch die Boost C++ Libraries. Es wird schon seit mehreren Jahren von einer großen Entwickler-Community verwendet und wird aktiv weiterentwickelt. Boost.Build verwendet ein Build Tool namens bjam und Textdateien, die als Jamfiles bezeichnet werden. Seine größte Stärke ist die Einfachheit, mit der es Sie auch komplexe Projekte verwalten lässt, die auf mehreren 4 | Kapitel 1: C++-Programme kompilieren
Plattformen kompilieren sollen und mehrere Build-Konfigurationen haben. Obwohl Boost.Build anfangs nur eine Erweiterung von Perforces Build-System Jam war, wurde es seither einer umfassenden Neukonzeption unterzogen. Während dieses Buch gedruckt wurde, bereiteten die Entwickler von Boost.Build die offizielle Veröffentlichung der zweiten Major-Version des Build-Systems vor, die diesem Kapitel zugrunde liegt.
Die wichtigsten Toolsets In diesem Kapitel behandle ich sieben Sammlungen von Kommandozeilen-Tools: GCC, Visual C++, Intel, Metrowerks, Borland, Comeau und Digital Mars. In Tabelle 1-2 finden Sie die Namen der jeweiligen Kommandozeilen-Tools aus den verschiedenen Toolsets; in Tabelle 1-3 sehen Sie, wo diese Tools nach der Installation auf Ihrem System zu finden sind. Unter Windows haben die Dateinamen der Tools wie alle ausführbaren Dateien die Erweiterung .exe. Bei den Toolsets, die für Windows und Unix verfügbar sind, habe ich die Dateierweiterung in eckige Klammern gesetzt. Tabelle 1-2: Namen der Kommandozeilen-Tools der verschiedenen Toolsets Toolset
Compiler
Linker
Archiver
GCC
g++[.exe]
g++
ar[.exe] ranlib[.exe]
Visual C++
cl.exe
link.exe
lib.exe
Intel (Windows)
icl.exe
xilink.exe
xilib.exe
Intel (Linux)
icpc
icpc
ar ranlib
Metrowerks
mwcc[.exe]
mwld[.exe]
mwld[.exe]
Comeau
como[.exe]
como[.exe]
abhängig vom Toolset
Borland
bcc32.exe
bcc32.exe ilink32.exe
tlib.exe
Digital Mars
dmc.exe
link.exe
lib.exe
Tabelle 1-3: Verzeichnispfade der Kommandozeilen-Tools Toolset
a
Speicherort
GCC (Unix)
Normalerweise /usr/bin oder /usr/local/bin
GCC (Cygwin)
Das Unterverzeichnis bin in Ihrem Cygwin-Verzeichnis
GCC (MinGW)
Das Unterverzeichnis bin in Ihrem MinGW-Verzeichnis
Visual C++
Das Unterverzeichnis VC/bin in Ihrem Visual Studio-Verzeichnisa
Intel (Windows)
Das Unterverzeichnis Bin in Ihrem Intel-Compiler-Verzeichnis
Intel (Linux)
Das Unterverzeichnis bin in Ihrem Intel-Compiler-Verzeichnis
Metrowerks
Das Unterverzeichnis Other Metrowerks Tools/Command Line Tools in Ihrem CodeWarrior-Verzeichnis
Comeau
Das Unterverzeichnis bin in Ihrem Comeau-Verzeichnis
Borland
Das Unterverzeichnis Bin im Verzeichnis von C++Builder, C++BuilderX oder Borland Command-Line Tools
In früheren Versionen von Visual Studio hatte das Verzeichnis VC den Namen VC98 oder Vc7.
Einführung | 5
Lassen Sie sich von der Zahl der verschiedenen Toolsets nicht abschrecken – Sie müssen nicht mit allen umgehen können. In den meisten Fällen können Sie einfach alles überspringen, das sich nicht mit Ihrem Toolset beschäftigt. Wenn Sie jedoch etwas über andere Toolsets erfahren möchten, sollten Sie zumindest die Abschnitte über Visual C++ und GCC lesen, da dies die dominanten Toolsets unter Windows und Unix sind. Lassen Sie uns nun jedes dieser sieben Toolsets genauer ansehen.
Die GNU Compiler Collection (GCC) GCC ist eine Sammlung von Compilern für eine breite Auswahl von Programmiersprachen, zu denen auch C und C++ gehören. Sehr bemerkenswert ist, dass dieses Toolset Open Source und auf fast jeder Plattform verfügbar ist und sich in hohem Maße an den C++-Standard hält. Es ist der marktbeherrschende Compiler auf vielen Unix-Plattformen und ist selbst unter Microsoft Windows häufig anzutreffen. Selbst wenn GCC nicht Ihr primäres Toolset ist, können Sie dennoch eine Menge lernen, wenn Sie Ihren Code mit GCC kompilieren. Wenn Sie darüber hinaus meinen, eine Idee zu haben, wie man die Programmiersprache C++ verbessern könnte, können Sie Ihre Idee mit Hilfe des frei verfügbaren GCC-Quellcodes testen. GCC wird mit libstdc++, einer guten Open Source-Implementierung der Standardbibliothek von C++, ausgeliefert. Stattdessen kann man aber auch die Open Source-Standardbibliothek STLPort oder die Standardbibliothek von Dinkumware zusammen mit GCC verwenden. Wie Sie GCC bekommen, erfahren Sie in Rezept 1.1.
Alle GCC-Beispiele in diesem Kapitel wurden mit GCC 3.4.3 und GCC 4.0.0 unter GNU/Linux (Fedora Core 3) sowie mit GCC 4.0.0 unter Mac OS X (Darwin 8.2.0) und mit GCC 3.4.2 (MinGW) und 3.4.4 (Cygwin) unter Windows 2000 Professional getestet.
Visual C++ Microsofts Toolset ist auf der Windows-Plattform das marktbeherrschende Toolset. Zwar werden weiterhin verschiedene alte Versionen verwendet, die aktuellste Version ist aber immer hochgradig standardkonform. Außerdem kann dieser Compiler hochoptimierten Code generieren. Microsofts Tools werden mit den Entwicklungsumgebungen Visual C++ und Visual Studio ausgeliefert, die im nächsten Abschnitt behandelt werden. Zu dem Zeitpunkt, als dieses Buch geschrieben wurde, waren diese Tools auch als Teil des Visual C++ Toolkit 2003 verfügbar, das kostenlos auf www.microsoft.com heruntergeladen werden kann.
6 | Kapitel 1: C++-Programme kompilieren
Visual C++ wird mit einer angepassten Version von Dinkumwares Implementierung der C++-Standardbibliothek ausgeliefert. Dinkumwares C++-Standardbibliothek gehört zu den effizientesten und standardkonformsten kommerziellen Implementierungen. Sie ist auf vielen Plattformen verfügbar und kann auch mit vielen der anderen in diesem Kapitel behandelten Toolsets eingesetzt werden. Die Visual C++-Beispiele in diesem Kapitel wurden mit Microsoft Visual Studio .NET 2003 und Microsoft Visual Studio 2005 (Beta 2) getestet. Genaueres dazu finden Sie in Tabelle 1-4. Tabelle 1-4: Versionen von Microsoft Visual Studio Produktname
IDE-Version
Compiler-Version
Microsoft Visual Studio
6.0
1200
Microsoft Visual Studio .NET
7.0
1300
Microsoft Visual Studio .NET 2003
7.1
1310
Microsoft Visual Studio 2005 (Beta 2)
8.0
1400
Intel Intel vertreibt mehrere C++-Compiler, die speziell für Intel-Prozessoren entwickelt werden. Das Bemerkenswerte an ihnen ist der extrem schnelle Code, den sie erzeugen – vielleicht der schnellste, den überhaupt ein Compiler für die Intel-Architektur erzeugt. Diese Compiler basieren auf dem C++-Frontend der Edison Design Group (EDG) und sind ebenfalls hochgradig standardkonform. Unter Windows integriert sich der Intel C++-Compiler in die Entwicklungsumgebung Microsoft Visual C++ oder Visual Studio, die installiert sein muss, damit der Compiler wie vorgesehen funktioniert. Der Compiler ist auf größtmögliche Kompatibilität mit Visual C++ ausgelegt. So kann er als Plug-In in die Entwicklungsumgebung von Visual C++ integriert werden. Er generiert Code, der binärkompatibel mit dem vom Visual C++-Compiler generierten Code ist, er bietet viele der Kommandozeilen-Parameter des Visual C++-Compilers an, und er emuliert – sofern Sie ihm nichts anderes sagen – sogar einige der Bugs des Microsoft-Compilers. Die kommerzielle Version von Intels C++Compiler für Windows kann unter www.intel.com erworben werden. Eine preiswerte akademische Version ist ebenfalls verfügbar. Während sich Intels Compiler unter Windows um Kompatibilität mit dem Visual C++Compiler bemüht, bemüht er sich unter Linux um Kompatibilität mit GCC. Er setzt einen installierten GCC voraus, unterstützt einige von GCCs Kommandozeilen-Parametern und implementiert darüber hinaus auch einige von GCCs Spracherweiterungen. Die kommerzielle Version von Intels C++-Compiler für Linux kann unter www.intel.com erworben werden. Eine nicht-kommerzielle Version steht zum kostenlosen Download bereit. Unter Windows verwendet der Intel-Compiler die Dinkumware Standardbibliothek, die auch mit Visual C++ ausgeliefert wird. Unter Linux verwendet er dagegen die libstdc++.
Einführung | 7
Die Intel-Beispiele in diesem Kapitel wurden mit dem Intel C++-Compiler 9.0 für Linux unter GNU/Linux (Fedora Core 3) und mit dem Intel C++Compiler 9.0 für Windows unter Windows 2000 Professional getestet.
Metrowerks Metrowerks’ Kommandozeilen-Tools, die mit der Entwicklungsumgebung CodeWarrior ausgeliefert werden, gehören sowohl in puncto Standardkonformität, als auch was die Effizienz des erzeugten Codes angeht, mit zu den besten verfügbaren Tools. Sie werden zusammen mit MSL, Metrowerks’ erstklassiger Implementierung der C++-Standardbibliothek, ausgeliefert. Bis vor kurzem hat Metrowerks Tools für Windows, Mac OS und verschiedene Embedded-Plattformen vertrieben. Im Jahr 2004 hat Metrowerks jedoch seine Intel x86-Compiler- und -Debugger-Technologie an Nokia verkauft und die CodeWarrior-Produktfamilie für Windows eingestellt. Als Apple Computer 2005 ankündigte, in Zukunft auf Intel-Chips umzustellen, gab Metrowerks bekannt, dass die kommende Version 10 von CodeWarrior für Mac OS wahrscheinlich das letzte große Release für diese Plattform sein wird. In Zukunft wird sich Metrowerks auf die Entwicklung von Werkzeugen für Embedded-Systeme mit Chips von Freescale Semiconductor konzentrieren. Wenn Sie das hier lesen, wird Metrowerks bereits ein Teil von Freescale Semiconductor sein, und der Name Metrowerks wird nicht länger mit der CodeWarrior-Produktlinie assoziiert werden. Ich verwende hier aber trotzdem den Namen Metrowerks, da noch nicht klar ist, wie diese Produkte in Zukunft heißen werden.
Die Metrowerks-Beispiele in diesem Kapitel wurden mit CodeWarrior 9.6 und 10.0 (Beta) unter Mac OS X (Darwin 8.2.0) und mit CodeWarrior 9.4 unter Windows 2000 Professional getestet.
Borland Borlands Kommandozeilen-Tools hatten einstmals einen ziemlich guten Ruf. Im September 2005 war das letzte große Update aber nun schon mehr als drei Jahre alt, und das Update brachte auch damals schon nur eher kleinere Verbesserungen gegenüber der Vorversion, die im Jahr 2000 veröffentlicht wurde. Deshalb sind Borlands Tools heutzutage schon ziemlich angestaubt. Im Jahr 2003 kündigte Borland an, seinen C++-Compiler unter Verwendung des EGD-Frontends einem ambitionierten Redesign zu unterziehen; leider ist es nun schon länger her, dass Borland sich zu diesem Vorhaben das letzte Mal geäußert hat. Nichtsdestotrotz sind Borlands Kommandozeilen-Tools auch heute noch wichtig, da sie nach wie vor noch häufig im Einsatz sind. Die aktuellsten Versionen von Borlands Kommandozeilen-Tools bekommen Sie, indem Sie die Entwicklungsumgebungen C++Builder oder C++BuilderX kaufen, die im nächs-
8 | Kapitel 1: C++-Programme kompilieren
ten Abschnitt beschrieben werden, oder indem Sie die kostenlose Personal Edition von C++BuilderX herunterladen. Borland liefert sein Toolset mit zwei C++-Standard-Bibliotheken aus: mit STLPort und mit einer veralteten Version von Rogue Waves Standardbibliothek. Borland arbeitet ebenfalls an einer Version seiner Tools, die mit der Standardbibliothek von Dinkumware ausgeliefert werden soll. Die Borland-Beispiele in diesem Kapitel wurden mit Borland C++ Builder 6.0 (Compiler-Version 5.6.4) unter Windows 2000 Professional getestet.
Comeau Der Comeau C++-Compiler wird weithin als der standardkonformste C++-Compiler angesehen. Zusätzlich zur aktuellsten Version von C++ unterstützt dieser Compiler auch mehrere Versionen von C und ein paar frühe Dialekte von C++. Außerdem gehört dieser Compiler auch zu den günstigsten Produkten und kostet momentan nur $50. Wie der Intel-Compiler so verwendet auch Comeau das EDG-Frontend und benötigt einen separaten C-Compiler. Im Gegensatz zu Intel kann Comeau viele verschiedene CCompiler als Backends verwenden. Comeau ist für Microsoft Windows und für viele Unix-Plattformen verfügbar. Falls Comeau auf Ihrer Plattform nicht verfügbar ist, können Sie die Firma Comeau Computing auch dafür bezahlen, dass sie ihren Compiler eigens für Sie portiert. Allerdings wird das Ganze dann wesentlich teurer. Sie können den Comeau-Compiler unter www. comeaucomputing.com bestellen. Wenn ich Comeau für Unix bespreche, gehe ich davon aus, dass als Backend GCC verwendet wird. Bei Comeau für Windows versuche ich dagegen, die unterschiedlichen Kommandozeilen-Parameter für möglichst viele Backends mit anzugeben. Da Comeau aber mit so vielen verschiedenen Backends eingesetzt werden kann, kann ich nicht immer die Kommandozeilen-Parameter für alle möglichen Backends erschöpfend behandeln.
Comeau wird mit libcomo geliefert, einer auf der Standardbibliothek von Silicon Graphics basierenden Implementierung der Standardbibliothek. Allerdings kann der Compiler auch zusammen mit Dinkumwares Standardbibliothek eingesetzt werden. Die Comeau-Beispiele in diesem Kapitel gehen davon aus, dass Sie libcomo verwenden und der Compiler so konfiguriert ist, dass er libcomo automatisch findet. Die Beispiele wurden mit Comeau 4.3.3 und libcomo 31 und mit GCC 3.4.3 als Backend unter GNU/Linux (Fedora Core 3) und mit Visual C++ .NET 2003 als Backend unter Windows 2000 Professional getestet (siehe Tabelle 1-4).
Einführung | 9
Digital Mars Digital Mars ist ein C++-Compiler, der von Walter Bright geschrieben wurde. Unter www.digitalmars.com können Sie diesen Compiler kostenlos herunterladen, und gegen eine geringe Gebühr können Sie eine CD bestellen, auf der der Digital Mars-Compiler, eine IDE und ein paar andere nützliche Tools enthalten sind. Mit der kostenlosen Version des Compilers können Sie alle Digital Mars-Beispiele in diesem Kapitel kompilieren. Davon ausgenommen sind nur jene Beispiele, die eine dynamische Version der Runtime Library benötigen, da diese nur auf der CD enthalten ist. Bei Digital Mars handelt es sich um einen sehr schnellen Compiler, der hochoptimierten Code erzeugt. Leider hat er momentan ein paar Probleme beim Kompilieren von Code, der fortgeschrittene Templates verwendet. Glücklicherweise reagiert Walter Bright sehr schnell auf Bug-Reports und ist bestrebt, Digital Mars standardkonform zu machen. Digital Mars wird mit zwei Standardbibliotheken geliefert: mit einer Portierung der STLPort-Standardbibliothek und mit einer älteren Standardbibliothek, die nicht standardkonform und unvollständig ist. Aus Gründen der Abwärtskompatibilität muss STLPort vom Benutzer explizit aktiviert werden. Alle Digital Mars-Beispiele in diesem Kapitel verwenden STLPort als Standardbibliothek. Die Digital Mars-Beispiele in diesem Kapitel wurden mit Digital Mars 8.45 unter Windows 2000 Professional getestet.
Die wichtigsten IDEs In diesem Kapitel werde ich vier IDEs behandeln: Microsoft Visual C++, Metrowerks CodeWarrior, Borland C++Builder und Dev-C++ von Bloodshed Software. Es gibt auch ein paar wichtige IDEs, die ich hier nicht behandle – Apples Xcode und das Eclipse Project sind hierfür prominente Beispiele –, allerdings sollten Sie durch die Informationen, die Sie hier zu diesen vier IDEs finden, genug erfahren, um auch die Verwendung einer beliebigen andern IDE schnell erlernen zu können. Wie schon bei den Kommandozeilen-Tools so können Sie auch hier die Abschnitte übergehen, die sich nicht mit Ihrer IDE beschäftigen.
Visual C++ Microsoft Visual C++ ist die marktbeherrschende C++-Entwicklungsumgebung für Microsoft Windows. Es ist als eigenständige Applikation oder als Teil der Visual Studio Suite verfügbar und wird mit einer großen Auswahl an Tools für die Windows-Entwicklung ausgeliefert. Für die Entwicklung portabler C++-Anwendungen sind vor allem folgende Features interessant:
10 | Kapitel 1: C++-Programme kompilieren
• Ein hochgradig standardkonformer C++-Compiler • Die Dinkumware C++-Standardbibliothek • Ein guter visueller Debugger • Ein Projekt-Manager, der die Abhängigkeiten zwischen unterschiedlichen Projekten automatisch überwacht Heutzutage sind gleich mehrere verschiedene Versionen von Visual Studio im Einsatz. Da die Namen dieser Versionen oft Verwirrung stiften, habe ich die wichtigsten Versionen in Tabelle 1-4 aufgelistet. Die erste Version von Visual C++, die einen erstklassigen C++-Compiler und eine erstklassige Standardbibliothek enthalten hat, wird in der dritten Zeile von Tabelle 1-4 aufgeführt. Alle vorangegangenen Versionen hatten ernsthafte Probleme, dem Sprachstandard von C++ zu genügen.
CodeWarrior CodeWarrior ist Metrowerks’ plattformunabhängige Entwicklungsumgebung. Sie hat viele der Features, die auch von Visual C++ unterstützt werden, wie z.B.: • Einen sehr standardkonformen C++-Compiler • Eine ausgezeichnete C++-Standardbibliothek • Einen guten visuellen Debugger • Einen Projekt-Manager, der die Abhängigkeiten zwischen unterschiedlichen Projekten automatisch überwacht Eine von CodeWarriors Stärken liegt traditionell in der großen Zahl unterstützter Plattformen, für die diese IDE verfügbar ist. Allerdings habe ich im letzten Abschnitt bereits erwähnt, dass die Produktlinie für Windows eingestellt wurde und die Produktlinie für Macintosh wahrscheinlich bald ebenfalls eingestellt wird. Im Bereich der Embedded-Entwicklung wird CodeWarrior wohl aber auch in Zukunft ein wichtiges Produkt bleiben. Wenn ich hier die CodeWarrior-IDE bespreche, dann gehe ich davon aus, dass Sie CodeWarrior 10 für Mac OS X verwenden. Die Versionen der CodeWarrior-IDE für andere Plattformen sind allerdings sehr ähnlich.
C++Builder C++Builder ist Borlands Entwicklungsumgebung für Windows-Anwendungen. Eines der interessantesten Features dieser IDE ist die Unterstützung für Borlands Visual Component Library. Was die Entwicklung portabler C++-Anwendungen angeht, so sind jedoch folgende Eigenschaften dieser IDE am wichtigsten: • Ein alter C++-Compiler • Die Standardbibliothek STLPort
Einführung
| 11
• Ein guter visueller Debugger • Ein Projekt-Manager, der nur eingeschränkt in der Lage ist, Abhängigkeiten zwischen verschiedenen Projekten zu verwalten Ich behandle den C++Builder hier deshalb, weil er von vielen Entwicklern verwendet wird und über eine aktive Community von Usern verfügt. C++Builder sollte nicht mit C++BuilderX, einer 2003 von Borland veröffentlichten Cross Plattform-Entwicklungsumgebung, verwechselt werden. Obwohl es sich bei C++BuilderX um ein nützliches Entwicklungs-Tool handelt, war ihm bisher kein kommerzieller Erfolg beschieden, und es ist unklar, ob Borland je eine neue Version veröffentlichen wird.
Dev-C++ Dev-C++ von Bloodshed Software ist eine freie C++-Entwicklungsumgebung für Windows, die den in Rezept 1.1 behandelten MinGW-Port von GCC verwendet. Diese IDE besitzt einen ziemlich guten Texteditor und eine grafische Oberfläche für den GNUDebugger. Dev-C++ bietet leider nur eine unvollständige grafische Oberfläche für GCCs zahlreiche Kommandozeilen-Parameter, so dass man ein Projekt häufig konfigurieren muss, indem man die entsprechenden Kommandozeilen-Parameter in Textfelder einträgt. Außerdem kann der Projekt-Manager immer nur mit einem Projekt arbeiten, und der visuelle Debugger ist unzuverlässig. Trotz dieser Einschränkungen besitzt Dev-C++ eine aktive Community von Usern, zu der auch viele Studenten gehören. Es ist eine gute Entwicklungsumgebung für jemanden, der C++ lernen möchte und noch keine EntwicklungsTools besitzt.
John, Paul, George und Ringo Seitdem Brian Kernighan und Dennis Ritchie 1978 ihr Buch The C Programming Language veröffentlicht haben, ist es unter Programmierern zu einer Art Tradition geworden, beim Lernen einer neuen Programmiersprache zuerst ein simples Programm zu schreiben und auszuführen, das die Worte »Hallo Welt!« auf die Konsole ausgibt. Da in diesem Kapitel nicht nur ausführbare Programme, sondern auch statische und dynamische Bibliotheken behandelt werden, benötigen wir ein etwas komplexeres Beispielprogramm. In den Beispielen 1-1, 1-2 und 1-3 sehen Sie den Quellcode für das Programm hellobeatles, das den Text John, Paul, George und Ringo
auf die Konsole ausgibt. Für dieses Programm hätte auch eine einzige Quelldatei genügt, aber ich habe es in diese drei Module aufgeteilt: eine statische Bibliothek libjohnpaul, eine dynamische Bibliothek libgeorgeringo und ein ausführbares Programm namens hellobeatles. Des Weiteren habe ich die Implementierung auf mehrere Quelldateien verteilt, obwohl jede Bibliothek durch eine Headerdatei und eine .cpp-Datei hätte realisiert 12 | Kapitel 1: C++-Programme kompilieren
werden können. Das habe ich getan, um zu zeigen, wie man Projekte kompiliert und linkt, die mehr als eine Quelldatei enthalten. Bevor Sie sich durch die Rezepte in diesem Kapitel hindurcharbeiten, sollten Sie zuerst in einem Verzeichnis die Unterverzeichnisse johnpaul, georgeringo, hellobeatles und binaries anlegen. In den ersten drei Verzeichnissen legen Sie die Quelldateien aus den Beispielen 1-1, 1-2 und 1-3 ab. Das vierte Verzeichnis verwenden Sie, um darin die von den IDEs erzeugten Binärdateien abzulegen.
Der Quellcode für libjohnpaul ist in Beispiel 1-1 abgedruckt. Die öffentliche Schnittstelle von libjohnpaul besteht lediglich aus der Funktion johnpaul( ), die in der Headerdatei johnpaul.hpp deklariert wird. Die Funktion johnpaul( ) gibt den Text John, Paul
auf die Konsole aus. Die Implementierung von johnpaul( ) ist auf die zwei Quelldateien john.cpp und paul.cpp verteilt, wobei in jeder dieser Quelldateien jeweils ein Name ausgegeben wird. Beispiel 1-1: Quellcode von libjohnpaul johnpaul/john.hpp #ifndef JOHN_HPP_INCLUDED #define JOHN_HPP_INCLUDED void john( ); // Gibt "John, " aus #endif // JOHN_HPP_INCLUDED johnpaul/john.cpp #include #include "john.hpp" void john( ) { std::cout bcc32 -c -q -WR -o ringo.obj ringo.cpp ringo.cpp: > bcc32 -c -q -WR -DGERORGERINGO_DLL -o georgeringo.obj georgeringo.cpp georgeringo.cpp:
Die Compiler-Option -WR weist den Compiler an, eine dynamische Version der Laufzeitbibliothek zu verwenden. Durch die Ausführung dieser drei Befehle werden die Objektdateien george.obj, ringo.obj und georgeringo.obj erzeugt. Anschließend müssen Sie diesen Befehl eingeben: > bcc32 -q -WD -WR -elibgeorgeringo.dll george.obj ringo.obj georgeringo.obj
Dadurch wird die dynamische Bibliothek libgeorgeringo.dll erstellt. Schließlich müssen Sie noch diesen Befehl eingeben: > implib –c libgeorgeringo.lib libgeorgeringo.dll
Dadurch wird die Import-Bibliothek libgeorgeringo.lib erzeugt.
1.4 Eine dynamische Bibliothek von der Kommandozeile aus kompilieren
| 29
Diskussion Die Art und Weise, wie mit dynamischen Bibliotheken umgegangen wird, hängt sehr stark vom verwendeten Betriebssystem und vom eingesetzten Toolset ab. Aus Sicht des Programmierers sind hierbei die folgenden beiden Unterschiede am wichtigsten:
Sichtbarkeit von Symbolen Dynamische Bibliotheken können die Definitionen von Klassen, Funktionen und Daten enthalten. Auf manchen Plattformen sind solche Symbole für den Code, der die dynamische Bibliothek verwendet, automatisch sichtbar; andere Systeme ermöglichen es dem Programmierer dagegen, genau festzulegen, auf welche Symbole die Benutzer der Bibliothek zugreifen dürfen und auf welche nicht. Wenn man von Fall zu Fall entscheiden kann, welche Symbole sichtbar sind, ist das erst mal sehr vorteilhaft, da es dem Programmierer die volle Kontrolle über die öffentliche Schnittstelle seiner Bibliothek gibt und oftmals auch zu einer besseren Performance führt. Leider wird dadurch die Erstellung und Verwendung dynamischer Bibliotheken aber auch aufwändiger. Bei den meisten Windows-Toolsets muss jedes in einer dynamischen Bibliothek definierte Symbol beim Kompilieren der Bibliothek explizit exportiert und beim Kompilieren des Codes, der die Bibliothek verwenden soll, importiert werden. Andernfalls ist das Symbol für den aufrufenden Code nicht sichtbar. Auch unter Unix existieren ein paar Toolsets, die dieselbe Flexibilität anbieten. Zu diesen Toolsets gehören neuere Versionen von GCC, die dieses Feature auf mehreren Plattformen anbieten, Metrowerks für Mac OS X und Intel für Linux. In manchen Fällen gibt es dennoch keine andere Möglichkeit, als alle Symbol sichtbar zu machen.
Bibliotheken an den Linker übergeben Unter Unix können dynamische Bibliotheken dem Linker als Eingabe übergeben werden, wenn Code gelinkt werden soll, der diese dynamischen Bibliotheken verwendet. Unter Windows werden dynamische Bibliotheken bei allen Toolsets außer GCC nicht direkt dem Linker übergeben; stattdessen erhält der Linker eine Import-Bibliothek oder eine Moduldefinitionsdatei.
Import-Bibliotheken und Moduldefinitionsdateien Bei Import-Bibliotheken handelt es sich vereinfacht gesagt um statische Bibliotheken, die die Informationen enthalten, die benötigt werden, um Funktionen einer DLL zur Laufzeit aufzurufen. Man muss nicht wissen, wie sie genau funktionieren, sondern nur, wie man sie erstellt und verwendet. Die meisten Linker erzeugen beim Erstellen einer DLL automatisch eine passende Import-Bibliothek, allerdings ist es in manchen Fällen doch notwendig, ein separates und als Import Librarian bezeichnetes Tool zu verwenden. In Tabelle 1-11 habe ich Borlands Import Librarian implib.exe verwendet, um Ihnen die eigentümliche Aufrufsyntax von Borlands Linker ilink32.exe zu ersparen.
30 | Kapitel 1: C++-Programme kompilieren
Eine Moduldefinitionsdatei, auch .def-Datei genannt, ist eine Textdatei, in der die von einer DLL exportierten Funktionen und Daten angegeben werden. Man kann eine .defDatei entweder von Hand schreiben oder von einem Tool automatisch generieren lassen. Ein Beispiel einer .def-Datei sehen Sie in Beispiel 1-5, wo eine solche Datei für die Bibliothek libgeorgeringo.dll abgedruckt ist. Beispiel 1-5: Eine Moduldefinitionsdatei für libgeorgeringo.dll LIBRARY
LIBGEORGERINGO.DLL
EXPORTS Georgeringo
@1
Symbole einer DLL exportieren Es gibt zwei Standardvorgehensweisen, um die Symbole einer DLL zu exportieren: • Verwenden Sie in den Headerdateien der DLL das Attribut _ _declspec(dllexport), und erstellen Sie eine Import-Bibliothek, die verwendet wird, um Code zu kompilieren, der auf Ihre DLL zugreift. Das Attribut _ _declspec(dllexport) sollte vor der Deklaration zu exportierender Funktionen und Daten stehen, sollte nach Anweisungen für den Linker stehen, und im Falle zu exportierender Klassen sollte diese Anweisung immer direkt nach dem Schlüsselwort class oder struct stehen. In Beispiel 1-6 sehen Sie, wie eine solche Headerdatei aussehen könnte. Beachten Sie, dass _ _declspec(dllexport) nicht Teil der Programmiersprache C++ und nur eine Spracherweiterung ist, die von den meisten Windows-Compilern unterstützt wird. • Erzeugen Sie eine .def-Datei, in der alle Funktionen und Daten aufgeführt werden, die Sie exportieren möchten. Beispiel 1-6: Verwendung des Attributs _ _declspec(dllexport) _ _declpec(dllexport) int m = 3; extern _ _declpec(dllexport) int n; _ _declpec(dllexport) void f( ); class _ _declpec(dllexport) c { /* ... */ };
// // // //
Exportierte Exportierte Exportierte Exportierte
Datendefinition Datendeklaration Funktionsdeklaration Klassendefinition
Die Verwendung einer .def-Datei bringt mehrere Vorteile. So ermöglicht sie z.B., dass auf Funktionen einer DLL über eine Nummer und nicht über den Funktionsnamen zugegriffen wird, wodurch sich die Größe der DLL verringert. Außerdem erspart man sich dadurch die Verwendung hässlicher Präprozessor-Direktiven wie in der Headerdatei georgeringo.hpp aus Beispiel 1-2. Leider hat diese Methode aber auch ein paar große Nachteile. So kann man mit einer .def-Datei beispielsweise keine Klassen exportieren. Des Weiteren kann man auch leicht vergessen die .def-Datei zu aktualisieren, wenn man Funktionen zur DLL hinzufügt, sie aus dieser entfernt oder bereits existierende Funktio-
1.4 Eine dynamische Bibliothek von der Kommandozeile aus kompilieren
| 31
nen verändert. Deshalb empfehle ich Ihnen, immer das Attribut _ _declspec(dllexport) zu verwenden. Wenn Sie mehr über die Syntax von .def-Dateien oder über ihre Verwendung erfahren möchten, sollten Sie dazu die Dokumentation Ihres Toolsets konsultieren.
Symbole einer DLL importieren So wie es zwei Möglichkeiten gibt, um die Symbole einer DLL zu exportieren, so gibt es auch zwei Möglichkeiten, um Symbole zu importieren: • Verwenden Sie in den Headerdateien des Codes, der die DLL verwendet, das Attribut _ _declspec(dllimport), und übergeben Sie dem Linker beim Linken dieses Codes eine entsprechende Import-Bibliothek. • Geben Sie beim Linken von Code, der Ihre DLL verwendet, eine entsprechende .defDatei an. Wie schon beim Exportieren von Symbolen so empfehle ich Ihnen auch hier, in Ihrem Code anstelle von .def-Dateien das Attribut _ _decl-spec(dllimport) zu verwenden. Das Attribut _ _decl-spec(dllimport) wird genauso wie das zuvor behandelte Attribut _ _declspec(dllexport) verwendet. Wie _ _declspec(dllexport) so ist auch _ _declspec(dllimport) kein Bestandteil der Programmiersprache C++ und nur eine Spracherweiterung, die von den meisten Windows-Compilern unterstützt wird. Falls Sie __declspec(dllexport) und __declspec(dllimport) verwenden möchten, müssen Sie sicherstellen, dass Sie _ _declspec(dllexport) zum Erstellen Ihrer DLL verwenden und _ _declspec(dllimport) zum Kompilieren von Code, der Ihre DLL benutzt. Ein Lösungsansatz wäre, wenn Sie zwei Sätze von Headerdateien verwenden: einen zum Kompilieren der DLL und einen anderen zum Kompilieren von Code, der Ihre DLL verwendet. Allerdings ist das keine wirklich zufrieden stellende Lösung, da es recht schwierig ist, zwei separate Versionen derselben Headerdateien zu pflegen. Stattdessen definiert man in der Regel ein Makro, das entweder zu __declspec(dllexport) oder zu _ _declspec(dllimport) expandiert, je nachdem, ob die DLL gerade kompiliert oder benutzt wird. In Beispiel 1-2 habe ich dazu das Makro GEORGERINGO_DECL verwendet. Unter Windows expandiert GEORGERINGO_DECL, falls das Makro GEORGERING_SOURCE definiert ist, zu _ _declspec(dllexport) und andernfalls zu _ _declspec(dllimport). Dies erreicht man ganz einfach dadurch, dass man GEORGERING_SOURCE beim Kompilieren der DLL libgeorgeringo.dll definiert, es aber undefiniert lässt, wenn man Code kompiliert, der libgeorgeringo.dll verwendet.
DLLs mit GCC erstellen Die Cygwin- und MinGW-Ports von GCC, die ich in Recipe 1.1 behandelt habe, verfahren mit DLLs anders als andere Windows-Toolsets. Wenn Sie mit GCC eine DLL kompilieren, werden alle Funktionen, Klassen und Daten standardmäßig exportiert. Dieses Verhalten können Sie entweder dadurch ändern, dass Sie dem Linker den Parameter --no-export-all-symbols übergeben, oder indem Sie das Attribut _ _declspec-(dllexport)
32 | Kapitel 1: C++-Programme kompilieren
in Ihren Quelldateien verwenden, oder indem Sie eine .def-Datei verwenden. In jedem dieser drei Fälle werden – sofern Sie den Linker nicht mit dem Parameter --export-allsymbols zwingen, alle Symbole zu exportieren – nur die Funktionen, Klassen und Daten exportiert, die per _ _decl-spec(dllexport) oder über die .def-Datei dafür explizit vorgesehen sind. Es ist deshalb möglich, DLLs mit GCC auf zwei verschiedene Arten zu kompilieren: zum einen wie mit einem gewöhnlichen Windows-Toolset, so dass Symbole per _ _declspec explizit exportiert werden müssen, oder wie mit einem Unix-Toolset, wo alle Symbole automatisch exportiert werden.1 Ich habe in Beispiel 1-2 und Tabelle 1-11 die letztere Methode gewählt. Wenn Sie diese Methode verwenden, sollten Sie sicherheitshalber den Parameter --export-all-symbols verwenden, da Sie unwissentlich Headerdateien einbinden könnten, in denen das Attribut _ _declspec(dllexport) vorkommt. GCC unterscheidet sich noch in einem anderen Punkt von den klassischen WindowsToolsets: Anstatt dem Linker die Import-Bibliothek für eine DLL zu übergeben, können Sie ihm auch die DLL selbst übergeben. Diese Methode ist für gewöhnlich schneller als die Verwendung einer Import-Bibliothek. Allerdings kann das auch zu Problemen führen, da sich mehrere Versionen derselben DLL auf Ihrem System befinden können und Sie deshalb sicherstellen müssen, dass der Linker die richtige Version verwendet. In Tabelle 1-11 habe ich auf die Verwendung dieses Features verzichtet, da ich zeigen wollte, wie man mit GCC Import-Bibliotheken erzeugt. Bei Cygwin hat eine Import-Bibliothek für eine DLL xxx.dll für gewöhnlich den Namen xxx.dll.a, während sie bei MinGW normalerweise xxx.a heißen würde. Dies liegt aber nur daran, dass beide Projekte eine andere Namenskonvention verwenden.
Die Option -fvisibility bei GCC 4.0 Neuere Versionen von GCC bieten dem Programmierer unter mehreren Plattformen, darunter auch Linux und Mac OS X, die genaue Kontrolle darüber, welche Symbole einer dynamischen Bibliothek exportiert werden sollen: Der Kommandozeilen-Parameter -fvisibility kann verwendet werden, um festzulegen, ob die Symbole einer dynamischen Bibliothek standardmäßig sichtbar sein sollen oder nicht. Außerdem kann im Quellcode eine spezielle Attributsyntax, ähnlich der Syntax mit _ _declspec(dllexport) unter Windows, verwendet werden, um die Sichtbarkeit eines Symbols explizit festzulegen. Der Parameter -fvisibility hat mehrere mögliche Werte, die wichtigsten sind aber default und hidden. Vereinfacht gesagt bedeutet die Sichtbarkeit default, dass Code aus anderen Modulen auf das jeweilige Symbol zugreifen kann; hidden bedeutet dagegen, dass fremder Code auf dieses Symbol nicht zugreifen kann. Damit Symbole nur exportiert werden, wenn Sie das explizit festgelegt haben, müssen Sie auf der Komman-
1 Verwirrenderweise wird der Export mittels _ _declspec(dllexport) manchmal als impliziter Export bezeichnet.
1.4 Eine dynamische Bibliothek von der Kommandozeile aus kompilieren
| 33
dozeile -fvisibility=hidden angeben und im Code das Attribut visibility verwenden, um die zu exportierenden Symbole so, wie in Beispiel 1-7 gezeigt, zu kennzeichnen. Beispiel 1-7: Gemeinsame Verwendung des visibility-Attributs mit dem Kommandozeilen-Parameter -fvisibility=hidden extern _ _attribute_ _((visibility("default"))) int m; extern int n;
// wird exportiert // wird nicht exportiert
_ _attribute_ _((visibility("default"))) void f( ); void g( );
// wird exportiert // wird nicht exportiert
struct _ _attribute_ _((visibility("default"))) S { }; struct T { };
// wird exportiert // wird nicht exportiert
In Beispiel 1-7 kommt dem Attribut _ _attribute_ _((visibility("default"))) dieselbe Rolle zu wie dem Attribut _ _declspec(dllexport) in Windows-Code. Die Verwendung des visibility-Attributs bringt dieselben Schwierigkeiten wie die Verwendung der Attribute _ _decl-spec(dllexport) und _ _declspec(dllimport) mit sich, da das Attribut nur dann sichtbar sein soll, wenn die Shared Library kompiliert wird, nicht aber, wenn Code kompiliert wird, der diese Shared Library verwendet. Außerdem soll es auf Plattformen, auf denen es nicht unterstützt wird, versteckt werden. Wie schon bei _ _declspec(dllexport) und _ _declspec(dllimport), so kann auch dieses Problem mit Hilfe des Präprozessors gelöst werden. Beispielsweise könnten Sie, um das visibilityAttribut zu verwenden, die Headerdatei georgeringo.hpp aus Beispiel 1-2 wie folgt abändern: georgeringo/georgeringo.hpp #ifndef GEORGERINGO_HPP_INCLUDED #define GEORGERINGO_HPP_INCLUDED // definiere GEORGERINGO_DLL, wenn libgeorgeringo kompiliert wird # if defined(_WIN32) && !defined(__GNUC__) # ifdef GEORGERINGO_DLL # define GEORGERINGO_DECL _ _declspec(dllexport) # else # define GEORGERINGO_DECL _ _declspec(dllimport) # endif # else // Unix # if defined(GEORGERINGO_DLL) && defined(HAS_GCC_VISIBILITY) # define GEORGERINGO_DECL _ _attribute_ _((visibility("default"))) # else # define GEORGERINGO_DECL # endif # endif // Gibt "George und Ringo\n" aus GEORGERINGO_DECL void georgeringo( ); #endif // GEORGERINGO_HPP_INCLUDED
34 | Kapitel 1: C++-Programme kompilieren
Damit dieser Code funktioniert, müssen Sie das Makro HAS_GCC_VISIBILITY definieren, wenn Sie die Bibliothek auf einem System kompilieren, das den Kommandozeilen-Parameter -fvisibility unterstützt. Neuere Versionen des Intel-Compilers für Linux bieten ebenfalls Unterstützung für den Kommandozeilen-Parameter -fvisibility.
Sichtbarkeit von Symbolen unter Metrowerks für Mac OS X Metrowerks für Mac OS X bietet mehrere Möglichkeiten, um die Symbole einer dynamischen Bibliothek zu exportieren. Wenn Sie die CodeWarrior-IDE verwenden, können Sie eine Symbolexport-Datei verwenden, die in ihrer Funktion den unter Windows verwendeten .def-Dateien ähnelt. Sie können sich auch dazu entschließen, einfach alle Symbole exportieren zu lassen. Das erreichen Sie über den Kommandozeilen-Parameter -export all, der beim Kompilieren auf der Kommandozeile standardmäßig eingestellt ist. Ich empfehle Ihnen aber, alle zu exportierenden Symbole in Ihrem Code durch die Anweisung #pragma export auszuzeichnen und beim Kompilieren Ihrer dynamischen Bibliothek auf der Kommandozeile den Parameter -export pragma anzugeben. Wie man #export pragma verwendet, sehen Sie in Beispiel 1-2: Vor jeder Gruppe zu exportierender Funktionen notieren Sie in Ihrer Headerdatei einfach die Anweisung #pragma export on, und gleich am Ende dieser Gruppe schließen Sie diese Anweisung mit #export pragma off wieder ab. Wenn Sie erreichen möchten, dass Ihr Code auch mit anderen Toolsets als dem von Metrowerks kompiliert werden kann, sollten Sie alle Vorkommen von #pragma export, wie in Beispiel 1-2 gezeigt, mit den Anweisungen #ifdef und #endif umschließen.
Kommandozeilen-Parameter Lassen Sie uns nun einen kurzen Blick auf die in Tabelle 1-11 verwendeten Kommandozeilen-Parameter werfen. Jede Befehlszeile enthält folgende Informationen: • Den Namen der Eingabedateien: george.obj, ringo.obj und georgeringo.obj • Den Namen der dynamischen Bibliothek, die erstellt werden soll • Unter Windows zusätzlich noch den Namen der Import-Bibliothek Zusätzlich muss dem Linker noch mitgeteilt werden, dass er eine dynamische Bibliothek und keine ausführbare Programmdatei generieren soll. Die meisten Linker verwenden hierfür den Parameter -shared. Visual C++ und Intel für Windows verwenden stattdessen aber -dll, Borland und Digital Mars verwenden -WD, und GGC für Mac OS X verwendet -dynamiclib. Mehrere der in Tabelle 1-11 verwendeten Kommandozeilen-Parameter tragen dazu bei, dass die erstellten dynamischen Bibliotheken zur Laufzeit effizienter verwendet werden. So sollte man beispielsweise manche Unix-Linker mit dem Parameter -fPIC (GCC und Intel für Linux) anweisen, so genannten Position-Independent Code zu generieren. Durch diesen Parameter wird es wahrscheinlicher, dass mehrere Prozesse dieselbe Kopie einer 1.4 Eine dynamische Bibliothek von der Kommandozeile aus kompilieren
| 35
dynamischen Bibliothek verwenden können. Auf manchen Systemen meldet der Linker sogar einen Fehler, wenn dieser Parameter nicht angegeben wird. Ähnliches bewirkt unter Windows der Parameter --enable-auto-image-base für den Linker des GCC, durch den es weniger wahrscheinlich wird, dass das Betriebssystem versucht, zwei dynamische Bibliotheken an dieselbe Adresse zu laden, wodurch das Laden von DLLs beschleunigt wird. Sie können dem Linker des GCC auch über den Compiler Kommandozeilen-Parameter zukommen lassen, indem Sie dem Compiler g++ den Parameter -Wl, übergeben. (Der Buchstabe nach dem W ist ein kleines l.)
Die meisten der noch nicht angesprochenen Kommandozeilen-Parameter werden verwendet, um (wie in Rezept 1.23 beschrieben) verschiedene Varianten der Laufzeitbibliothek festzulegen.
Siehe auch Rezept 1.9, 1.12, 1.17, 1.19 und 1.23
1.5
Eine komplexe Anwendung von der Kommandozeile aus kompilieren
Problem Sie möchten mit Hilfe Ihrer Kommandozeilen-Tools eine ausführbare Programmdatei generieren, die von mehreren statischen und dynamischen Bibliotheken abhängt.
Lösung Generieren Sie zuerst die statischen und dynamischen Bibliotheken, die von Ihrer Anwendung benötigt werden. Falls diese Bibliotheken von Dritten stammen, sollten Sie die beigefügten Instruktionen befolgen; andernfalls kompilieren Sie die Bibliotheken wie in Rezept 1.3 und 1.4 beschrieben. Als Nächstes übersetzen Sie die .cpp-Dateien Ihrer Anwendung wie in Rezept 1.2 beschrieben in Objektdateien. Eventuell benötigen Sie hierbei den Parameter -I, um dem Compiler, wie in Tabelle 1-12 gezeigt, mitzuteilen, wo er nach den Headerdateien Ihrer Anwendung suchen muss. Tabelle 1-12: Angabe der Suchpfade für Headerdateien Toolset
Parameter
Alle
-I
36 | Kapitel 1: C++-Programme kompilieren
Schließlich verwenden Sie den Linker, um aus diesen Objektdateien und Bibliotheken eine ausführbare Programmdatei zu erzeugen. Für jede Bibliothek müssen Sie dem Linker entweder den absoluten Dateinamen oder einen Suchpfad angeben. Falls Ihr Toolset statische und dynamische Versionen seiner Laufzeitbibliotheken anbietet und Ihr Programm mindestens eine dynamische Bibliothek verwendet, sollten Sie den Compiler bzw. Linker bei jedem Aufruf (wie in Rezept 1.23 beschrieben) anweisen, eine dynamisch gelinkte Version der Laufzeitbibliothek zu verwenden. In Tabelle 1-13 sehen Sie die Befehle zum Linken der Anwendung hellobeatles aus Beispiel 1-3. Folgende Voraussetzungen müssen erfüllt sein, damit diese Befehle funktionieren: • Das Verzeichnis hellobeatles muss das aktuelle Arbeitsverzeichnis sein. • Die statische Bibliothek libjohnpaul.lib bzw. libjohnpaul.a wurde bereits erstellt und befindet sich im Verzeichnis johnpaul. • Die dynamische Bibliothek georgeringo.dll bzw. georgeringo.so oder georgeringo. dylib und, falls nötig, auch ihre Import-Bibliothek, wurden erstellt und befinden sich im Verzeichnis georgeringo. Da Comeau, wie bereits in Rezept 1.4 erwähnt wurde, keine dynamischen Bibliotheken erstellen kann, erwarten die Befehle für Comeau in Tabelle 1-13, dass libgeorgeringo als statische und nicht als dynamische Bibliothek kompiliert worden ist. Damit libgeorgeringo als statische Bibliothek kompiliert wird, müssen Sie die Anweisung GEORGERINGO_DECL vor der Deklaration der Funktion georgeringo( ) in Beispiel 1-2 entfernen. Tabelle 1-13: Befehle zum Linken des Programms hellobeatle.exe Toolset
Eingabedateien
Befehlszeile
GCC (Unix)
hellobeatles.o libjohnpaul.a libgeorgeringo.so
g++ -o hellobeatles hellobeatles.o -L../johnpaul -L../georgeringo -ljohnpaul -lgeorgeringo oder g++ -o hellobeatles hellobeatles.o ../johnpaul/libjohnpaul.a ../georgeringo/libgeorgeringo.so
Intel (Linux)
icpc -o hellobeatles hellobeatles.o -L../johnpaul -L../georgeringo -ljohnpaul -lgeorgeringo oder icpc -o hellobeatles hellobeatles.o ../johnpaul/libjohnpaul.a ../georgeringo/libgeorgeringo.so
Comeau (Unix)
como --no_prelink_verbose -o hellobeatles hellobeatles.o -L../johnpaul -L../georgeringo -ljohnpaul -lgeorgeringo oder como --no_prelink_verbose -o hellobeatles hellobeatles.o ../johnpaul/libjohnpaul.a ../georgeringo/libgeorgeringo.a
1.5 Eine komplexe Anwendung von der Kommandozeile aus kompilieren | 37
Tabelle 1-13: Befehle zum Linken des Programms hellobeatle.exe (Fortsetzung) Toolset
Eingabedateien
Befehlszeile
GCC (Mac OS X)
hellobeatles.o libjohnpaul.a libgeorgeringo.dylib
g++ -o hellobeatles hellobeatles.o -L../johnpaul -L../georgeringo -ljohnpaul -lgeorgeringo oder g++ -o hellobeatles hellobeatles.o ../johnpaul/libjohnpaul.a ../georgeringo/libgeorgeringo.dylib
Metrowerks (Mac OS X)
mwld -o hellobeatles hellobeatles.o -search -L../johnpaul -search -L../georgeringo -ljohnpaul -lgeorgeringo oder mwld -o hellobeatles hellobeatles.o ../johnpaul/libjohnpaul.a ../georgeringo/libgeorgering.dylib
GCC (Cygwin)
hellobeatles.o libjohnpaul.a libgeorgeringo.dll.a
g++ -o hellobeatles hellobeatles.o -L../johnpaul -L../georgeringo -ljohnpaul -lgeorgeringo oder g++ -o hellobeatles hellobeatles.o ../johnpaul/libjohnpaul.a ../georgeringo/libgeorgeringo.dll.a
GCC (MinGW)
hellobeatles.o libjohnpaul.a libgeorgeringo.a
g++ -o hellobeatles hellobeatles.o -L../johnpaul -L../georgeringo -ljohnpaul -lgeorgeringo oder g++ --o hellobeatles hellobeatles.o ../johnpaul/libjohnpaul.a ../georgeringo/libgeorgeringo.a
Visual C++
hellobeatles.obj libjohnpaul.lib libgeorgeringo.lib
link -nologo -out:hellobeatles.exe -libpath:../johnpaul -libpath:../georgeringo libjohnpaul.lib libgeorgeringo.lib hellobeatles.obj
Intel (Windows)
xilink -nologo -out:hellobeatles-libpath:../johnpaul -libpath:../georgeringo libjohnpaul.lib libgeorgeringo.lib hellobeatles.obj
Metrowerks (Windows)
mwld-o hellobeatles -search -L../johnpaul libjohnpaul.lib -search -L../georgeringo libgeorgeringo.lib hellobeatles.obj
Metrowerks (Mac OS X)a
mwld -o hellobeatles hellobeatles.o -search -L../johnpaul -search -L../georgeringo libjohnpaul.a libgeorgeringo.dylib
CodeWarrior 10.0 (Mac OS X)b
Konsultieren Sie die Metrowerks-Dokumentation.
Borland
bcc32 -q -WR -WC -ehellobeatles -L.../johnpaul -L.../georgeringo libjohnpaul.lib libgeorgeringo.lib hellobeatles.obj
Digital Mars
link -noi hellobeatles.obj,hellobeatles.exe,NUL,user32.lib kernel32.lib .. \johnpaul\ ..\georgeringo\ libjohnpaul.lib libgeorgeringo.lib,, oder link -noi hellobeatles.obj,hellobeatles.exe,NUL,user32.lib kernel32.lib .. \johnpaul\libjohnpaul.lib ..\georgeringo\libgeorgeringo.lib,,
38 | Kapitel 1: C++-Programme kompilieren
Tabelle 1-13: Befehle zum Linken des Programms hellobeatle.exe (Fortsetzung)
a b
Toolset
Eingabedateien
Befehlszeile
Comeau (Windows)
hellobeatles.obj libjohnpaul.lib libgeorgeringo.lib
como --no_prelink_verbose -o hellobeatles ../johnpaul/ libjohnpaul.lib ../ georgeringo/libgeorgeringo.lib hellobeatles.obj
Möglicherweise wird sich das Programm hellobeatles nicht ausführen lassen, wenn es mit der angegebenen Befehlszeile kompiliert wird, da das Programm zwei Kopien von Metrowerks’ statischen Laufzeitbibliotheken verwenden wird (siehe Rezept 1.23). CodeWarrior 10.0 für Mac OS X wird dynamische Versionen seiner Laufzeitbibliotheken bereitstellen. Zum Kompilieren von hellobeatles sollten Sie auf diese dynamischen Bibliotheken zurückgreifen (siehe Rezept 1.23).
Wenn Sie Microsoft Visual Studio .NET 2003 verwenden und es in das Standardverzeichnis auf Laufwerk C installiert haben, können Sie das Programm hellobeatles.exe von der Kommandozeile aus kompilieren, indem Sie in das Verzeichnis hellobeatles wechseln und die folgenden Befehle ausführen: > "C:\Programme\Microsoft Visual Studio .NET 2003\VC\bin\ vcvars32.bat" Setting environment for using Microsoft Visual Studio 2005 tools. (If you have another version of Visual Studio or Visual C++ installed and wish to use its tools from the command line, run vsvars32.bat for that version.) > cl -c -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t -MD -I.. -Fohellobeatles hellobeatles.cpp hellobeatles.cpp > link -nologo -out:hellobeatles.exe -libpath:../johnpaul -libpath:../georgeringo libjohnpaul.lib libgeorgeringo.lib hellobeatles.obj
Diskussion Die Suche nach eingebundenen Headerdateien Der Parameter -I wird verwendet, um einen Include-Pfad anzugeben. Wenn der Compiler – genauer gesagt der Präprozessor – eine include-Anweisung wie #include "file"
findet, versucht er für gewöhnlich zuerst die Datei zu finden, indem er den angegebenen relativen Pfad im selben Verzeichnis sucht, in dem sich auch die gerade verarbeitete Quelldatei befindet. Falls er sie dort nicht findet, sucht er sie als Nächstes in den Verzeichnissen, die mit Hilfe des Parameters -I als Include-Pfade angegeben wurden. Führt auch das nicht zum Erfolg, dann sucht er in den entsprechenden Spezialverzeichnissen des Toolsets, die oftmals über Umgebungsvariablen umkonfiguriert werden können. Das Ganze läuft ganz ähnlich ab, wenn die einzubindende Headerdatei in spitzen Klammern angegeben wird: #include
1.5 Eine komplexe Anwendung von der Kommandozeile aus kompilieren | 39
Der einzige Unterschied ist, dass der Compiler in diesem Fall für gewöhnlich nicht im Verzeichnis der Quelldatei nach der Headerdatei sucht.
Bibliotheken an den Linker übergeben Es gibt gleich mehrere interessante Aspekte an den Befehlszeilen in Tabelle 1-13. Unter Windows erhält der Linker als Eingabe Objektdateien, statische Bibliotheken und Import-Bibliotheken, während ihm unter Unix Objektdateien sowie statische und dynamische Bibliotheken übergeben werden. Unter Windows wie auch unter Unix können dem Linker die Bibliotheken auf zwei Arten übergeben werden: • Entweder durch Angabe des Dateinamens auf der Kommandozeile • Oder durch Angabe eines einfachen Namens und eines Verzeichnisses, in dem nach der Bibliothek gesucht werden soll In Tabelle 1-13 werden die Befehle für beide Arten aufgeführt. Der Suchpfad für die Bibliotheken kann für gewöhnlich auf der Kommandozeile angegeben werden. Die meisten Linker verwenden hierfür die Syntax -L; Visual C++ und Intel für Windows verwenden stattdessen aber -lipath: , und Metrowerks verwendet die Syntax -search -L. Beim Linker von Digital Mars kann man Suchpfade für Bibliotheken und Dateinamen von Bibliotheken auf der Kommandozeile gemeinsam angeben, wobei sich Suchpfade durch abschließende Backslashes von Bibliotheken unterscheiden; außerdem müssen auch einzelne Suchpfade durch Backslashes voneinander getrennt werden. Comeau bietet unter Windows keinen Parameter an, um einen Suchpfad für Bibliotheken anzugeben.
Zusätzlich zu den explizit angegebenen Suchpfaden durchsuchen Linker für gewöhnlich auch vom jeweiligen Toolset abhängige Verzeichnisse, die oftmals über Umgebungsvariablen umkonfiguriert werden können. Unter Windows gehört zu diesen Verzeichnissen meist auch das Unterverzeichnis lib des Verzeichnisses, in dem das Toolset installiert ist. Deshalb können Sie Bibliotheken, wenn Sie die entsprechenden .lib-Dateien in dieses Verzeichnis kopiert haben, auf der Kommandozeile direkt über ihren Namen ansprechen, ohne einen Suchpfad angeben zu müssen. Wenn Sie diese Methode mit der in Rezept 1.25 vorgestellten Technik kombinieren, können Sie es gänzlich vermeiden, dem Linker überhaupt Informationen zu einer bestimmten Bibliothek zu übergeben. Der Name einer Bibliothek wird unter Unix und Windows auf unterschiedliche Weise angegeben. Unter Windows muss der vollständige Dateiname der Bibliothek inklusive Dateierweiterung angegeben werden. Unter Unix – und mit GCC für Windows – werden Bibliotheken mit Hilfe des Parameters -l angegeben, gefolgt vom Namen der Bibliothek
40 | Kapitel 1: C++-Programme kompilieren
ohne Dateierweiterung und ohne das Präfix lib. Das bedeutet, dass der Name einer Bibliothek mit lib beginnen muss, damit sie vom Linker automatisch gefunden wird. Am wichtigsten ist aber, dass dies dem Linker ermöglicht, zwischen verschiedenen Versionen einer Bibliothek zu wählen. Falls der Linker eine statische und eine dynamische Version einer Bibliothek findet, wird automatisch die dynamische Version verwendet, außer dem Linker wurde explizit gesagt, dass er die statische Version verwenden soll. Unter manchen Systemen kann der Linker auch zwischen mehreren Versionen einer dynamischen Bibliothek wählen, wobei die gewählte Bibliothek davon abhängt, was im Dateinamen auf den Text .so folgt. Metrowerks unterstützt sowohl die von Windows als auch die von Unix verwendete Methode zur Angabe von Bibliotheken.
Schließlich sollten Sie noch wissen, dass Unix-Linker in Bezug auf die Reihenfolge, in der Objektdateien und statische Bibliotheken auf der Kommandozeile angegeben werden, sehr sensibel sein können: Falls eine statische Bibliothek oder eine Objektdatei ein Symbol referenziert, das in einer anderen statischen Bibliothek oder Objektdatei enthalten ist, muss die erste Datei auf der Kommandozeile vor der zweiten Datei angegeben werden. Manchmal ist es auch notwendig, eine Bibliothek oder eine Objektdatei mehrmals anzugeben, um zyklische Abhängigkeiten zu vermeiden. Alternativ kann man dieses Problem auch lösen, indem man eine Reihe von Objektdateien und statischen Bibliotheken umklammert von den Zeichenfolgen -( und -) an den Linker übergibt. Dadurch erreicht man, dass der Linker diese Dateien so oft wiederholt durchsucht, bis alle Referenzen aufgelöst sind. Diese Lösung sollte man aber nach Möglichkeit vermeiden, da sich die Performance dadurch merklich verschlechtert.
Das fertige Programm ausführen Falls Ihr Programm eine dynamische Version der Laufzeitbibliothek Ihres Toolsets verwendet, muss sich diese Laufzeitbibliothek bei der Ausführung Ihres Programms in einem Verzeichnis befinden, in dem sie vom Ladeprogramm des Betriebssystems automatisch gefunden wird. Für gewöhnlich bedeutet das, dass sich diese dynamische Laufzeitbibliothek entweder im selben Verzeichnis wie die Programmdatei oder in einem dafür vorgesehenen systemspezifischen Verzeichnis befinden muss. Dies ist eher ein Problem, wenn Sie für Windows entwickeln, als wenn Sie für Unix entwickeln, da diese Laufzeitbibliotheken unter Unix oftmals bereits in den richtigen Verzeichnissen installiert sind. Die Namen der dynamischen Laufzeitbibliotheken, die mit den verschiedenen Toolsets ausgeliefert werden, finden Sie in Rezept 1.23.
Siehe auch Rezept 1.10, 1.13, 1.18 und 1.23
1.5 Eine komplexe Anwendung von der Kommandozeile aus kompilieren | 41
1.6
Boost.Build installieren
Problem Sie möchten Boost.Build beschaffen und installieren.
Lösung Konsultieren Sie die Dokumentation von Boost.Build, die Sie unter www.boost.org/boostbuild2 finden können, oder führen Sie die folgenden Schritte durch: 1. Besuchen Sie die Boost-Homepage unter www.boost.org, und gehen Sie über den Link »Download« auf Boosts Download-Seite bei SourceForge. 2. Laden Sie nun entweder das neuste Release des Pakets boost oder das neuste Release von boost-build herunter, und entpacken Sie es anschließend. Das erstere Paket enthält sämtliche Boost-Bibliotheken, während im letzteren nur Boost.Build enthalten ist. Suchen Sie sich für die entpackten Dateien ein sinnvolles Installationsverzeichnis. 3. Laden Sie die aktuellste Version von boost-jam für Ihre Plattform herunter, und entpacken Sie das Archiv anschließend; dieses Archiv enthält ein ausführbares Programm bjam. Falls boost-jam für Ihre Plattform nicht verfügbar ist, müssen Sie es selbst kompilieren. Die dazu nötigen Anweisungen finden Sie in dem Paket, das Sie in Schritt 2 heruntergeladen haben. 4. Kopieren Sie das Programm bjam in ein Verzeichnis, das in der Umgebungsvariable PATH eingetragen ist. 5. Setzen Sie die Umgebungsvariable BOOST_BUILD_PATH dauerhaft auf das Wurzelverzeichnis von Boost.Build. Falls Sie in Schritt 1 das Paket boost heruntergeladen haben, ist dies das Unterverzeichnis tools/build/v2 im Installationsverzeichnis von Boost. Andernfalls ist es das Verzeichnis boost-build. 6. Konfigurieren Sie Boost.Build für Ihre Toolsets und Ihre Bibliotheken, indem Sie die Konfigurationsdatei user-config.jam editieren. Sie finden diese Datei im Wurzelverzeichnis von Boost.Build. In der Datei user-config.jam finden Sie Kommentare, die Sie bei der Konfiguration unterstützen.
Diskussion Das Schwierigste bei der Verwendung von Boost.Build sind der Download und die Installation dieses Systems. Möglicherweise wird Boost eines Tages auch ein grafisches Installationsprogramm dafür anbieten; bis dahin gibt es aber keine andere Möglichkeit, als nach der obigen Anleitung zu verfahren. Schritt 5 ist dazu da, um dem Build-Tool bjam zu sagen, wo es das Wurzelverzeichnis des Build-Systems finden kann. Allerdings ist dieser Schritt nicht zwingend erforderlich, da man dasselbe auch auf andere Weise erreichen kann: Legen Sie einfach eine Datei namens boost-build.jam mit dem Inhalt
42 | Kapitel 1: C++-Programme kompilieren
boost-build Wurzelverzeichnis ;
an, und legen Sie diese Datei im Wurzelverzeichnis Ihres Projekts oder in einem seiner übergeordneten Verzeichnisse ab. Die zweite Methode ist vorzuziehen, falls Sie Boost. Build zusammen mit Ihrem Quellcode ausliefern möchten, da dadurch der Installationsvorgang für den Endbenutzer vereinfacht wird. Die Konfiguration in Schritt 6 stellt zwar potenziell den aufwändigsten Schritt dar, in der Regel ist das Ganze aber recht einfach. Wenn Sie nur eine Version Ihres Toolsets installiert haben und das Toolset auch noch im Standardverzeichnis installiert ist, dann muss die Datei user-config.jam nur aus einer einzigen Zeile der Form using ;
bestehen. Wenn Sie beispielsweise Visual C++ verwenden, wird in den meisten Fällen folgende Konfigurationszeile vollauf genügen: using msvc ;
Wenn Sie dagegen GCC verwenden, schreiben Sie stattdessen einfach: using gcc ;
Wenn Sie mehr als ein Toolset installiert haben oder wenn Sie Ihr Toolset in einem anderen als dem Standardverzeichnis installiert haben, wird das Ganze ein wenig komplizierter. Falls sich Ihr Toolset nicht in seinem Standardverzeichnis befindet, müssen Sie Boost.Build die Befehlszeile zum Aufruf des Compilers als drittes Argument von using übergeben. Beispiel: using msvc : : "C:/Tools/Compiler/Visual Studio/Vc7/bin/cl" ;
Wenn Sie mehrere Versionen eines Toolsets installiert haben, können Sie die Anweisung using mehrmals hintereinander aufrufen, wobei Sie als erstes Argument den Namen des Toolsets, als zweites Argument die Versionsnummer und als drittes Argument die Befehlszeile zum Aufruf des Compilers übergeben. Wenn Sie nun beispielsweise zwei Versionen des Intel-Compilers mit Boost.Build verwenden möchten, könnte Ihre Konfiguration wie folgt aussehen: using intel : 7.1 : "C:/Programme/Intel/Compiler70/IA32/Bin/icl" ; using intel : 8.0 : "C:/Programme/Intel/CPP/Compiler80/IA32/Bin/icl" ;
In Tabelle 1-14 sind die Kürzel aufgeführt, die Boost.Build für die sieben in diesem Kapitel behandelten Toolsets verwendet. Tabelle 1-14: Toolset-Kürzel für Boost.Build Toolset
Name
GCC
gcc
Visual C++
msvc
Intel
intel
Metrowerks
cw
1.6 Boost.Build installieren | 43
Tabelle 1-14: Toolset-Kürzel für Boost.Build (Fortsetzung) Toolset
Name
Comeau
como
Borland
borland
Digital Mars
dmc
1.7
Ein einfaches »Hallo Welt«-Programm mit Boost.Build kompilieren
Problem Sie möchten mit Hilfe von Boost.Build ein einfaches »Hallo Welt«-Programm wie das aus Beispiel 1-4 kompilieren.
Lösung Legen Sie in dem Verzeichnis, in dem die ausführbare Programmdatei und alle Zwischendateien angelegt werden sollen, eine Textdatei namens Jamroot an. Diese Datei sollte aus den folgenden beiden Regeln bestehen. Zuerst notieren Sie eine exe-Regel, hinter der Sie den Namen der zu erstellenden ausführbaren Programmdatei und die Quelldateien angeben, aus denen das Programm erstellt wird. Als Nächstes notieren Sie eine install-Regel, hinter der Sie den Namen der Programmdatei und das Verzeichnis angeben, in das diese Datei installiert werden soll. Nun müssen Sie nur noch das Programm bjam ausführen, und Ihr Programm wird kompiliert. Um beispielsweise von der Datei hello.cpp aus Beispiel 1-4 eine Programmdatei hello bzw. hello.exe zu erstellen, legen Sie in dem Verzeichnis, in dem sich die Datei hello.cpp befindet, eine Datei namens Jamroot mit dem in Beispiel 1-8 gezeigten Inhalt an. Beispiel 1-8: Jamfile für das Projekt hello # jamfile for project hello exe hello : hello.cpp ; install dist : hello : . ;
Nun wechseln Sie in das Verzeichnis mit den Dateien hello.cpp und Jamroot und geben folgenden Befehl ein: > bjam hello
Dieser Befehl erstellt in einem Unterverzeichnis des Projektverzeichnisses die Programmdatei hello bzw. hello.exe. Schließlich müssen Sie noch diesen Befehl ausführen: > bjam dist
44 | Kapitel 1: C++-Programme kompilieren
Dadurch wird die Programmdatei in das hinter location angegebene Verzeichnis, in diesem Fall also in das aktuelle Arbeitsverzeichnis, kopiert. Als dieses Buch in den Druck ging, bereiteten die Entwickler von Boost. Build gerade das offizielle Release von Boost.Build Version 2 vor. Wenn Sie das hier lesen, ist die Version 2 wahrscheinlich schon offiziell verfügbar. Falls das nicht der Fall ist, können Sie das hier beschriebene Verhalten erzwingen, indem Sie bjam den Parameter --v2 übergeben. Anstatt beispielsweise bjam hello einzugeben, müssen Sie also bjam --v2 hello eingeben.
Diskussion Bei der Datei Jamroot handelt es sich um ein so genanntes Jamfile. Während sich ein paar C++-Quelldateien noch mit einem einzigen Jamfile verwalten lassen, benötigt man für eine große Codebasis in der Regel viele Jamfiles, die noch dazu hierarchisch organisiert sind. Jedes Jamfile befindet sich dabei in einem eigenen Verzeichnis und entspricht einem eigenen Projekt. Die meisten Jamfiles werden einfach Jamfile genannt. Die Ausnahme bildet dabei das in der Hierarchie ganz oben stehende Jamfile – also das Jamfile, das in dem Verzeichnis liegt, dessen Unterverzeichnisse all die anderen Jamfiles enthalten. Es wird meist unter dem Namen Jamroot abgelegt. Das von diesem in der Hierarchie ganz oben stehenden Jamfile definierte Projekt wird auch als Wurzelprojekt (Project Root) bezeichnet. Außer dem Wurzelprojekt hat jedes Projekt ein Elternprojekt (Parent Project). Dabei handelt es sich um das Projekt, das im nächstgelegenen übergeordneten Verzeichnis, das ein Jamfile enthält, definiert wird. Dieses hierarchische System ist ziemlich mächtig. So ist es dadurch beispielsweise recht einfach möglich, ein neues Requirement (eine Anforderung), wie z.B. die Unterstützung von Threads, an ein Projekt und alle ihm untergeordneten Projekte durchzureichen. Jedes Projekt ist eine Ansammlung von Targets. Targets werden durch den Aufruf von Regeln, wie z.B. der exe-Regel und der install-Regel, deklariert. Die meisten Targets beziehen sich auf eine Binärdatei oder, genauer gesagt, auf eine Ansammlung entsprechender Binärdateien, wie z.B. auf die Debug- und Release-Version eines Programms. Die Regel exe wird verwendet, um ein Target für eine ausführbare Programmdatei zu deklarieren. In Beispiel 1-9 sehen Sie, wie ein Aufruf dieser Regel aussehen muss. Beispiel 1-9: Aufruf der Regel exe exe Target-Name : Quellen : Requirements : Default-Build : Usage-Requirements ;
1.7 Ein einfaches »Hallo Welt«-Programm mit Boost.Build kompilieren | 45
Im Feld Target-Name wird der Name der Programmdatei erwartet, im Feld Quellen wird eine Liste von Quelldateien und Bibliotheken erwartet, und im Feld Requirements können Einstellungen stehen, die für dieses Target unabhängig von allen auf der Befehlszeile angeforderten oder von anderen Projekten geerbten Einstellungen für dieses Feature gelten sollen. Dagegen gelten die Einstellungen im Feld Default-Build nur dann, wenn auf der Kommandozeile für dieses Feature nicht explizit ein anderer Wert angegeben wird. Schließlich können im Feld Usage-Requirements noch Einstellungen angegeben werden, die an alle Targets, die von diesem Target abhängen, durchgereicht werden sollen. Einstellungen werden mit der Syntax Wert festgelegt. Um beispielsweise eine Programmdatei zu deklarieren, die immer mit Thread-Unterstützung kompiliert werden soll, könnten Sie folgende Regel notieren: exe hello : hello.cpp : multi ;
Sie müssen die Doppelpunkte für die nachfolgenden Argumente einer Boost.Build-Regel nur dann angeben, wenn Sie für diese Regeln auch Werte festlegen.
In Tabelle 1-15 finden Sie eine Übersicht über mehrere häufig verwendete Features und die möglichen Werte, die für diese Features angegeben werden können. Tabelle 1-15: Häufig verwendete Features von Boost.Build
a
Feature
Wert
Effekt
include
Pfad
Gibt einen Include-Pfad an.
define
Name[=Wert]
Definiert ein Makro.
threading
multi oder single
Aktiviert oder deaktiviert Threads.
runtime-link
static oder shared
Gibt die Methode zum Linken der Laufzeitbibliothek an.a
variant
debug oder release
Legt fest, ob eine Debug- oder eine Release-Version des Programms erstellt werden soll.
Siehe Rezept 1.23.
Wenn ein ausführbares Target – oder ein Target für eine statische oder dynamische Bibliothek – erstellt wird, wird diese Datei in einem Unterverzeichnis jenes Verzeichnisses erstellt, in dem sich das Jamfile befindet. Der relative Pfad dieses Verzeichnisses hängt vom verwendeten Toolset und von Ihrer Konfiguration ab, allerdings beginnt dieser Pfad immer mit bin. So könnte die Programmdatei für Beispiel 1-8 beispielsweise im Verzeichnis bin/msvc/debug erstellt werden. Der Einfachheit halber habe ich Ihnen empfohlen, das Jamfile für Beispiel 1-8 im selben Verzeichnis wie die Datei hello.cpp abzulegen. In einem richtigen Projekt werden Sie es 46 | Kapitel 1: C++-Programme kompilieren
aber oftmals vorziehen, Ihre Quell- und Binärdateien in unterschiedlichen Verzeichnissen unterzubringen. Das Jamfile für Beispiel 1-8 können Sie prinzipiell überall ablegen, Sie müssen lediglich den Pfad zu hello.cpp so abändern, dass die Datei hello.cpp vom Verzeichnis des Jamfiles ausgehend auch gefunden wird. Die Regel install weist Boost.Build an, eine oder mehrere Dateien – angegeben über Datei- oder Target-Namen – in ein bestimmtes Verzeichnis zu kopieren. Wie diese Regel aufgerufen wird, sehen Sie in Beispiel 1-10. Beispiel 1-10: Aufruf der Regel install install Target-Name : Quelldateien : Requirements : Default-Build : Usage-Requirements ;
Hier ist Target-Name der Name des deklarierten Targets, und Dateien ist eine Liste von Dateien oder Targets, die kopiert werden sollen. Die übrigen Parameter, Requirements, Default-Build und Usage-Requirements, haben dieselbe Bedeutung wie in Beispiel 1-9. Das Verzeichnis, in das die Dateien kopiert werden sollen, kann entweder in Form eines Target-Namens oder als Wert der Einstellung location einer Target-Anforderung angegeben werden. Damit könnte das Target install in Beispiel 1-8 auch so aussehen: install . : hello ;
Die Programmdatei könnte dann mit diesem Befehl installiert werden: > bjam .
Allerdings ist die in Beispiel 1-8 verwendete Methode vorzuziehen, da man sich einen Target-Namen leichter merken kann als einen Verzeichnisnamen. Lassen Sie uns abschließend noch einen kurzen Blick auf das Programm bjam werfen. Um das Target xxx mit dem Standard-Toolset zu erstellen, verwendet man den Befehl: > bjam xxx
Um das Target xxx mit dem Toolset yyy zu erstellen, verwendet man den Befehl: > bjam xxx toolset=yyy
Um das Target xxx mit Version vvv von Toolset yyy zu erstellen, verwendet man den Befehl: > bjam xxx toolset=yyy-vvv
Um auf der Kommandozeile die Standardbibliothek zzz auszuwählen, kann man diese Syntax verwenden: > bjam xxx stdlib=zzz
1.7 Ein einfaches »Hallo Welt«-Programm mit Boost.Build kompilieren | 47
Sie können gleich mehrere Targets auf einmal erstellen, indem Sie die Namen dieser Targets auf der Kommandozeile hintereinander angeben, und Sie können alle Targets eines Projekts erstellen, indem Sie überhaupt kein Target angeben. Somit könnte man das Programm aus Beispiel 1-9 auch kompilieren und installieren, indem man einfach nur diesen Befehl eingibt: > bjam
Wenn Sie alle während des Kompilierens erstellten Dateien inklusive der Programmdatei löschen möchten, geben Sie einfach diesen Befehl ein: > bjam --clean
Eine Einstellung der Form Wert kann auf der Kommandozeile als Feature=Wert übergeben werden.
Siehe auch Rezept 1.2 und 1.15
1.8
Eine statische Bibliothek mit Boost.Build kompilieren
Problem Sie möchten Boost.Build verwenden, um aus ein paar C++-Quelldateien, wie denen aus Beispiel 1-1, eine statische Bibliothek zu kompilieren.
Lösung Legen Sie in dem Verzeichnis, in dem die statische Bibliothek erstellt werden soll, eine Jamroot-Datei an. In der Datei Jamroot rufen Sie dann die Regel lib auf, um ein Target für eine Bibliothek zu deklarieren, und geben dabei Ihre .cpp-Dateien als Quellen und als Requirement die Einstellung static an. Geben Sie zusätzlich noch ein UsageRequirement der Form Pfad an, um das Include-Verzeichnis anzugeben, d.h. das Verzeichnis, in dem sich alle vom Code der Bibliothek eingebundenen Headerdateien befinden. Möglicherweise müssen Sie mehrere Include-Verzeichnisse angeben, damit der Compiler auch wirklich alle Headerdateien finden kann. Nun müssen Sie nur noch das Programm bjam in dem Verzeichnis mit der Datei Jamroot, wie in Rezept 1.7 beschrieben, aufrufen. Um beispielsweise die Quelldateien aus Beispiel 1-1 als statische Bibliothek zu übersetzen, könnte die Datei Jamroot so wie in Beispiel 1-11 aussehen.
48 | Kapitel 1: C++-Programme kompilieren
Beispiel 1-11: Ein Jamfile zum Kompilieren der statischen Bibliothek libjohnpaul.lib bzw. libjohnpaul.a # Jamfile für das Projekt libjohnpaul lib libjohnpaul : # Quelldateien john.cpp paul.cpp johnpaul.cpp : # Requirements static : # Default-Build : # Usage-Requirements .. ;
Die Bibliothek kann mit diesem Befehl kompiliert werden: > bjam libjohnpaul
Diskussion Die Regel lib wird verwendet, um ein Target für eine statische oder dynamische Bibliothek zu deklarieren. Sie erwartet dieselben Parameter wie die Regel exe, deren Aufruf in Beispiel 1-9 demonstriert wurde. Das Usage-Requirement .. sorgt dafür, dass andere Projekte, die Ihre Bibliothek verwenden, die Include-Verzeichnisse Ihrer Bibliothek nicht mehr explizit unter ihren eigenen Requirements angeben müssen. Durch das Requirement static wird festgelegt, dass dieses Target immer als statische Bibliothek kompiliert werden soll. Wenn Sie erreichen möchten, dass eine Bibliothek entweder als statische oder als dynamische Bibliothek kompiliert werden soll, können Sie das Requirement static auch einfach weglassen. Dann kann auf der Kommandozeile oder in den Requirements des Projekts, das Ihre Bibliothek verwendet, festgelegt werden, ob die Bibliothek als statische oder als dynamische Bibliothek kompiliert werden soll. Wenn Sie beispielsweise in Beispiel 1-11 das Requirement static weglassen würden, könnten Sie das Target libjohnpaul durch folgenden Befehl als statische Bibliothek kompilieren: > bjam libjohnpaul link=static
Allerdings ist es nicht ganz einfach, eine Bibliothek zu schreiben, die entweder statisch oder dynamisch kompiliert werden kann. In Rezept 1.9 wird diese Problematik noch etwas genauer angesprochen.
Siehe auch Rezept 1.3, 1.11 und 1.16
1.8 Eine statische Bibliothek mit Boost.Build kompilieren | 49
1.9
Eine dynamische Bibliothek mit Boost.Build kompilieren
Problem Sie möchten Boost.Build verwenden, um aus ein paar C++-Quelldateien wie denen in Beispiel 1-2 eine dynamische Bibliothek zu kompilieren.
Lösung Legen Sie in dem Verzeichnis, in dem die dynamische Bibliothek – und die Import-Bibliothek, falls es eine gibt – erstellt werden soll, eine Jamroot-Datei an. In der Datei Jamroot rufen Sie die Regel lib auf, um ein Target für eine Bibliothek zu deklarieren, und geben die .cpp-Dateien als Quellen und die Einstellung shared als Requirement an. Zusätzlich geben Sie noch ein Usage-Requirement der Form Pfad an, um das Include-Verzeichnis anzugeben, d.h. das Verzeichnis, in dem sich alle vom Code der Bibliothek eingebundenen Headerdateien befinden. Falls Ihre Quelldateien die Headerdateien anderer Bibliotheken einbinden, müssen Sie eventuell mehrere derartige Requirements angeben, damit der Compiler alle Headerdateien findet. Möglicherweise müssen Sie auch ein oder mehrere Requirements der Form Symbol angeben, damit die Symbole Ihrer dynamischen Bibliothek unter Windows mit Hilfe der Anweisung _ _ declspec(dllexport) exportiert werden. Nun müssen Sie nur noch das Programm bjam in dem Verzeichnis mit der Datei Jamroot, wie in Rezept 1.7 beschrieben, aufrufen. Um beispielsweise die Quelldateien aus Beispiel 1-2 als dynamische Bibliothek zu übersetzen, könnte die Datei Jamroot im Verzeichnis georgeringo so wie in Beispiel 1-12 aussehen. Beispiel 1-12: Ein Jamfile zum Übersetzen der dynamischen Bibliothek georgeringo.so bzw. georgeringo. dll oder georgeringo.dylib # Jamfile für das Projekt georgeringo lib libgeorgeringo : # Quelldateien george.cpp ringo.cpp georgeringo.cpp : # Requirements shared GEORGERINGO_DLL : # Default-Build : # Usage-Requirements .. ;
Die Bibliothek kann mit diesem Befehl kompiliert werden: > bjam libgeorgeringo
50 | Kapitel 1: C++-Programme kompilieren
Diskussion Wie in Rezept 1.8 schon gesagt wurde, wird die Regel lib verwendet, um ein Target zu deklarieren, das eine statische oder dynamische Bibliothek repräsentiert. Das UsageRequirement .. sorgt dafür, dass andere Projekte, die Ihre Bibliothek verwenden, die Include-Verzeichnisse Ihrer Bibliothek nicht mehr explizit unter Ihren eigenen Requirements angeben müssen. Durch das Requirement shared wird festgelegt, dass dieses Target immer als dynamische Bibliothek kompiliert werden soll. Wenn Sie erreichen möchten, dass eine Bibliothek entweder als statische oder dynamische Bibliothek kompiliert werden soll, können Sie das Requirement shared auch einfach weglassen. Dann kann auf der Kommandozeile oder in den Requirements des Projekts, das Ihre Bibliothek verwendet, festgelegt werden, ob die Bibliothek als statische oder dynamische Bibliothek kompiliert werden soll. Man muss aber eine gewisse Sorgfalt walten lassen, wenn man eine Bibliothek schreibt, die sich als statische wie auch als dynamische Bibliothek übersetzen lassen soll, da dann bestimmte Präprozessor-Direktiven eingesetzt werden müssen, um sicherzustellen, dass die Symbole unter Windows richtig exportiert werden. Wenn Sie das Ganze einmal üben möchten, empfehle ich Ihnen, Beispiel 1-2 so umzuschreiben, dass der Code wahlweise als statische oder als dynamische Bibliothek übersetzt werden kann.
Siehe auch Rezept 1.4, 1.12, 1.17 und 1.19
1.10 Ein komplexes Programm mit Boost.Build kompilieren Problem Sie möchten mit Boost.Build ein Programm erstellen, das von mehreren statischen und dynamischen Bibliotheken abhängt.
Lösung Führen Sie nacheinander diese Schritte durch: 1. Für jede Bibliothek, von der das Programm abhängt – und die nicht als fertige Binärdatei vorliegt –, erzeugen Sie ein Jamfile nach der Anleitung in den Rezepten 1.8 und 1.9. 2. Legen Sie in dem Verzeichnis, in dem die Programmdatei erstellt werden soll, eine Jamroot-Datei an. 3. In der Datei Jamroot rufen Sie die Regel exe auf, um ein Target für eine ausführbare Programmdatei zu deklarieren. Geben Sie die .cpp-Dateien und die Targets der Bibli-
1.10 Ein komplexes Programm mit Boost.Build kompilieren | 51
otheken als Quellen an. Wenn nötig geben Sie auch noch Anweisungen der Form Pfad als Quellen an, damit der Compiler weiß, wo er die Headerdateien der Bibliotheken finden kann. 4. Rufen Sie in der Datei Jamroot die Regel install auf, und geben Sie als Requirements die Anweisungen on, EXE und SHARED_LIB an. 5. Rufen Sie, wie in Rezept 1.7 beschrieben, das Programm bjam im Verzeichnis mit der Datei Jamroot auf. Um nun die Quelldateien aus Beispiel 1-3 in ein ausführbares Programm zu übersetzen, würde die Datei Jamroot im Verzeichnis hellobeatles so wie in Beispiel 1-13 aussehen. Beispiel 1-13: Ein Jamfile zum Kompilieren des Programms hellobeatles.exe bzw. hellobeatles # Jamfile für das Projekt hellobeatles exe hellobeatles : # Quellen ../johnpaul//libjohnpaul ../georgeringo//libgeorgeringo hellobeatles.cpp ; install dist : # Quellen hellobeatles : # Requirements on EXE SHARED_LIB . ;
Geben Sie nun im Verzeichnis hellobeatles den Befehl > bjam hellobeatles
ein. Dadurch werden zuerst die beiden Projekte, von denen hellobeatles abhängt, und und anschließend das Target hellobeatles kompiliert. Geben Sie nun noch den Befehl > bjam dist
ein. Dadurch werden die Programmdatei hellobeatles und die dynamische Bibliothek georgeringo in das Verzeichnis mit der Datei hellobeatles.cpp kompiliert. Wie schon in Rezept 1.5 gesagt wurde, müssen Sie eventuell erst die dynamische Laufzeitbibliothek Ihres Toolsets in ein Verzeichnis kopieren, in dem es vom Betriebssystem gefunden wird, bevor Sie das Programm hellobeatles ausführen können.
52 | Kapitel 1: C++-Programme kompilieren
Diskussion Library Targets Die Library Targets, von denen ein Target abhängt, werden als Quellen in der Form Pfad//Target-Name angegeben. In Rezept 1.8 und 1.9 habe ich Ihnen gezeigt, wie man ein Target für eine Bibliothek deklariert, die mit Hilfe von Boost.Build aus dem Quellcode kompiliert werden soll. Falls eine Bibliothek jedoch als vorkompilierte Binärdatei vorliegt, können Sie dazu wie folgt ein Target deklarieren: lib Target-Name : : Dateiname ;
Wie ich bereits in Rezept 1.7 erwähnt habe, beziehen sich die meisten Haupt-Targets nicht auf einzelne Dateien, sondern auf mehrere zusammengehörende Dateien, wie z.B. die Debug- und Release-Version einer ausführbaren Programmdatei. Um ein Target für eine vorkompilierte Bibliothek, von der mehrere Varianten vorhanden sind, zu deklarieren, können Sie folgende Notation verwenden: lib Target-Name : : Dateiname Requirements ; lib Target-Name : : anderer-Dateiname andere-Requirements ;
Die Debug- und Release-Versionen einer vorkompilierten Bibliothek könnten beispielsweise wie folgt deklariert werden: lib cryptolib : : ../libraries/cryptolib/cryptolib_debug.lib debug ; lib cryptolib : : ../libraries/cryptolib/cryptolib.lib release ;
Falls eine vorkompilierte Bibliothek in einem der Verzeichnisse abgelegt wurde, die der Linker automatisch durchsucht (siehe Rezept 1.5), können Sie ein Target für diese Bibliothek wie folgt deklarieren: lib Target-Name : : Bibliotheksname ;
1.10 Ein komplexes Programm mit Boost.Build kompilieren | 53
Der Platzhalter Bibliotheksname steht hier stellvertretend für den Namen, der dem Linker übergeben werden muss. Wie bereits in Rezept 1.5 gesagt wurde, muss dieser Name nicht unbedingt mit dem tatsächlichen Dateinamen der Bibliothek übereinstimmen. Wenn Sie den Linker anweisen möchten, in einem bestimmten Verzeichnis nach der Bibliothek zu suchen, können Sie das Target wie folgt deklarieren: lib Target-Name : : Bibliotheksname Bibliothekspfad ;
Installation. Eine komplexe Anwendung muss möglicherweise gemeinsam mit einer bestimmten Anzahl zusätzlicher Programmdateien und dynamischer Bibliotheken, von denen sie abhängt, installiert werden. Anstatt alle diese Dateien einzeln anzugeben, können Sie auch einfach das Feature install-dependencies verwenden, wodurch Sie nur das in der Abhängigkeitshierarchie ganz oben stehende exe-Target und die Art der zu installierenden Abhängigkeiten angeben müssen. In Beispiel 1-13 wird durch das Requirement on das Feature install-dependencies aktiviert, während die Requirements EXE und SHARED_LIB Boost.Build dazu veranlassen, alle Abhängigkeiten zu installieren, bei denen es sich um ausführbare Programmdateien oder Shared Libraries handelt. Zwei andere mögliche Werte für install-type sind LIB und IMPORT_LIB. Projektorganisation. Alle drei Jamfiles, die für das Kompilieren von hellobeatles benötigt werden, tragen den Namen Jamroot. Während das in einem solch einfachen Beispiel noch problemlos funktioniert, ist es im Allgemeinen besser, wenn man eine Sammlung von Jamfiles hierarchisch organisiert, wobei ein einziges, allen anderen Jamfiles übergeordnetes Jamfile das Wurzelprojekt definiert. Wenn Sie Ihre Projekte auf diese Weise organisieren, können Sie auch einige von Boost.Builds fortgeschritteneren Features einsetzen, wie z.B. die Vererbung von Einstellungen an Kindprojekt. Eine Möglichkeit, um das Projekt derart umzuorganisieren, besteht darin, die Jamfiles in den Verzeichnissen johnpaul, georgeringo und hellobeatles von Jamroot in Jamfile umzubenennen und im übergeordneten Verzeichnis eine Datei Jamroot mit folgendem Inhalt zu platzieren: # Jamfile für ein Beispielprogramm build-project hellobeatles
;
Die Regel build-project weist bjam einfach an, ein bestimmtes Projekt, das entweder über den Pfadnamen oder über einen symbolischen Namen festgelegt wird, zu kompilieren. Wenn Sie nun ins Verzeichnis mit der Datei Jamroot wechseln und bjam ausführen, werden die drei Kindprojekte kompiliert.
Siehe auch Rezept 1.5, 1.13 und 1.18
54 | Kapitel 1: C++-Programme kompilieren
1.11 Eine statische Bibliothek mit einer IDE kompilieren Problem Sie möchten Ihre IDE verwenden, um aus ein paar C++-Quelldateien, wie denen in Beispiel 1-1, eine statische Bibliothek zu erstellen.
Lösung Dazu müssen Sie allgemein wie folgt vorgehen: 1. Legen Sie ein neues Projekt an, und geben Sie dabei an, dass Sie eine statische Bibliothek und weder ein ausführbares Programm noch eine dynamische Bibliothek erstellen möchten. 2. Entscheiden Sie, wie die Bibliothek kompiliert werden soll (also z.B. Debug- oder Release-Build, Singlethreaded oder Multithreaded). 3. Geben Sie den Namen Ihrer Bibliothek und das Verzeichnis an, in dem sie erstellt werden soll. 4. Fügen Sie dem Projekt Ihre Quelldateien hinzu. 5. Geben Sie falls nötig ein oder mehrere Verzeichnisse an, in denen der Compiler nach eingebundenen Headerdateien suchen soll (siehe Rezept 1.13). 6. Kompilieren Sie das Projekt. Je nach der verwendeten IDE können diese eher allgemein gefassten Schritte etwas anders aussehen. So müssen Sie z.B. bei manchen IDEs anstatt mehrerer dieser Schritte nur einen einzigen Arbeitsschritt oder dieselben Schritte in einer anderen Reihenfolge ausführen. Der zweite Schritt wird in den Rezepten 1.21, 1.22 und 1.23 im Detail behandelt. Vorerst sollten Sie aber nach Möglichkeit die Standardeinstellungen Ihrer IDE verwenden. Als Beispiel beschreibe ich nun anschließend, wie man den Quellcode aus Beispiel 1-1 mit der Visual C++ IDE zu einer statischen Bibliothek kompiliert. Klicken Sie im DATEI-Menü auf den Eintrag NEU ➝ PROJEKT..., wählen Sie im linken Fensterbereich VISUAL C++2, wählen Sie WIN32-KONSOLENANWENDUNG, und geben Sie als Projektname libjohnpaul ein. Im Win32-Anwendungs-Assistent klicken Sie auf WEITER, um zu den ANWENDUNGSEINSTELLUNGEN zu gelangen. Dort wählen Sie STATISCHE BIBLIOTHEK aus, deaktivieren die Option VORKOMPILIERTE HEADER und klicken auf FERTIG STELLEN. Sie sollten nun ein leeres Projekt mit den beiden Build-Konfigurationen DEBUG und RELEASE erhalten, wobei die erste der beiden Konfigurationen standardmäßig aktiv ist.
2 In Visual C++-Versionen vor Visual C++ 2005 trug diese Option den Namen Visual C++ Projekte.
1.11 Eine statische Bibliothek mit einer IDE kompilieren | 55
Lassen Sie sich nun die Eigenschaften Ihres Projekts anzeigen, indem Sie im PROJEKTMAPPEN-EXPLORER mit der rechten Maustaste auf den Projektnamen klicken und EIGENSCHAFTEN auswählen. Wählen Sie nun den Eintrag KONFIGURATIONSEINSTELLUNGEN ➝ BIBLIOTHEKAR ➝ ALLGEMEIN aus, und geben Sie im Feld AUSGABEDATEI den Pfad für die Ausgabedatei Ihres Projekts an. Der Verzeichnisanteil des Pfadnamens sollte zu dem Verzeichnis binaries führen, das Sie am Anfang des Kapitels erzeugt haben, während die Ausgabedatei selbst den Namen libjohnpaul.lib haben sollte. Anschließend wählen Sie im Menü PROJEKT den Eintrag VORHANDENES ELEMENT HINZUaus, um die Quelldateien aus Beispiel 1-1 zu Ihrem Projekt hinzuzufügen. In den Eigenschaften Ihres Projekts sollte nun ein Knoten namens »C/C++« auftauchen. Wählen Sie KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ CODEGENERIERUNG, und wählen Sie als Laufzeitbibliothek MULTITHREADED-DEBUG-DLL aus. Sie können Ihr Projekt nun kompilieren, indem Sie im Menü ERSTELLEN auf den Eintrag PROJEKTMAPPE ERSTELLEN klicken. Anschließend sollten Sie noch überprüfen, dass im Verzeichnis binaries auch wirklich eine Binärdatei namens libjohnpaul.lib erstellt wurde. FÜGEN…
Anstatt die Quelldateien aus Beispiel 1-1 mit dem Befehl VORHANDENES ELEMENT HINZUFÜGEN… in Ihr Projekt aufzunehmen, können Sie auch den Menüpunkt NEUES ELEMENT HINZUFÜGEN… verwenden, um leere Quelldateien anzulegen und in Ihr Projekt aufzunehmen. Anschließend können Sie den Code aus Beispiel 1-1 in die neuen Dateien abtippen oder kopieren. Ganz ähnlich können Sie auch bei anderen IDEs vorgehen.
Diskussion IDEs unterscheiden sich viel mehr voneinander als Toolsets. Jede IDE stellt ihre eigenen Mechanismen zur Erzeugung von Projekten, zur Angabe von Projekteinstellungen und zum Aufnehmen von Dateien in ein bestimmtes Projekt bereit. Nichtsdestotrotz ist es meist recht einfach, den Umgang mit einer neuen IDE zu lernen, wenn Sie bereits mit mehreren anderen IDEs gearbeitet haben. Wenn Sie die Verwendung einer neuen IDE erlernen möchten, sollten Sie sich dabei vor allem auf diese Features konzentrieren: • Wie legt man ein neues Projekt an? • Wie legt man den Projekttyp fest (ausführbare Programmdatei, statische oder dynamische Bibliothek)? • Wie nimmt man bereits vorhandene Dateien in ein Projekt auf? • Wie legt man in einem Projekt neue Dateien an? • Wie legt man den Namen der Ausgabedatei eines Projekts fest? • Wie gibt man Include-Pfade an? • Wie gibt man Suchpfade für Bibliotheken an? • Wie gibt man die Bibliotheken an, von denen ein Projekt abhängt?
56 | Kapitel 1: C++-Programme kompilieren
• Wie kompiliert man ein Projekt? • Wie kann man mehrere Projekte als Gruppe zusammenfassen und ihre Abhängigkeiten untereinander angeben? Dieses Rezept demonstriert bereits viele dieser Features. Die meisten anderen Features werden in den Rezepten 1.12 und 1.13 behandelt. Schauen Sie sich nun an, wie man eine statische Bibliothek mit CodeWarrior, C++Builder und Dev-C++ kompiliert.
CodeWarrior Wählen Sie im FILE-Menü NEW..., und klicken Sie im NEW-Dialog auf den Reiter PROJECT. Geben Sie als Projektname libjohnpaul.mcp an, wählen Sie ein Verzeichnis aus, in dem die Konfigurationsdateien Ihres Projekts abgelegt werden sollen, und klicken Sie anschließend doppelt auf MAC OS C++ STATIONERY. Klappen Sie im NEW PROJECT-Dialog die Knoten MAC OS X MACH-O und STANDARD CONSOLE auf, und klicken Sie anschließend doppelt auf C++ CONSOLE MACH-O. Sie sollten nun ein Projekt mit den zwei Targets »Mach-O C++ Console Debug« und »Mach-O C++ Console Final« haben, wobei das erste das Standard-Target ist. Da Sie die Namen dieser Targets angeben müssen, wann immer Sie ein Projekt erzeugen, das von diesem Projekt abhängt, sollten Sie diesen Targets möglichst sprechende Namen geben. Vorerst benennen Sie nur das Debug-Target wie folgt um. Wählen Sie im Fenster Ihres Projekts den Reiter TARGETS aus, und klicken Sie doppelt auf den Namen des Debug-Targets, wodurch das Fenster TARGET SETTINGS eingeblendet wird. Wählen Sie nun TARGET ➝ TARGET SETTINGS aus, und geben Sie im Feld TARGET NAME libjohnpaul Debug ein. Nun gehen Sie im Fenster TARGET SETTINGS auf TARGET ➝ PPC MAC OS X TARGET. Wählen Sie als Projekttyp LIBRARY aus, und geben Sie im Feld FILE NAME libjohnpaul.a ein. Anschließend wählen Sie TARGET ➝ TARGET SETTINGS, klicken auf CHOOSE... und geben nun das Verzeichnis binaries als das Verzeichnis an, in dem die Ausgabedatei libjpohnpaul.a erstellt werden soll. Zuletzt wählen Sie im Fenster Ihres Projekts den Reiter FILES und entfernen die existierenden Quell- und Bibliotheksdateien, indem Sie sie in den Papierkorb ziehen. Dann wählen Sie im Menü PROJECT den Befehl ADD FILES... und nehmen die Dateien aus Beispiel 1-1 in Ihr Projekt auf. Sie können Ihr Projekt nun kompilieren, indem Sie den Befehl MAKE aus dem Menü PROJECT anklicken. Überprüfen Sie anschließend, ob im Verzeichnis binaries die Datei libjohnpaul.a erstellt wurde.
C++Builder Wählen Sie unter DATEI den Menüpunkt NEU ➝ WEITERE… und anschließend den Projekttyp BIBLIOTHEK. Sie sollten nun ein leeres Projekt bekommen. Wählen Sie im Menü DATEI den Befehl PROJEKT SPEICHERN UNTER…, und geben Sie ein Verzeichnis an, in dem
1.11 Eine statische Bibliothek mit einer IDE kompilieren | 57
die Konfigurationsdateien Ihres Projekts abgelegt werden sollen. Als Projektnamen geben Sie libjohnpaul.bpr ein. Als Nächstes klicken Sie im Menü PROJEKT auf OPTIONEN…, wodurch der Dialog mit den Projektoptionen eingeblendet wird. Dort wählen Sie den Reiter VERZEICHNISSE/ BEDINGUNGEN aus und klicken auf den Button neben ENDGÜLTIGE AUSGABE, um das Verzeichnis anzugeben, in dem die Ausgabedatei libjohnpaul.lib erstellt werden soll. Standardmäßig wird diese Datei in dem Verzeichnis angelegt, in dem sich auch die Projektdatei libjohnpaul.bpr befindet. In diesem Beispiel sollten Sie C++Builder aber anweisen, stattdessen das Verzeichnis binaries zu verwenden. Wenn Sie möchten, können Sie auch den Button neben Zwischenausgabe verwenden, um das Verzeichnis festzulegen, in dem die Objektdateien erstellt werden sollen. Standardmäßig werden diese im selben Verzeichnis abgelegt, in dem sich auch die Quelldateien befinden. Zuletzt klicken Sie im Menü PROJEKT auf den Befehl DEM PROJEKT HINZUFÜGEN… und nehmen die Quelldateien aus Beispiel 1-1 in Ihr Projekt auf. Sie können Ihr Projekt nun kompilieren, indem Sie im Menü PROJEKT den Befehl LIBJOHNPAUL ERSTELLEN auswählen. Sie sollten nun noch überprüfen, ob im Verzeichnis binaries eine Datei namens libjohnpaul.lib erstellt wurde.
Dev-C++ Wählen Sie im Menü Datei den Menüpunkt NEU ➝ PROJEKT… aus. Im Dialog NEUES PROJEKT wählen Sie nun als Projekttyp STATIC LIBRARY UND C++-PROJEKT aus und geben libjohnpaul als Projektnamen an. Nachdem Sie auf OK geklickt haben, müssen Sie noch das Verzeichnis auswählen, in dem die Konfigurationsdateien Ihres Projekts abgelegt werden sollen. Als Nächstes wählen Sie im Menü PROJEKT den Menüpunkt PROJEKT OPTIONEN aus, wodurch der Dialog mit den Projektoptionen eingeblendet wird. Klicken Sie nun auf den Reiter BUILD OPTIONEN, und stellen Sie sicher, dass die Ausgabedatei Ihres Projekts den Namen libjohnpaul.a hat. Geben Sie unter AUSGABEVERZEICHNIS FÜR AUSFÜHRBARE DATEIEN den Pfad für das Verzeichnis binaries an. Wenn Sie möchten, können Sie unter AUSGABEVERZEICHNIS FÜR OBJEKTDATEIEN auch explizit angeben, in welchem Verzeichnis die Objektdateien erstellt werden sollen. Zuletzt wählen Sie im Menü PROJEKT noch den Befehl ZUM PROJEKT HINZUFÜGEN und nehmen die Quelldateien aus Beispiel 1-1 in Ihr Projekt auf. Sie können Ihr Projekt nun kompilieren, indem Sie im Menü AUSFÜHREN auf KOMPILIEREN klicken. Nun sollten Sie sich noch vergewissern, dass im Verzeichnis binaries eine Datei namens libjohnpaul.a erstellt worden ist.
Siehe auch Rezept 1.3, 1.8 und 1.16
58 | Kapitel 1: C++-Programme kompilieren
1.12 Eine dynamische Bibliothek mit einer IDE kompilieren Problem Sie möchten Ihre IDE verwenden, um aus ein paar C++-Quelldateien, wie denen aus Beispiel 1-2, eine dynamische Bibliothek zu erstellen.
Lösung Dazu müssen Sie allgemein wie folgt vorgehen: 1. Legen Sie ein neues Projekt an, und geben Sie an, dass Sie eine dynamische Bibliothek und weder eine statische Bibliothek noch eine ausführbare Programmdatei erzeugen möchten. 2. Entscheiden Sie, wie die Bibliothek kompiliert werden soll (also z.B. als Debug- oder Release-Build, singlethreaded oder multithreaded). 3. Geben Sie den Namen der Bibliothek und das Verzeichnis an, in dem sie erstellt werden soll. 4. Fügen Sie dem Projekt Ihre Quelldateien hinzu. 5. Unter Windows müssen Sie auch noch die Makros definieren, die sicherstellen, dass die Symbole der dynamischen Bibliothek mit Hilfe von _ _declspec(dllexport) exportiert werden. 6. Geben Sie falls nötig ein oder mehrere Verzeichnisse an, in denen der Compiler nach eingebundenen Headerdateien suchen soll (siehe Rezept 1.13). 7. Kompilieren Sie das Projekt. Wie schon bei Rezept 1.11, so unterscheiden sich die nötigen Schritte auch hier von IDE zu IDE geringfügig. Der zweite Schritt wird in den Rezepten 1.21, 1.22 und 1.23 im Detail behandelt. Vorerst sollten Sie nach Möglichkeit die Standardeinstellungen Ihrer IDE verwenden. Als Beispiel beschreibe ich nun anschließend, wie man den Quellcode aus Beispiel 1-2 mit der Visual C++-IDE zu einer dynamischen Bibliothek kompiliert. Wählen Sie im Menü DATEI den Menüpunkt NEU ➝ PROJEKT..., wählen Sie im linken Fensterbereich VISUAL C++3, wählen Sie als Projekttyp WIN32-KONSOLENANWENDUNG, und geben Sie als Projektnamen libgeorgeringo ein. Klicken Sie im Win32-AnwendungsAssistenten auf WEITER, um zu den ANWENDUNGSEINSTELLUNGEN zu gelangen. Dort wählen Sie die Optionen DLL und LEERES PROJEKT aus und klicken auf den Button FERTIG STELLEN. Sie sollten nun ein leeres Projekt mit den beiden Build-Konfigurationen DEBUG und RELEASE haben, wobei Debug standardmäßig aktiviert ist. 3 In Visual C++-Versionen vor Visual C++ 2005 trug diese Option den Namen Visual C++ Projekte.
1.12 Eine dynamische Bibliothek mit einer IDE kompilieren | 59
Lassen Sie sich nun die Eigenschaften Ihres Projekts anzeigen, indem Sie mit der rechten Maustaste im Projektmappen-Explorer auf den Projektnamen klicken und EIGENSCHAFTEN auswählen. Wählen Sie nun den Eintrag KONFIGURATIONSEINSTELLUNGEN ➝ LINKER ➝ ALLGEMEIN aus, und geben Sie im Feld AUSGABEDATEI den Pfad für die Ausgabedatei Ihres Projekts an. Der Verzeichnisanteil des Pfadnamens sollte zu dem Verzeichnis binaries führen, das Sie am Anfang des Kapitels erzeugt haben, während die Ausgabedatei selbst den Namen libgeorgeringo.dll haben sollte. Gehen Sie nun auf den Eintrag KONFIGURATIONSEINSTELLUNGEN ➝ LINKER ➝ ADVANCED, und geben Sie dort im Feld IMPORT LIBRARY den Pfadnamen der zu Ihrer DLL gehörenden Import-Bibliothek ein. Der Verzeichnisanteil des Pfadnamens sollte zu dem Verzeichnis binaries führen, das Sie am Anfang des Kapitels erzeugt haben, während als Dateiname libgeorgeringo.lib angegeben werden sollte. Wählen Sie als Nächstes aus dem Menü PROJEKT den Befehl NEUES ELEMENT HINZUFÜGEN…, und nehmen Sie die Quelldateien von Beispiel 1-2 in Ihr Projekt auf. Anstatt die Quelldateien aus Beispiel 1-2 mit dem Befehl VORHANDENES ELEMENT HINZUFÜGEN… in Ihr Projekt aufzunehmen, können Sie auch den Menüpunkt NEUES ELEMENT HINZUFÜGEN… verwenden, um leere Quelldateien anzulegen und in Ihr Projekt aufzunehmen. Anschließend können Sie den Code aus Beispiel 1-2 in die neuen Dateien abtippen oder kopieren. Ganz ähnlich können Sie auch bei anderen IDEs vorgehen.
In den Eigenschaften Ihres Projekts sollte nun ein Knoten namens C/C++ auftauchen. Wählen Sie nun KONFIGURATIONSEINSTELLUNGEN ➝ C/C++ ➝ CODEGENERIERUNG aus, und definieren Sie, wie in Rezept 1.19 beschrieben, das Makro GEORGERINGO_DLL. Gehen Sie als Nächstes auf KONFIGURATIONSEINSTELLUNGEN ➝ C/C++ ➝ CODEGENERIERUNG, und wählen Sie unter LAUFZEITBIBLIOTHEK den Punkt MULTITHREADED-DEBUG-DLL aus. Sie können Ihr Projekt nun kompilieren, indem Sie im Menü ERSTELLEN auf den Eintrag PROJEKTMAPPE ERSTELLEN klicken. Anschließend sollten Sie noch überprüfen, dass im Verzeichnis binaries die Binärdateien libgeorgeringo.dll und libgeorgeringo.lib erstellt worden sind.
Diskussion Wie Sie bereits in Rezept 1.11 gesehen haben, gibt es bei jeder IDE eine eigene Vorgehensweise, um Projekte anzulegen, Konfigurationseinstellungen anzugeben und um Dateien zu einem Projekt hinzuzufügen. In den nächsten Abschnitten werden wir uns damit befassen, wie man eine dynamische Bibliothek mit CodeWarrior, C++Builder und Dev-C++ kompiliert.
CodeWarrior Wählen Sie im FILE-Menü NEW..., und klicken Sie im NEW-Dialog auf den Reiter PROJECT. Geben Sie als Projektname libgeorgeringo.mcp an, wählen Sie ein Verzeichnis aus, in dem die Konfigurationsdateien Ihres Projekts abgelegt werden sollen, und klicken Sie anschlie-
60 | Kapitel 1: C++-Programme kompilieren
ßend doppelt auf MAC OS C++ STATIONERY. Klappen Sie im NEW PROJECT-Dialog die Knoten MAC OS X MACH-O und STANDARD CONSOLE auf, und klicken Sie anschließend doppelt auf C++ CONSOLE MACH-O. Sie sollten nun ein Projekt mit den zwei Targets »Mach-O C++ Console Debug« und »Mach-O C++ Console Final« haben, wobei Ersteres das Standard-Target ist. Da Sie die Namen dieser Targets angeben müssen, wann immer Sie ein Projekt erzeugen, das von diesem Projekt abhängt, sollten Sie diesen Targets möglichst sprechende Namen geben. Vorerst benennen Sie nur das Debug-Target wie folgt um. Wählen Sie im Fenster Ihres Projekts den Reiter TARGETS aus, und klicken Sie doppelt auf den Namen des DebugTargets, wodurch das Fenster TARGET SETTINGS eingeblendet wird. Wählen Sie nun TARGET ➝ TARGET SETTINGS aus, und geben Sie im Feld TARGET NAME libgeorgeringo Debug ein. Nun gehen Sie im Fenster TARGET SETTINGS auf TARGET ➝ PPC MAC OS X TARGET. Wählen Sie als Projekttyp DYNAMIC LIBRARY aus, und geben Sie im Feld FILE NAME libgeorgeringo.dylib ein. Anschließend wählen Sie TARGET ➝ TARGET SETTINGS, klicken auf CHOOSE... und geben nun das Verzeichnis binaries als das Verzeichnis an, in dem die Ausgabedatei libgeorgeringo.dylib erstellt werden soll. Gehen Sie dann auf LINKER ➝ PPC MAC OS X LINKER. Wählen Sie aus der Combo-Box EXPORT SYMBOLS den Eintrag USE #PRAGMA, und vergewissern Sie sich, dass das Feld MAIN ENTRY POINT leer ist. Zuletzt wählen Sie im Fenster Ihres Projekts den Reiter FILES und entfernen die existierenden Quell- und Bibliotheksdateien, indem Sie sie in den Papierkorb ziehen. Dann wählen Sie im Menü PROJECT den Befehl ADD FILES... und nehmen die Dateien aus Beispiel 1-2 in Ihr Projekt auf. Anschließend klicken Sie auf den Befehl ADD FILES... und fügen die Datei dylib1.o im Verzeichnis /usr/lib und die Dateien MSL_All_Mach-O_D.dylib und MSL_ Shared_AppAndDylib_Runtime_D.lib im Verzeichnis Metrowerks CodeWarrior/MacOS X Support/Libraries/Runtime/Runtime_PPC/Runtime_MacOSX/Libs zu Ihrem Projekt hinzu. Wenn Sie anstatt des Debug-Targets das Release-Target konfigurieren, müssen Sie stattdessen die Bibliotheken MSL_All_Mach-O.dylib und MSL_Shared_AppAndDylib_Runtime.lib in Ihr Projekt aufnehmen. Sie können Ihr Projekt nun kompilieren, indem Sie den Befehl MAKE aus dem Menü PROJECT anklicken. Überprüfen Sie anschließend, ob im Verzeichnis binaries die Datei libgeorgeringo.dylib erstellt wurde.
C++Builder Wählen Sie im Menü DATEI den Menüpunkt NEU ➝ WEITERE…, und wählen Sie anschließend den DLL-Experten aus. Im Dialog DLL-EXPERTE wählen Sie die Optionen C++ und MULTI-THREADS aus. Sie sollten nun ein Projekt bekommen, das eine einzige Quelldatei namens Unit1.cpp besitzt. Entfernen Sie Unit1.cpp aus dem Projekt, indem Sie im Menü PROJEKT auf den Befehl AUS DEM PROJEKT ENTFERNEN... klicken und anschließend die Datei Unit1.cpp auswählen. Wählen Sie nun im Menü DATEI den Menüpunkt PROJEKT SPEICHERN UNTER… aus, und geben Sie das Verzeichnis an, in dem die Konfigurationsda-
1.12 Eine dynamische Bibliothek mit einer IDE kompilieren | 61
teien des Projekts gespeichert werden sollen. Zusätzlich geben Sie als Projektname noch libgeorgeringo.bpr an. Als Nächstes klicken Sie im Menü PROJEKT auf OPTIONEN…, wodurch der Dialog mit den Projektoptionen eingeblendet wird. Dort wählen Sie den Reiter VERZEICHNISSE/ BEDINGUNGEN aus und klicken auf den Button neben ENDGÜLTIGE AUSGABE, um anzugeben, dass die Ausgabedateien Ihres Projekts im Verzeichnis binaries abgelegt werden sollen. Standardmäßig werden diese Dateien im selben Verzeichnis wie libjohnpaul.bpr abgelegt. Wenn Sie möchten, können Sie auch den Button neben ZWISCHENAUSGABE verwenden, um das Verzeichnis festzulegen, in dem die Objektdateien erstellt werden sollen. Standardmäßig werden diese im selben Verzeichnis abgelegt, in dem sich auch die Quelldateien befinden. Definieren Sie nun, wie in Rezept 1.19 beschrieben, das Makro GEORGERINGO_DLL. Zuletzt müssen Sie noch im Menü PROJEKT auf den Befehl DEM PROJEKT HINZUFÜGEN… klicken und die Quelldateien von Beispiel 1-2 in Ihr Projekt aufnehmen. Sie können Ihr Projekt nun kompilieren, indem Sie im Menü PROJEKT auf LIBGEORGERINGO ERZEUGEN klicken. Überprüfen Sie anschließend, ob im Verzeichnis binaries die Dateien libgeorgeringo. dll und libgeorgeringo.lib erstellt worden sind.
Dev-C++ Wählen Sie im Menü DATEI den Menüpunkt NEU ➝ PROJEKT… aus. Im Dialog NEUES PROJEKT wählen Sie nun als Projekttyp DLL UND C++-PROJEKT aus und geben libgeorgeringo als Projektnamen an. Nachdem Sie auf OK geklickt haben, müssen Sie noch das Verzeichnis auswählen, in dem die Konfigurationsdateien Ihres Projekts abgelegt werden sollen. Als Nächstes wählen Sie im Menü PROJEKT den Menüpunkt PROJEKT OPTIONEN aus, wodurch der Dialog mit den Projektoptionen eingeblendet wird. Klicken Sie nun auf den Reiter BUILD OPTIONEN, und stellen Sie sicher, dass die Ausgabedatei Ihres Projekts den Namen libjohnpaul.dll hat. Geben Sie unter AUSGABEVERZEICHNIS FÜR AUSFÜHRBARE DATEIEN den Pfad für das Verzeichnis binaries an. Wenn Sie möchten, können Sie unter AUSGABEVERZEICHNIS FÜR OBJEKTDATEIEN auch explizit angeben, in welchem Verzeichnis die Objektdateien erstellt werden sollen. Definieren Sie nun, wie in Rezept 1.19 beschrieben, das Makro GEORGERINGO_DLL. Zuletzt müssen Sie noch alle bereits existierenden Projektdateien aus Ihrem Projekt entfernen, indem Sie sie mit der rechten Maustaste anklicken und den Befehl DATEI ENTFERNEN auswählen. Wählen Sie nun im Menü DATEI den Befehl SPEICHERE PROJEKT ALS …, und speichern Sie die Konfigurationsdatei Ihres Projekts unter dem Dateinamen libgeorgeringo.dev. Nun wählen Sie im Menü PROJEKT noch den Befehl ZUM PROJEKT HINZUFÜGEN und nehmen die Quelldateien aus Beispiel 1-2 in Ihr Projekt auf. Jetzt können Sie Ihr Projekt kompilieren, indem Sie im Menü AUSFÜHREN auf KOMPILIEREN kli-
62 | Kapitel 1: C++-Programme kompilieren
cken. Anschließend sollten Sie sich noch vergewissern, dass im Verzeichnis binaries eine Datei namens libjohnpaul.a erstellt wurde.
Siehe auch Rezept 1.4, 1.9, 1.17, 1.19 und 1.23
1.13 Ein komplexes Programm mit einer IDE kompilieren Problem Sie möchten Ihre IDE verwenden, um eine Programmdatei zu kompilieren, die von mehreren statischen und dynamischen Dateien abhängt.
Lösung Dazu müssen Sie allgemein wie folgt vorgehen: 1. Falls Sie die verwendeten Bibliotheken aus dem Quellcode kompilieren und diese nicht mit eigenen IDE-Projekten ausgeliefert wurden, müssen Sie für diese eigene Projekte anlegen. In den Rezepten 1.11 und 1.12 erfahren Sie, wie Sie dabei vorgehen müssen. 2. Legen Sie ein neues Projekt an, und geben Sie an, dass Sie eine ausführbare Programmdatei und keine Bibliothek erstellen möchten. 3. Entscheiden Sie, wie die Bibliothek kompiliert werden soll (also z.B. Debug- oder Release-Build, singlethreaded oder multithreaded). 4. Geben Sie den Namen der Programmdatei und das Verzeichnis an, in dem sie erstellt werden soll. 5. Fügen Sie dem Projekt Ihre Quelldateien hinzu. 6. Sagen Sie dem Compiler, wo er die Headerdateien der verwendeten Bibliotheken findet. 7. Sagen Sie dem Linker, welche Bibliotheken er verwenden soll und wo er sie findet. 8. Falls Ihre IDE Projektgruppen unterstützt, können Sie alle oben genannten Projekte in einer Projektgruppe unterbringen und die Abhängigkeiten zwischen den Projekten spezifizieren. 9. Falls Ihre IDE Projektgruppen unterstützt, können Sie einfach die ganze Projektgruppe von Schritt 8 kompilieren. Andernfalls müssen Sie die Projekte einzeln kompilieren und darauf achten, dass Sie jedes Projekt vor den Projekten kompilieren, die von ihm abhängen.
1.13 Ein komplexes Programm mit einer IDE kompilieren | 63
Wie schon bei den Rezepten 1.11 und 1.12 so unterscheiden sich die nötigen Schritte auch hier von IDE zu IDE geringfügig. Der dritte Schritt wird in den Rezepten 1.21, 1.22 und 1.23 im Detail behandelt. Vorerst sollten Sie nach Möglichkeit die Standardeinstellungen Ihrer IDE verwenden. Als Beispiel beschreibe ich nun anschließend, wie man den Quellcode aus Beispiel 1-3 mit der Visual C++-IDE kompiliert. Klicken Sie im DATEI-Menü auf den Eintrag NEU ➝ PROJEKT..., wählen Sie im linken Fensterbereich VISUAL C++4, wählen Sie als Projekttyp WIN32-KONSOLENANWENDUNG, und geben Sie als Projektnamen hellobeatles ein. Klicken Sie im Win32-AnwendungsAssistenten auf WEITER, um zu den ANWENDUNGSEINSTELLUNGEN zu gelangen. Dort wählen Sie die Optionen KONSOLENANWENDUNG und LEERES PROJEKT aus und klicken auf den Button FERTIG STELLEN. Sie sollten nun ein leeres Projekt hellobeatles.vcproj mit den beiden Build-Konfigurationen »Debug« und »Release« haben, wobei Erstere standardmäßig aktiviert ist. Sie sollten außerdem auch eine Projektmappe hellobeatles.sln haben, die als einziges Projekt das Projekt hellobeatles.vcproj enthält. Lassen Sie sich nun die Eigenschaften Ihres Projekts anzeigen, indem Sie mit der rechten Maustaste im PROJEKTMAPPEN-EXPLORER auf den Projektnamen klicken und EIGENSCHAFTEN auswählen. Wählen Sie nun den Eintrag KONFIGURATIONSEINSTELLUNGEN ➝ LINKER ➝ ALLGEMEIN aus, und geben Sie im Feld AUSGABEDATEI den Pfad für die Ausgabedatei Ihres Projekts an. Der Verzeichnisanteil des Pfadnamens sollte zu dem Verzeichnis binaries führen, das Sie am Anfang des Kapitels erzeugt haben, während die Ausgabedatei selbst den Namen hellobeatles.exe haben sollte. Wählen Sie als Nächstes aus dem Menü PROJEKT den Befehl NEUES ELEMENT HINZUFÜGEN…, und nehmen Sie die Quelldateien von Beispiel 1-3 in Ihr Projekt auf. In den Eigen-
schaften Ihres Projekts sollte nun ein Knoten namens C/C++ auftauchen. Wählen Sie KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ CODEGENERIERUNG, und wählen Sie als Laufzeitbibliothek MULTITHREADED-DEBUG-DLL aus. Anstatt die Datei helllobeatles.cpp mit dem Befehl VORHANDENES ELEMENT HINZUFÜGEN… in Ihr Projekt aufzunehmen, können Sie auch den Menüpunkt NEUES ELEMENT HINZUFÜGEN… verwenden, um eine leere .cppDatei anzulegen und in Ihr Projekt aufzunehmen. Anschließend können Sie den Code aus Beispiel 1-3 in die neuen Dateien abtippen oder kopieren. Ganz ähnlich können Sie auch bei anderen IDEs vorgehen.
Gehen Sie nun auf KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ ALLGEMEIN, und geben Sie in dem Feld ZUSÄTZLICHE INCLUDEVERZEICHNISSE den Namen des Verzeichnisses an, das die Verzeichnisse johnpaul und georgeringo enthält. Dadurch können die in der Headerdatei hellobeatles.hpp per include-Anweisung eingebundenen Dateien problemlos gefunden werden. 4 In Visual C++-Versionen vor Visual C++ 2005 trug diese Option den Namen Visual C++ Projekte.
64 | Kapitel 1: C++-Programme kompilieren
Wählen Sie als Nächstes im Menü DATEI den Menüpunkt HINZUFÜGEN ➝ VORHANDENES PROJEKT…, und fügen Sie die Projektdateien libjohnpaul.vcproj und libgeorgeringo.vcproj zur Projektmappe hellobeatles hinzu. Klicken Sie nun im Menü PROJEKT auf den Menüpunkt PROJEKTABHÄNGIGKEITEN…, wodurch der Dialog PROJEKTABHÄNGIGKEITEN eingeblendet wird. Wählen Sie in der Combo-Box das Projekt hellobeatles aus, und aktivieren Sie die Checkboxen neben libjohnpaul und libgeorgringo. Falls Sie schon vorher wissen, dass Sie mehrere Projekte in eine Projektmappe aufnehmen möchten, müssen Sie nicht notwendigerweise für jedes Projekt eine eigene Projektmappe anlegen. Stattdessen können Sie eine leere Projektmappe erstellen, indem Sie im Menü DATEI den Menüpunkt NEU ➝ LEERE PROJEKTMAPPE... auswählen und dann über den Menüpunkt NEU ➝ PROJEKT... aus dem Menü DATEI neue Projekte in die Projektmappe aufnehmen.
Nun können Sie Ihre Projektmappe kompilieren, indem Sie im Menü ERSTELLEN auf den Menüpunkt PROJEKTMAPPE ERSTELLEN klicken. Anschließend sollten Sie überprüfen, ob im Verzeichnis binaries die drei Dateien libjohnpaul.lib, libgeorgeringo.dll, libgeorgeringo. lib sowie die Datei hellobeatles.exe erstellt wurden. Wenn alles problemlos abgelaufen ist, können Sie Ihr Programm nun über den Menüpunkt STARTEN OHNE DEBUGGEN aus dem Menü DEBUGGEN ausführen lassen.
Diskussion Im vorangehenden Beispiel konnte man problemlos angeben, dass das Programm hellobeatles.exe von den Bibliotheken libjohnpaul.lib und libgeorgeringo.dll abhängt, da beide Bibliotheken aus dem Quellcode kompiliert werden und als Visual C++-Projekte vorliegen. Wenn Sie eine Anwendung kompilieren möchten, die von Bibliotheken abhängt, die vorkompiliert und mitsamt ihren Headerdateien ausgeliefert werden, können Sie Visual C++ die Pfade zu den benötigten Dateien wie folgt mitteilen: Wechseln Sie zuerst nach KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ ALLGEMEIN, und geben Sie dort im Feld ZUSÄTZLICHE INCLUDEVERZEICHNISSE die Verzeichnisse an, in denen sich die Headerdateien der Bibliotheken befinden. Anschließend wechseln Sie nach KONFIGURATIONSEIGENSCHAFTEN ➝ LINKER ➝ EINGABE und geben im Feld ZUSÄTZLICHE ABHÄNGIGKEITEN die Namen der Bibliotheken an. Schließlich müssen Sie noch nach KONFIGURATIONSEIGENSCHAFTEN ➝ LINKER ➝ ALLGEMEIN wechseln und dort im Feld ZUSÄTZLICHE BIBLIOTHEKSVERZEICHNISSE die Verzeichnisse angeben, in denen sich die Binärdateien der Bibliotheken befinden. In den nächsten Abschnitten werden wir uns damit befassen, wie man aus dem Quellcode von Beispiel 1-3 mit CodeWarrior, C++Builder und Dev-C++ eine ausführbare Programmdatei kompiliert.
CodeWarrior Wählen Sie im FILE-Menü NEW..., und klicken Sie im NEW-Dialog auf den Reiter PROJECT. Geben Sie als Projektname hellobeatles.mcp an, wählen Sie ein Verzeichnis aus, in
1.13 Ein komplexes Programm mit einer IDE kompilieren | 65
dem die Konfigurationsdateien Ihres Projekts abgelegt werden sollen, und klicken Sie anschließend doppelt auf MAC OS C++ STATIONERY. Klappen Sie im NEW PROJECT-Dialog die Knoten MAC OS X MACH-O und STANDARD CONSOLE auf, und klicken Sie anschließend doppelt auf C++ CONSOLE MACH-O. Sie sollten nun ein Projekt mit den zwei Targets »Mach-O C++ Console Debug« und »Mach-O C++ Console Final« haben, wobei Ersteres das Standard-Target ist. Da Sie die Namen dieser Targets angeben müssen, wann immer Sie neue Abhängigkeiten für dieses Projekt festlegen, sollten Sie diesen Targets möglichst sprechende Namen geben. Vorerst benennen Sie nur das Debug-Target wie folgt um. Wählen Sie im Fenster Ihres Projekts den Reiter TARGETS aus, und klicken Sie doppelt auf den Namen des Debug-Targets, wodurch das Fenster TARGET SETTINGS eingeblendet wird. Wählen Sie nun TARGET ➝ TARGET SETTINGS aus, und geben Sie im Feld TARGET NAME hellobeatles Debug ein. Wählen Sie als Nächstes im Projektfenster den Reiter TARGETS aus, und klicken Sie doppelt auf den Namen des Debug-Targets, so dass das Fenster TARGET SETTINGS eingeblendet wird. Wechseln Sie nun nach TARGET ➝ PPC MAC OS X TARGET, und geben Sie als Projekttyp EXECUTABLE und im Feld FILE NAME hellobeatles an. Wechseln Sie nun nach TARGET ➝ TARGET SETTINGS, klicken Sie auf CHOOSE..., und geben Sie als Verzeichnis, in dem die Ausgabedatei hellobeatles erzeugt werden soll, das Verzeichnis binaries an. Nun wählen Sie in Ihrem Projektfenster den Reiter FILES aus und entfernen alle existierenden Quelldateien und MSL-Bibliotheken, indem Sie sie auf den Mülleimer ziehen. Klicken Sie dann im Menü PROJEKT auf den Eintrag ADD FILES..., und nehmen Sie die Datei hellobeatles.cpp aus Beispiel 1-3 in Ihr Projekt auf. Anschließend benutzen Sie wieder ADD FILES..., um die Dateien MSL_All_Mach-O_D.dylib und MSL_Shared_ AppAndDylib_Runtime_D.lib aus dem Verzeichnis Metrowerks CodeWarrior/MacOS X Support/Libraries/Runtime/Runtime_PPC/Runtime_MacOSX/Libs ebenfalls in Ihr Projekt aufzunehmen. Wenn Sie nicht das Debug-, sondern das Release-Target konfigurieren, müssen Sie stattdessen die Bibliotheken MSL_All_Mach-O.dylib und MSL_Shared_ AppAndDylib_Runtime.lib auswählen. Nun wechseln Sie im Fenster TARGET SETTINGS nach TARGET ➝ ACCESS PATHS und klicken auf USER PATHS. Dann klicken Sie auf ADD... und nehmen das Verzeichnis, das die Unterverzeichnisse johnpaul und georgeringo enthält, in das Projekt auf. Dadurch ist sichergestellt, dass die in hellobeatles.hpp eingebundenen Headerdateien gefunden werden. Wählen Sie nun im Menü PROJEKT den Befehl ADD FILES..., und fügen Sie die Projektdateien libjohnpaul.mcp und libgeorgeringo.mcp zu Ihrem Projekt hellobeatles.mcp hinzu. Wählen Sie nun den Reiter TARGETS aus, und klappen Sie die Knoten hellobeatles Debug, libjohnpaul.mcp und libgeorgeringo.mcp auf. Klicken Sie auf die Target-Icons neben den ersten Unterknoten von libjohnpaul.mcp und libgeorgeringo.mcp, die libjohgnpaul Debug und libgeorgeringo Debug heißen. Auf diesen zwei Icons sollten jetzt fett gedruckte Pfeile erscheinen. Vergrößern Sie wenn nötig Ihr Projektfenster, so dass Sie ein kleines Icon ganz rechts im Fenster erkennen können, das ein Kettenglied darstellt. Klicken Sie zwei-
66 | Kapitel 1: C++-Programme kompilieren
mal in diese Spalte, die gegenüber den Target-Icons mit den Pfeilen liegt. Nun sollten in dieser Spalte zwei schwarze Punkte erscheinen. Kompilieren Sie die Projektmappe, indem Sie im Menü PROJEKT auf MAKE klicken. Der Linker gibt jetzt möglicherweise mehrere Warnungen über mehrfach definierte Symbole aus. Diese können Sie aber getrost ignorieren. Wenn Sie diese Warnungen unterdrücken möchten, wechseln Sie dazu nach LINKER ➝ MAC OS X LINKER und aktivieren die Einstellung SUPPRESS WARNING MESSAGES. Im Verzeichnis binaries sollten nun die Dateien libjohnpaul.a, libgeorgeringo.dylib und hellobeatles erstellt worden sein. Sie können hellobeatles aufrufen, indem Sie die Bibliothek MSL_All_Mach-O_D.dylib in das Verzeichnis binaries kopieren und anschließend auf der Kommandozeile ins Verzeichnis binaries wechseln und den Befehl ./hellobeatles eingeben.
C++Builder Wählen Sie im Menü DATEI den Menüpunkt NEU ➝ WEITERE…, und wählen Sie anschließend den KONSOLEN-EXPERTEN aus. Im Dialog KONSOLEN-EXPERTE wählen Sie die Optionen C++, MULTI-THREADS und KONSOLEN-ANWENDUNG aus. Sie sollten nun ein Projekt bekommen, das eine einzige Quelldatei namens Unit1.cpp besitzt. Entfernen Sie Unit1.cpp aus dem Projekt, indem Sie im Menü PROJEKT auf den Befehl AUS DEM PROJEKT ENTFERNEN... klicken und anschließend die Datei Unit1.cpp auswählen. Wählen Sie nun im Menü DATEI den Menüpunkt PROJEKT SPEICHERN UNTER… aus, und geben Sie das Verzeichnis an, in dem die Konfigurationsdateien des Projekts gespeichert werden sollen. Zusätzlich geben Sie als Projektname noch hello_beatles an. Ich habe im Projektnamen einen Unterstrich verwendet, da C++Builder es nicht erlaubt, dass eine Projektdatei denselben Namen wie eine Quelldatei hat. Als Nächstes klicken Sie im Menü PROJEKT auf OPTIONEN…, wodurch der Dialog mit den Projektoptionen eingeblendet wird. Dort wählen Sie den Reiter VERZEICHNISSE/ BEDINGUNGEN aus und klicken auf den Button neben ENDGÜLTIGE AUSGABE, um anzugeben, dass die Ausgabedatei hello_beatles.exe im Verzeichnis binaries abgelegt werden soll. Standardmäßig wird diese Datei im selben Verzeichnis wie hello_beatles.bpr abgelegt. Wenn Sie möchten, können Sie auch den Button neben ZWISCHENAUSGABE verwenden, um das Verzeichnis festzulegen, in dem die Objektdateien erstellt werden sollen. Standardmäßig werden diese im selben Verzeichnis abgelegt, in dem sich auch die Quelldateien befinden. Verwenden Sie nun den Befehl DEM PROJEKT HINZUFÜGEN… aus dem Menü PROJEKT, um die Quelldatei hellobeatles.cpp aus Beispiel 1-3 in Ihr Projekt aufzunehmen. Wählen Sie als Nächstes im Dialog PROJEKTOPTIONEN den Reiter VERZEICHNISSE/BEDINGUNGEN aus, und klicken Sie auf den Button neben INCLUDE-PFAD. Jetzt wählen Sie das Verzeichnis aus, das die Unterverzeichnisse johnpaul und georgeringo enthält. Somit wird sichergestellt, dass die von der Headerdatei hellobeatles.hpp eingebundenen Headerdateien gefunden werden. 1.13 Ein komplexes Programm mit einer IDE kompilieren | 67
Wählen Sie im Menü ANSICHT den Eintrag PROJEKTVERWALTUNG, und klicken Sie in diesem Dialog mit der rechten Maustaste auf PROJECTGROUP1. Nun klicken Sie auf PROJEKTGRUPPE SPEICHERN UNTER..., wählen das Verzeichnis mit der Datei hello_beatles.bpr aus und geben als Dateinamen für die Projektgruppe hello_beatles.bpg ein. Als Nächstes nehmen Sie die Projektdateien libjohnpaul.bpr und libgeorgeringo.bpr in Ihre Projektgruppe auf, indem Sie mit der rechten Maustaste auf HELLO_BEATLES klicken und im Kontextmenü den Eintrag EXISTIERENDES PROJEKT HINZUFÜGEN… auswählen. Kompilieren Sie diese beiden Projekte, wie in Rezept 1.11 und 1.12 beschrieben, falls Sie das noch nicht getan haben, und fügen Sie anschließend die Ausgabedateien libjohnpaul. lib und libgeorgeringo.lib mit dem Befehl DEM PROJEKT HINZUFÜGEN… aus dem Menü PROJEKT zum Projekt hello_beatles hinzu. Halten Sie nun die Strg-Taste gedrückt, während Sie mit Hilfe der Pfeil-nach-oben-Taste die Projekte libjohnpaul und libgeorgeringo in der Projektverwaltung über das Projekt hello_beatles schieben. Dadurch ist gewährleistet, dass diese beiden Projekte zuerst kompiliert werden. Schließlich können Sie die Projektgruppe kompilieren, indem Sie im Menü PROJEKT auf den Menüpunkt ALLE PROJEKTE ERSTELLEN klicken. Überprüfen Sie anschließend, ob im Verzeichnis binaries eine Datei namens hellobeatles.exe erstellt wurde. Nun können Sie das Programm durch die Auswahl des Menüpunkts START im Menü START ausführen.
Dev-C++ Wählen Sie im Menü DATEI den Menüeintrag NEU ➝ PROJEKT… aus. Im Dialog NEUES PROJEKT wählen Sie anschließend CONSOLE APPLICATION und C++-PROJEKT und geben als Projektname hellobeatles ein. Nachdem Sie auf OK geklickt haben, müssen Sie noch ein Verzeichnis auswählen, in dem die Konfigurationsdatei Ihres Projekts abgelegt werden soll. Wählen Sie nun im Dialog PROJEKT OPTIONEN den Reiter BUILD OPTIONEN aus, und stellen Sie sicher, dass die Ausgabedatei Ihres Projekts hellobeatles.exe heißt. Geben Sie unter AUSGABEVERZEICHNIS FÜR AUSFÜHRBARE DATEIEN den Pfadnamen des Verzeichnisses binaries an. Wenn Sie möchten, können Sie im Feld AUSGABEVERZEICHNIS FÜR OBJEKTDATEIEN auch noch explizit angeben, in welchem Verzeichnis die Objektdateien erstellt werden sollen. Entfernen Sie als Nächstes alle Dateien aus Ihrem Projekt, indem Sie sie mit der rechten Maustaste anklicken und im Kontextmenü den Befehl DATEI ENTFERNEN auswählen. Klicken Sie im Menü DATEI auf den Eintrag SPEICHERE PROJEKT ALS…, und speichern Sie die Projektdatei unter dem Namen hellobeatles.dev. Klicken Sie nun noch im Menü PROJEKT auf den Befehl ZUM PROJEKT HINZUFÜGEN, und nehmen Sie die Datei hellobeatles.cpp aus Beispiel 1-3 in Ihr Projekt auf. Wählen Sie nun im Menü PROJEKT den Eintrag PROJEKT OPTIONEN aus, wodurch der Dialog PROJEKT OPTIONEN angezeigt wird. Dort wechseln Sie dann nach VERZEICHNISSE ➝ INCLUDE VERZEICHNISSE und wählen das Verzeichnis aus, das die Unterverzeichnisse
68 | Kapitel 1: C++-Programme kompilieren
johnpaul und georgeringo enthält, und klicken auf HINZUFÜGEN. Dadurch ist sichergestellt, dass die von der Headerdatei hellobeatles.hpp eingebundenen Headerdateien gefunden werden. Wechseln Sie nun noch im Dialog PROJEKT OPTIONEN nach VERZEICHNISSE ➝ BIBLIOTHEKSVERZEICHNISSE, und fügen Sie die Verzeichnisse hinzu, in denen die Ausgabedateien libjohnpaul.a und libgeorgeringo.a aus den Projekten libjohnpaul und libgeorgeringo erstellt werden. Wechseln Sie nun nach PARAMETER ➝ LINKER, und geben Sie die Optionen -ljohnpaul und -lgeorgeringo ein. Kompilieren Sie nun alle drei Projekte einzeln über den Befehl KOMPILIEREN im Menü AUSFÜHREN, und achten Sie darauf, dass Sie hellobeatles zuletzt kompilieren. Nun können Sie hellobeatles.exe starten, indem Sie im Menü AUSFÜHREN auf den Befehl AUSFÜHREN klicken.
Siehe auch Rezept 1.5, 1.10 und 1.18
1.14 GNU make beschaffen Problem Sie möchten sich das Utility GNU make, das Sie beim Kompilieren von Bibliotheken und ausführbaren Programme unterstützt, beschaffen und es installieren.
Lösung Diese Lösung ist von Ihrem Betriebssystem abhängig.
Windows Obwohl Sie vorkompilierte Versionen von GNU make von den verschiedensten Quellen beziehen können, ist es am sinnvollsten, wenn Sie GNU make als Teil einer Unix-ähnlichen Arbeitsumgebung installieren. Ich empfehle Ihnen, entweder Cygwin oder MSYS, das zum MinGW-Projekt gehört, zu verwenden. Cygwin und MinGW werden in Rezept 1.1 beschrieben.
Falls Sie Cygwin wie in Rezept 1.1 beschrieben installiert haben, ist GNU make bereits auf Ihrem System installiert. Sie können es ganz einfach von der Cygwin-Shell aus ausführen, indem Sie dort den Befehl make eingeben.
1.14 GNU make beschaffen
| 69
Um MSYS zu installieren, müssen Sie zuerst MinGW, wie in Rezept 1.1 beschrieben, installieren. In einer zukünftigen Version des MinGW-Installers wird es vielleicht eine Option geben, um MSYS automatisch installieren zu lassen. Vorerst müssen Sie die folgenden zusätzlichen Schritte ausführen. Zuerst müssen Sie die Homepage von MinGW unter http://www.mingw.org besuchen und im Download-Bereich von MinGW die neuste stabile Version des MSYS-Installationsprogramms herunterladen. Der Dateiname dieses Programms ist nach dem Muster MSYS-.exe aufgebaut. Nun müssen Sie das Installationsprogramm ausführen. Zuerst werden Sie gefragt, wo MinGW installiert ist und in welches Verzeichnis MSYS installiert werden soll. Wenn das Installationsprogramm durchgelaufen ist, sollte im Installationsverzeichnis von MSYS eine Datei namens msys.bat existieren. Durch Ausführen dieses Skripts wird die MSYSShell, ein Port der bash-Shell, gestartet. In dieser Shell können Sie GNU make und andere MinGW-Programme wie g++, ar, ranlib und dlltool ausführen. Damit Sie MSYS verwenden können, ist es nicht erforderlich, dass die Unterverzeichnisse namens bin in den Installationsverzeichnissen von MinGW und MSYS in der Umgebungsvariable PATH auftauchen.
Unix Prüfen Sie zuerst, ob GNU make bereits installiert ist, indem Sie auf der Kommandozeile den Befehl make -v ausführen. Wenn GNU make installiert ist, sollten Sie eine Ausgabe wie die folgende erhalten: GNU Make 3.80 Copyright (C) 2002 Free Software Foundation, Inc. This is free software; see the source for copying conditions. ...
Wenn auf Ihrem System eine andere als die GNU-Version von make installiert ist, kann es auch sein, dass die GNU-Version unter dem Namen gmake installiert ist. Sie können das überprüfen, indem Sie auf der Kommandozeile den Befehl gmake -v ausführen. Wenn Sie Mac OS X verwenden, besteht der einfachste Weg, um an GNU make zu kommen, darin, die Entwicklungsumgebung Xcode von Apples Website herunterzuladen und die Installationsanleitung zu befolgen. Xcode ist momentan unter developer.apple. com/tools erhältlich. Andernfalls können Sie die neuste Version von GNU make unter ftp://ftp.gnu.org/pub/ gnu/make herunterladen, entpacken und unter Zuhilfenahme der Installationsanleitung installieren.
Diskussion Vom Utility make gibt es die unterschiedlichsten Varianten. So bieten die meisten Toolsets ihre eigene Version von make an; wie beispielsweise Visual C++, wo ein make-Utility 70 | Kapitel 1: C++-Programme kompilieren
namens nmake.exe mitgeliefert wird. Für gewöhnlich besitzen diese Toolset-spezifischen Versionen von make eingebaute Features, durch die sie mit ihrem Toolset besonders einfach zu verwenden sind. Deshalb ist es bei der Behandlung von make im Zusammenspiel mit mehreren Toolsets notwendig, entweder mehrere Versionen von make zu behandeln oder sich mit dem Fall zu arrangieren, dass eine bestimmte Version von make und ein bestimmtes Toolset nicht besonders gut miteinander harmonieren. Anstatt mehrere make-Utilities vorzustellen, habe ich beschlossen, mich auf GNU make zu konzentrieren, das die mächtigste und portabelste Variante von make darstellt. GNU make wurde in erster Linie dazu entwickelt, um mit GCC zusammenzuarbeiten. Deshalb kann es manchmal recht schwierig sein, GNU make zusammen mit anderen Toolsets – und besonders zusammen mit Windows-Toolsets – einzusetzen. Dennoch ist es aufgrund der Flexibilität, die GNU make auszeichnet, weit einfacher, GNU make mit Toolsets anderer Hersteller einzusetzen, als das bei den anderen Toolsets, wie z.B. nmake.exe, der Fall ist. Einen großen Teil seiner Mächtigkeit verdankt GNU make der Fähigkeit, komplexe Shell-Skripten auszuführen. Wenn Sie schon mit Unix und Windows gearbeitet haben, wissen Sie, dass die Windows-Shell cmd.exe viel zu wünschen übrig lässt: Ihr fehlen viele nützliche Befehle, sie besitzt nur eingeschränkte Möglichkeiten zur Ausführung von Skripten und schränkt die mögliche Länge von Befehlszeilen massiv ein. Deshalb wird die Nützlichkeit von GNU make durch die Verwendung von cmd.exe auch stark begrenzt. Glücklicherweise bieten Cygwin und MSYS hervorragende Arbeitsumgebungen, um GNU make auch unter Windows einzusetzen. MSYS stellt unter Windows eine minimale Umgebung zur Ausführung Unix-artiger Makefiles und configure-Skripten bereit. Zu den nützlichen Tools, die von MSYS bereitgestellt werden, gehören u.a. awk, cat, cp, grep, ls, mkdir, mv, rm, rmdir und sed. MSYS wurde in erster Linie entworfen, um mit GCC zusammenzuarbeiten, was auch hervorragend funktioniert. Mit anderen Windows-Toolsets funktioniert das Zusammenspiel dagegen nicht ganz so gut. Besonders problematisch sind jene Toolsets, bei denen die Umgebungsvariablen mit Hilfe von .bat-Skripten gesetzt werden müssen und die Schrägstriche (/) anstatt Bindestriche (-) zur Kennzeichnung von Kommandozeilen-Parametern verwenden. Während MSYS eine minimalistische Umgebung bereitstellt, ist Cygwin bestrebt, eine möglichst vollständige UNIX-Umgebung anzubieten. Ein unter Cygwin installiertes make kann alles, was auch ein unter MSYS installiertes make kann, und noch viel mehr. Jedoch sollten sich portable Makefiles auf eine möglichst kleine Auswahl von GNU-Utilities beschränken, und MSYS unterstützt alle diese Utilities.
Siehe auch Rezept 1.1
1.14 GNU make beschaffen
| 71
1.15 Ein einfaches »Hallo Welt«-Programm mit GNU make kompilieren Problem Sie möchten mit GNU make ein einfaches »Hallo Welt«-Programm wie das aus Beispiel 1-4 kompilieren.
Lösung Bevor Sie Ihr erstes Makefile schreiben, müssen Sie ein paar Grundbegriffe kennen. Ein Makefile besteht aus einer Ansammlung von Regeln der Form: Targets: Vorbedingungen Befehlsskript
Syntaktisch handelt es sich bei Targets und Vorbedingungen um durch Kommas getrennte Strings, und Befehlsskript besteht aus keiner oder mehreren Zeilen Text, von der jede mit einem Tabulatorzeichen beginnt. Targets und Vorbedingungen sind meist Dateinamen, manchmal werden an dieser Stelle aber auch einfach formale Namen für von make auszuführende Aktionen verwendet. Das Befehlsskript besteht aus einer Sequenz von Befehlen, die an eine Shell übergeben werden. Vereinfacht gesagt weist eine Regel make an, aus den angegebenen Vorbedingungen die angegebenen Targets zu erzeugen, indem es das Befehlsskript ausführt. Whitespace-Zeichen sind in Makefiles sehr wichtig. Die Zeilen eines Befehlsskripts müssen mit einem Tabulator- und nicht mit einem Leerzeichen beginnen – hierdurch entstehen die meisten Anfängerfehler. In den folgenden Beispielen werden Zeilen, die mit einem Tabulator beginnen, durch eine Einrückung von vier Zeichen kenntlich gemacht.
Nun können Sie loslegen. Erzeugen Sie im Verzeichnis, in dem Ihre Quelldateien liegen, eine Datei namens Makefile. Deklarieren Sie in dieser Datei vier Targets. Nennen Sie das erste Target all, und geben Sie als einzige Vorbedingung den Namen der ausführbaren Programmdatei an, die Sie erstellen möchten. Dieses Target benötigt kein Befehlsskript. Benennen Sie das zweite Target nach Ihrer Programmdatei. Geben Sie hier als Vorbedingung Ihre Quelldatei an, und notieren Sie als Befehlsskript den Befehl, mit dem aus der Quelldatei die Programmdatei kompiliert wird. Das dritte Target sollte den Namen install bekommen. Es hat keine Vorbedingungen, und im Befehlsskript sollte der Befehl stehen, mit dem die Programmdatei vom Verzeichnis mit dem Makefile in das gewünschte Installationsverzeichnis kopiert wird. Das letzte Target sollte den Namen clean bekommen. Wie schon install, so hat auch dieses Target keine Vorbedingungen. Das Befehlsskript sollte die Programmdatei und die beim Kompilieren erzeugte Objekt-
72 | Kapitel 1: C++-Programme kompilieren
datei aus dem aktuellen Verzeichnis löschen. Die Targets clean und install sollten beide mit dem Attribut PHONY als Phony-Targets (unechte Targets) deklariert werden. Um beispielsweise aus dem Quellcode aus Beispiel 1-4 mit Hilfe von GCC eine ausführbare Programmdatei zu kompilieren, könnte Ihr Makefile wie das in Beispiel 1-14 aussehen. Beispiel 1-14: Makefile zum Erstellen der Programmdatei hello mit GCC # Dies ist das Standard-Target, das beim Aufruf von make # erstellt wird .PHONY: all all: hello # Durch diese Regel weiß make, wie es aus der Datei hello.cpp die Datei hello erstellen kann hello: hello.cpp g++ -o hello hello.cpp # Diese Regel weist make an, die Datei hello ins Unterverzeichnis binaries zu kopieren # und das Verzeichnis dabei notfalls anzulegen .PHONY: install install: mkdir -p binaries cp -p hello binaries # Diese Regel weist make an, die Dateien hello und hello.o zu löschen .PHONY: clean clean: rm -f hello
Wenn Sie aus dem Quellcode aus Beispiel 1-4 dagegen mit Visual C++ eine Programmdatei erstellen möchten, können Sie dazu das Makefile aus Beispiel 1-15 verwenden. Beispiel 1-15: Makefile zum Erstellen der Programmdatei hello.exe mit Visual C++ #Standard-Target .PHONY: all all: hello.exe #Regel zum Erstellen von hello.exe hello.exe: hello.cpp cl -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t \ -Fehello hello.cpp .PHONY: install install: mkdir -p binaries cp -p hello.exe binaries .PHONY: clean clean: rm -f hello.exe
1.15 Ein einfaches »Hallo Welt«-Programm mit GNU make kompilieren | 73
Befehle und Listen von Targets oder Vorbedingungen können Sie in einem Makefile auch über mehrere Zeilen notieren, indem Sie die Zeilenumbrüche durch einen Backslash (\) maskieren (genauso, wie Sie das auch in C++-Quelldateien tun können).
Um die Programmdatei zu erstellen, müssen Sie die von Ihren Kommandozeilen-Tools benötigten Umgebungsvariablen setzen, ins Verzeichnis mit dem Makefile wechseln und den Befehl make eingeben. Wenn Sie die Programmdatei ins Unterverzeichnis binaries kopieren möchten, geben Sie dazu den Befehl make install ein. Wenn Sie die Programmdatei und die beim Kompilieren erzeugte Objektdatei löschen möchten, geben Sie dazu den Befehl make clean ein. Wenn Sie, wie in Rezept 1.1 beschrieben, die Cygwin-Umgebung installiert haben, können Sie das Makefile aus Beispiel 1-15 direkt über die Windows-Shell cmd.exe ausführen.
Sie können dieses Makefile auch wie folgt über die Cygwin-Shell ausführen. Dazu rufen Sie über die Shell cmd.exe das Skript vcvars32.bat auf, wodurch die Umgebungsvariablen von Visual C++ gesetzt werden. Als Nächstes führen Sie das Skript cygwin.bat aus, um die Cygwin-Shell zu starten. Wenn Sie das Installationsverzeichnis von Cygwin in die Umgebungsvariable PATH aufnehmen, können Sie die Cygwin-Shell aus cmd.exe heraus durch Eingabe des Befehls cygwin starten. Nun müssen Sie nur noch ins Verzeichnis mit dem Makefile wechseln und make eingeben. Auf ähnliche Weise können Sie das Makefile auch aus der MSYS-Shell heraus ausführen: Führen Sie vcvars32.bat mit Hilfe von cmd.exe aus, und starten Sie anschließend die MSYS-Shell, indem Sie das Skript msys. bat ausführen. Falls Ihr Toolset ein Skript zum Setzen der Umgebungsvariablen bereitstellt, ist die Ausführung eines Makefiles über Cygwin oder MSYS etwas umständlicher als die Ausführung über cmd.exe. Allerdings ist das bei manchen Makefiles notwendig, da sie einfach nicht funktionieren, wenn sie über cmd.exe ausgeführt werden.
Diskussion Beim Lesen der nächsten Rezepte werden Sie merken, dass GNU make ein mächtiges Werkzeug zum Kompilieren komplexer Projekte ist. Aber was macht es eigentlich? GNU make arbeitet folgendermaßen: Wenn es ohne Parameter aufgerufen wird, sucht es im aktuellen Arbeitsverzeichnis nach einer Datei mit dem Namen GNUmakefile, makefile oder Makefile und versucht, das erste in dieser Datei vorhandene Target, das so genannte Default Target, zu kompilieren. Falls das Default Target uptodate ist, gibt es für make überhaupt nichts zu tun, und es beendet sich sofort wieder. Wenn das Default Target
74 | Kapitel 1: C++-Programme kompilieren
uptodate ist, bedeutet das, dass es existiert und alle Vorbedingungen ebenfalls uptodate sind und keine der Vorbedingungen nach der Erstellung des Default Targets verändert worden ist. Sollte das nicht der Fall sein, versucht make das Default Target aus seinen Vorbedingungen zu erstellen, indem es das Befehlsskript des Default Targets ausführt. Dabei geht make rekursiv vor: Für jede Vorbedingung, die nicht uptodate ist, sucht make nach einer Regel, die diese Vorbedingung als Target deklariert, und sucht auch bei diesem Target wieder nach nicht mehr aktuellen Vorbedingungen. Für diese Vorbedingungen wird dann ebenfalls wieder nach einem Target gesucht usw. Das Ganze geht so lange weiter, bis das Default Target auf den neusten Stand gebracht wurde oder ein Fehler auftritt. Aus der obigen Beschreibung folgt, dass ein Target, das keine Vorbedingungen besitzt, nur dann uptodate ist, wenn es sich auf eine Datei im Dateisystem bezieht. Deshalb kann ein Target, das sich auf eine nicht existierende Datei bezieht, niemals uptodate sein. Dadurch kann es verwendet werden, um die unbedingte Ausführung eines Befehlsskripts zu erzwingen. Solche Targets werden als Phony Targets (unechte Targets) bezeichnet. Wenn Sie ein Target wie in den Beispielen 1-14 und 1-15 mit dem Attribut .PHONY kennzeichnen, teilen Sie make dadurch mit, dass sich dieses Target auf keine Datei bezieht und deshalb immer wieder von neuem erstellt werden soll.
Umgekehrt ist eine Vorbedingung, die sich auf eine existierende Datei bezieht, immer uptodate, vorausgesetzt, sie wird nicht erst durch eine Regel erzeugt. Beschäftigen wir uns nun mit dem, was passiert, wenn wir das Makefile aus Beispiel 1-14 ausführen. Das Phony Target all ist niemals aktuell: Es dient nur dazu, um make zur Erstellung von hello.exe anzuweisen. In solch einem einfachen Makefile ist ein Target all nicht zwingend erforderlich; in komplexeren Beispielen könnte das Target all dagegen mehrere Vorbedingungen haben. Die Regel mit dem Target hello weist make an, wenn notwendig hello durch den Aufruf von g++ zu erstellen. Unter der Voraussetzung, dass das aktuelle Verzeichnis bis auf die Dateien Makefile und hello.cpp leer ist, ist das Target hello nicht uptodate. Die Vorbedingung ist dagegen uptodate, weil die Datei hello.cpp existiert und weil hello.cpp in keiner Regel als Target angegeben wird. Folglich ruft make g++ auf, um die Datei hello.cpp zu kompilieren und zur Datei hello zu linken. Die Vorbedingung des Targets all ist nun uptodate, weshalb make das Target all erstellt – wohlgemerkt durch die Ausführung eines leeren Befehlsskripts – und sich beendet. Wenn Sie make mit dem Namen eines Targets als Parameter aufrufen, versucht es, dieses Target zu erstellen. Deshalb werden durch den Aufruf von make install die folgenden Befehle ausgeführt: mkdir -p binaries cp -p hello binaries
1.15 Ein einfaches »Hallo Welt«-Programm mit GNU make kompilieren | 75
Der erste Befehl legt das Verzeichnis binaries an, falls es noch nicht existiert; der zweite Befehl kopiert hello in dieses Verzeichnis. Auf dieselbe Weise ruft make clean den Befehl rm -f hello
auf, der die Ausgabedatei hello löscht. Wenn Sie Windows verwenden, dann sind mit den Befehlen mkdir, cp und rm, die von den Targets install und clean aufgerufen werden, die entsprechenden mit Cygwin oder MSYS ausgelieferten GNU-Tools gemeint.
Sobald Sie verstehen, wie make Abhängigkeiten analysiert, wird Ihnen das Makefile aus Beispiel 1-14 wahrscheinlich ziemlich trivial vorkommen. Tatsächlich ist es aber weit komplizierter, als es eigentlich sein müsste. Wenn man sich mit den verschiedenen Möglichkeiten befasst, wie dieses Makefile vereinfacht werden kann, dann ist das gleichzeitig eine gute Methode, um sich mit den grundlegenden Features von Makefiles vertraut zu machen.
Make-Variablen GNU make erlaubt die Verwendung von String-Variablen. Am häufigsten werden Variablen in Makefiles als symbolische Konstanten verwendet. Dadurch muss man Dateinamen und Shell-Befehle nicht mehr an verschiedenen Stellen mehrfach direkt angeben, sondern kann sie stattdessen einer Variablen zuweisen und dann nur noch die Variable anstelle des Dateinamens oder des Shell-Befehls angeben. Dadurch werden die Makefiles weniger kompliziert und sind einfacher zu pflegen. Unter Zuhilfenahme von Make-Variablen könnten Sie beispielsweise das Makefile aus Beispiel 1-14 so umschreiben, dass es wie das Makefile in Beispiel 1-16 aussieht. Beispiel 1-16: Makefile zum Erstellen von hello mit GCC; modifizierte Version mit Make-Variablen # Geben Sie hier die Ausgabedatei und das Installationsverzeichnis an OUTPUTFILE=hello INSTALLDIR=binaries # Default Target .PHONY: all all: $(OUTPUTFILE) # Erstelle hello aus hello.cpp $(OUTPUTFILE): hello.cpp g++ -o hello hello.cpp # Kopiere hello ins Unterverzeichnis binaries .PHONY: install install: mkdir -p $(INSTALLDIR) cp -p $(OUTPUTFILE) $(INSTALLDIR) # Lösche hello
76 | Kapitel 1: C++-Programme kompilieren
Beispiel 1-16: Makefile zum Erstellen von hello mit GCC; modifizierte Version mit Make-Variablen .PHONY: clean clean: rm -f $(OUTPUTFILE)
Ich habe in diesem Makefile die beiden Make-Variablen OUTPUTFILE und INSTALLDIR neu eingeführt. Wie Sie sehen können, kann man einer Make-Variable mit dem Zuweisungsoperator = einen Wert zuweisen. Ausgewertet werden diese Variablen, wenn man sie einklammert und dem Ganzen ein Dollar-Zeichen voranstellt. Sie können den Wert einer Make-Variable auch von der Kommandozeile aus festlegen, indem Sie dazu die Syntax make X=Y verwenden. Zusätzlich werden beim Aufruf von make alle Make-Variablen mit dem Wert einer gleichnamigen Umgebungsvariable initialisiert, falls denn eine existiert. Die auf der Kommandozeile angegebenen Werte haben Vorrang vor den von Umgebungsvariablen geerbten Werten, während die im Makefile angegebenen Werte wiederrum Vorrang vor auf der Kommandozeile angegebenen Werten haben. GNU make unterstützt auch automatische Variablen, die spezielle Werte annehmen, wenn sie in einem Befehlsskript ausgewertet werden. Die wichtigsten davon sind die Variable $@, die für den Dateinamen des Targets steht, die Variable $ $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
Dieses Codefragment basiert auf der Compiler-Option -M, die GCC zur Ausgabe von Abhängigkeitsinformationen veranlasst, die dann in einem Makefile eingesetzt werden können. Wenn Sie ganz genau wissen möchten, wie diese Methode funktioniert und warum sie manchmal unzulänglich ist, können Sie das Ganze auch in dem Buch GNU make von Robert Mecklenburg (O’Reilly) nachlesen. Notieren Sie den Code zur Ermittlung der Abhängigkeiten immer am Ende Ihrer Makefiles.
Dieser Code kann für beinahe jedes Toolset angepasst werden, da die meisten Compiler einen ähnlichen Parameter wie GCCs Option -M besitzen. Tatsächlich heißt diese Option für gewöhnlich sogar -M oder -m. Visual C++ stellt jedoch keine Option zum Generieren von Makefile-Abhängigkeiten bereit. Wenn Sie Visual C++ verwenden, haben Sie zwei Möglichkeiten. Sie können die Option -Gm gemeinsam mit einer der Optionen -Zi oder -ZI verwenden, die in Rezept 1.21 behandelt werden. Die Option -Gm weist den Compiler an, eine Datenbank zu erstellen, die in einer Datei mit der Endung idb gespeichert wird und Informationen über die Abhängigkeiten von Quelldateien enthält. Die .idb-Datei wird dann erstellt, wenn eine oder mehrere .cpp-Dateien zum ersten Mal kompiliert werden. Bei jedem nachfolgenden Kompiliervorgang werden nun nur noch die Quelldateien neu kompiliert, die selbst modifiziert wurden oder von Headerdateien abhängen, die modifiziert wurden. Alternativ können Sie auch die Option -showIncludes in Kombination mit der Option -E verwenden. Durch die Option -showIncludes wird der Compiler veranlasst, jedes Mal, wenn er eine include-Anweisung findet, eine Meldung auf der Standardfehlerausgabe auszugeben. Die Option -E weist den Compiler an, nur den Präprozessor auszuführen und sich dann wieder zu beenden, ohne dabei irgendwelche Binärdateien zu erstellen. Wenn Sie jetzt noch ein kleines Shell-Skript schreiben, können Sie aus der von -showIncludes erzeugten Ausgabe die benötigten Makefile-Abhängigkeiten erzeugen: include john.d paul.d johnpaul.d %.d: %.cpp "$(MSVCDIR)/bin/cl" -E -showIncludes $< 2> $@.$$$$ > /dev/null; \ sed -n 's/^Note: including file: *\(.*\)/$*.obj•$*.d:\1/gp' \ < $@.$$$$ | sed 's:\\:/:g;s: :\\ :gp' > $@; \ rm -f $@.$$$$
Das Zeichen • steht in diesem Beispiel für einen Tabulator. Lassen Sie uns an dem Makefile aus Beispiel 1-20 noch eine letzte Verbesserung vornehmen. Momentan kommt die Liste john paul johnpaul an zwei verschiedenen Stellen im
84 | Kapitel 1: C++-Programme kompilieren
Makefile vor; in den Vorbedingungen der Regel, die die statische Bibliothek erstellt, und in der include-Anweisung, die die Liste der Abhängigkeiten generiert. Wenn sich die Liste der Quelldateien ändert, müssen Sie das Makefile an zwei Stellen abändern. Besser ist es, wenn Sie eine Variable SOURCES definieren und beide Vorkommen von john paul johnpaul durch einen Ausdruck ersetzen, der die nötigen Daten aus der Variablen SOURCES extrahiert: SOURCES = john.cpp paul.cpp johnpaul.cpp ... # Erstelle libjohnpaul.a aus john.o, paul.o und johnpaul.o $(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES)) ar ru $@ $^ ranlib $@ ... # Generiere die Abhängigkeiten, die .ccp- auf .hpp-Dateien besitzen include $(subst .cpp,.d,$(SOURCES)) %.d: %.cpp $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
Hier verwende ich die make-Funktion $(subst x,y,str), die jedes Vorkommen von x in str durch y ersetzt. GNU make verfügt über eine große Sammlung von Funktionen, mit denen u.a. Strings und Dateinamen manipuliert werden können. Außerdem werden auch benutzerdefinierte Funktionen unterstützt. Auch dieses Thema wird in dem Buch GNU make von Robert Mecklenburg (O’Reilly) erschöpfend behandelt.
Siehe auch Rezept 1.2 und 1.7
1.17 Eine dynamische Bibliothek mit GNU make kompilieren Problem Sie möchten GNU make verwenden, um ein paar C++-Quelldateien, wie die aus Beispiel 1-2, zu einer dynamischen Bibliothek zu kompilieren.
1.17 Eine dynamische Bibliothek mit GNU make kompilieren | 85
Lösung Legen Sie zuerst in dem Verzeichnis, in dem Ihre dynamische Bibliothek erstellt werden soll, ein Makefile an. Deklarieren Sie in diesem Makefile ein Phony Target namens all, dessen einzige Vorbedingung die zu erstellende dynamische Bibliothek ist. Als Nächstes deklarieren Sie ein Target für Ihre dynamische Bibliothek. Als Vorbedingungen geben Sie die Objektdateien an, aus denen die Bibliothek erstellt werden soll. Das Befehlsskript sollte aus der Befehlszeile bestehen, die aus diesen Objektdateien eine statische Bibliothek erstellt (siehe Rezept 1.4). Wenn Sie GCC oder einen anderen Compiler mit ähnlicher Aufrufsyntax verwenden, müssen Sie die impliziten Pattern-Regeln wenn nötig noch anpassen, indem Sie eine oder mehrere der Variablen CXX, CXXFLAGS etc. entsprechend setzen, die in makes eingebauter Datenbank impliziter Regeln verwendet werden (siehe Rezept 1.15). Andernfalls notieren Sie einfach eine Pattern-Regel, die make sagt, wie es .cpp-Dateien in Objektdateien kompilieren kann, und verwenden dazu die Befehlszeilen aus Tabelle 1-8 und die in Rezept 1.15 vorgestellte Syntax für Pattern-Regeln. Deklarieren Sie als Nächstes Targets, die abbilden, wie die Quelldateien Ihrer Bibliothek von den Headerdateien, die sie einbinden, direkt oder indirekt abhängen. Sie können diese Abhängigkeiten entweder von Hand angeben oder es so einrichten, dass sie automatisch generiert werden. Zuletzt müssen Sie nur noch (ähnlich wie in Rezept 1.15) die beiden Targets install und clean deklarieren und eine der in Rezept 1.16 gezeigten Methoden zur automatischen Generierung von Abhängigkeiten in Ihrem Makefile anwenden. Um beispielsweise aus den Quelldateien aus Beispiel 1-2 mit GCC unter Unix eine dynamische Bibliothek zu erstellen, könnten Sie im Verzeichnis georgeringo ein Makefile wie das in Beispiel 1-22 anlegen. Beispiel 1-22: Makefile zum Erstellen von libgeorgeringo.so mit GCC # Geben Sie hier Endungen von Dateien an, die von make clean gelöscht werden sollen CLEANEXTS = o so # Geben Sie hier die Quelldateien, die Ausgabedateien # und das Installationsverzeichnis an SOURCES = george.cpp ringo.cpp georgeringo.cpp OUTPUTFILE = libgeorgeringo.so INSTALLDIR = ../binaries .PHONY: all all: $(OUTPUTFILE) # Erstelle libgeorgeringo.so aus george.o, ringo.o # und georgeringo.o; subst ist die in Rezept 1.16 # vorgestellte Suchen-und-Ersetzen-Funktion $(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES)) $(CXX) -shared -fPIC $(LDFLAGS) -o $@ $^ .PHONY: install install: mkdir -p $(INSTALLDIR)
86 | Kapitel 1: C++-Programme kompilieren
Beispiel 1-22: Makefile zum Erstellen von libgeorgeringo.so mit GCC (Fortsetzung) cp -p $(OUTPUTFILE) $(INSTALLDIR) .PHONY: clean clean: for file in $(CLEANEXTS); do rm -f *.$$file; done # Ermittelt die Abhängigkeiten der .ccp-Dateien von den .hpp-Dateien include $(subst .cpp,.d,$(SOURCES)) %.d: %.cpp $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
Diskussion Das Makefile in Beispiel 1-22 basiert direkt auf den in den Rezepten 1.4, 1.15 und 1.16 vorgestellten Ideen. Der wichtigste Unterschied zwischen Beispiel 1-22 und Beispiel 1-20 ist die Regel zur Erstellung von lingeorgeringo.so aus den Objektdateien george.o, ringo.o und georgeringo.o: $(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES)) $(CXX) -shared -fPIC $(LDFLAGS) -o $@ $^
Die Variable $(OUTPUTFILE) expandiert zu lingeorgeringo.so, und der Ausdruck $(subst .cpp,.o,$(SOURCES)) expandiert zu george.o, ringo.o und georgeringo.o (siehe Rezept 1.16). Das Befehlsskript $(CXX) -shared -fPIC $(LDFLAGS) -o $@ $^ ist eine angepasste Variante der in Tabelle 1-11 abgedruckten GCC-Befehlszeile.
Siehe auch Rezept 1.4, 1.9, 1.12, 1.19 und 1.23
1.18 Ein komplexes Programm mit GNU make kompilieren Problem Sie möchten mit GNU make eine ausführbare Programmdatei erstellen, die von mehreren statischen und dynamischen Bibliotheken abhängt.
Lösung Befolgen Sie diese Anleitung:
1.18 Ein komplexes Programm mit GNU make kompilieren | 87
1. Legen Sie für die von Ihrem Programm benötigten Bibliotheken, wie in Rezept 1.16 und 1.17 beschrieben, eigene Makefiles an. Diese Makefiles sollten sich in verschiedenen Verzeichnissen befinden. 2. Legen Sie in einem anderen Verzeichnis ein weiteres Makefile an. Mit diesem Makefile kann das fertige Programm erstellt werden, allerdings müssen die in Schritt 1 erstellten Makefiles immer zuerst ausgeführt werden. Deklarieren Sie in diesem Makefile ein unechtes (phony) Target namens all, und geben Sie als Vorbedingung dieses Targets Ihre Programmdatei an. Zusätzlich deklarieren Sie noch ein Target für Ihre Programmdatei und geben als Vorbedingungen die von Ihrem Programm verwendeten Bibliotheken und die aus den .cpp-Dateien des Programms erstellten Objektdateien an. Schreiben Sie für dieses Target ein Befehlsskript, ähnlich wie das in Rezept 1.5, das aus den angegebenen Bibliotheken und den Objektdateien eine Programmdatei erstellt. Wenn nötig müssen Sie auch noch wie in Rezept 1.16 eine Pattern-Regel schreiben, die aus den .cpp-Dateien die entsprechenden Objektdateien erstellt. Nehmen Sie nun noch die aus Rezept 1.15 bekannten Targets install und clean sowie einen der in Rezept 1.16 gezeigten Mechanismen zur automatischen Ermittlung der Abhängigkeiten zwischen den Quelldateien in Ihr Makefile auf. 3. Erstellen Sie nun noch ein Makefile in einem Verzeichnis, das in der Verzeichnishierarchie über den Verzeichnissen mit den anderen Makefiles steht – lassen Sie uns das neue Makefile als Toplevel Makefile und die anderen als untergeordnete Makefiles bezeichnen. Deklarieren Sie ein Default Target all, und geben Sie als Vorbedingung das Verzeichnis an, in dem sich das in Schritt 2 erstellte Makefile befindet. Deklarieren Sie eine Regel, in der Sie als Targets die Verzeichnisse mit den untergeordneten Makefiles angeben. Das Befehlsskript dieser Regel soll in jedem dieser Verzeichnisse den Befehl make aufrufen, wobei der Name des Targets mit Hilfe der Variablen TARGET angegeben wird. Schließlich müssen Sie noch mehrere Targets definieren, die die Abhängigkeiten zwischen den Default Targets der untergeordneten Makefiles angeben. Um beispielsweise aus den Quelldateien aus Beispiel 1-3 mit GCC unter Unix eine Programmdatei zu erstellen, könnten Sie ein Makefile wie das in Beispiel 1-23 schreiben. Beispiel 1-23: Makefile zum Kompilieren von hellobeatles.exe mit GCC # Geben Sie hier die Quelldateien, die Ausgabedateien, die zu kompilierenden Verzeichnisse # und das Installationsverzeichnis an SOURCES = hellobeatles.cpp OUTPUTFILE = hellobeatles LIBJOHNPAUL = libjohnpaul.a LIBGEORGERINGO = libgeorgeringo.so JOHNPAULDIR = ../johnpaul GEORGERINGODIR = ../georgeringo INSTALLDIR = ../binaries
88 | Kapitel 1: C++-Programme kompilieren
Beispiel 1-23: Makefile zum Kompilieren von hellobeatles.exe mit GCC (Fortsetzung) # Gibt das Elternverzeichnis als Include-Verzeichnis an # CPPFLAGS += -I.. # # Default Target # .PHONY: all all: $(HELLOBEATLES) # # Target zum Erstellen der Programmdatei # $(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES)) \ $(JOHNPAULDIR)/$(LIBJOHNPAUL) \ $(GEORGERINGODIR)/$(LIBGEORGERINGO) $(CXX) $(LDFLAGS) -o $@ $^ .PHONY: install install: mkdir -p $(INSTALLDIR) cp -p $(OUTPUTFILE) $(INSTALLDIR) .PHONY: clean clean: rm -f *.o rm -f $(OUTPUTFILE) # Ermittelt die Abhängigkeiten der .ccp-Dateien von den .hpp-Dateien include $(subst .cpp,.d,$(SOURCES)) %.d: %.cpp $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
Erstellen Sie nun in dem Verzeichnis mit den Unterverzeichnissen johnpaul, georgeringo, hellobeatles und binaries ein Toplevel Makefile wie das in Beispiel 1-24. Beispiel 1-24: Toplevel Makefile für den Quellcode aus den Beispielen 1-1, 1-2 und 1-3 # Alle Targets in diesem Makefile sind unecht (phony) .PHONY: all johnpaul georgeringo hellobeatles # Default Target all: hellobeatles # Die Targets johnpaul, georgeringo und hellobeatles repräsentieren # Verzeichnisse; das Befehlsskript ruft in jedem dieser Verzeichnisse make auf johnpaul georgeringo hellobeatles: $(MAKE) --directory=$@ $(TARGET)
1.18 Ein komplexes Programm mit GNU make kompilieren | 89
Beispiel 1-24: Toplevel Makefile für den Quellcode aus den Beispielen 1-1, 1-2 und 1-3 (Fortsetzung) # Diese Regel drückt aus, dass das Default Target des Makefiles # im Verzeichnis hellobeatles von den Default Targets der Makefiles # in den Verzeichnissen johnpaul und georgeringo abhängt .PHONY: hellobeatles hellobeatles: johnpaul georgeringo
Wenn Sie das Programm hellobeatles erstellen möchten, wechseln Sie dazu in das Verzeichnis mit dem Toplevel Makefile und geben den Befehl make ein. Die Dateien libjohnpaul.a, libgeorgeringo.so und hellobeatles kopieren Sie in das Verzeichnis binaries, indem Sie den Befehl make TARGET=install eingeben. Wenn Sie die von Make erstellten Dateien wieder löschen möchten, geben Sie den Befehl make TARGET=clean ein.
Diskussion Die in diesem Rezept gezeigte Methode zur Verwaltung komplexer Projekte ist als rekursiver Aufruf von make bekannt. Bei dieser Methode wird ein Projekt in verschiedene Module unterteilt, die alle ein eigenes Makefile besitzen, während die Abhängigkeiten der einzelnen Module untereinander angegeben werden müssen. Diese Methode ist aber nicht auf ein einziges Toplevel Makefile mit mehreren direkt untergeordneten Makefiles beschränkt. Stattdessen kann sie auch erweitert werden, um mehrstufige Baumstrukturen abzubilden. Obwohl der rekursive Aufruf von make einst die Standardvorgehensweise bei der Verwaltung großer Projekte mit make war, werden heute andere, als überlegen angesehene Methoden eingesetzt. Weitere Details zu diesem Thema finden Sie in dem Buch GNU make von Robert Mecklenburg (O’Reilly). Beispiel 1-23 stellt eine direkte Umsetzung der in den Rezepten 1.15, 1.16 und 1.17 vorgestellten Techniken dar. Es gibt dort nur eine wirklich interessante Sache. Wie ich in Rezept 1.15 gesagt habe, muss, wenn hellobeatles.cpp von der Kommandozeile aus kompiliert werden soll, die Option -I.. angegeben werden, damit der Compiler die Headerdateien johnpaul.hpp und georgeringo.hpp finden kann. Eine Lösung für dieses Problem besteht darin, für das Kompilieren von hellobeatles.o eine eigene Regel zu schreiben, in deren Befehlsskript die Option -I.. angegeben wird: hellobeatles.o: hello.beatles.cpp g++ -c –I.. –o hellobeatles.o hellobeatles.cpp
Stattdessen habe ich die in Rezept 1.15 beschriebene Variable CPPFLAGS verwendet, um immer dann, wenn eine .cpp-Datei zu einer Objektdatei kompiliert wird, die Option -I.. an die von make generierte Befehlszeile anzuhängen: CPPFLAGS
+= -I..
Ich habe hier statt dem Zuweisungsoperator = den Operator += verwendet. Dadurch wird der von mir angegebene Wert an den bisherigen Wert von CPPFLAGS angehängt, und andere Werte, die auf der Kommandozeile oder über eine Umgebungsvariable angegeben wurden, werden nicht überschrieben.
90 | Kapitel 1: C++-Programme kompilieren
Lassen Sie uns nun herausfinden, wie das Makefile in Beispiel 1-24 funktioniert. Die wichtigste Regel ist hier diejenige, die dafür sorgt, dass make in jedem der Verzeichnisse johnpaul, georgeringo und hellobeatles aufgerufen wird: johnpaul georgeringo hellobeatles: $(MAKE) --directory=$@ $(TARGET)
Um diese Regel zu verstehen, müssen Sie drei Dinge wissen. Erstens, dass die Variable MAKE zum Programmnamen der momentan laufenden Version von make expandiert. Normalerweise ist das make, aber auf manchen Systemen kann das Programm auch gmake heißen. Zweitens, dass der Kommandozeilen-Parameter --directory= make dazu veranlasst, als Arbeitsverzeichnis zu verwenden. Drittens, dass eine Regel mit mehreren Targets gleichbedeutend ist mit mehreren Regeln, die unterschiedliche Targets, aber identische Befehlsskripten haben. Somit ist die obige Regel gleichbedeutend mit: johnpaul: $(MAKE) --directory=$@ $(TARGET) georgeringo: $(MAKE) --directory=$@ $(TARGET) hellobeatles: $(MAKE) --directory=$@ $(TARGET)
Diese wiederum sind gleichbedeutend mit: johnpaul: $(MAKE) --directory=johnpaul $(TARGET) georgeringo: $(MAKE) --directory=georgeringo $(TARGET) hellobeatles: $(MAKE) --directory=hellobeatles $(TARGET)
Somit ruft diese Regel also die Makefiles in jedem der Verzeichnisse johnpaul, georgeringo und hellobeatles auf, wobei der Wert der Variable TARGET auf der Kommandozeile übergeben wird. Deshalb können Sie das Target xxx jedes der untergeordneten Makefiles erstellen, indem Sie das Toplevel Makefile mit dem Parameter TARGET=xxx aufrufen. Die letzte Regel dieses Makefiles stellt sicher, dass die untergeordneten Makefiles in der richtigen Reihenfolge aufgerufen werden; sie gibt einfach an, dass das Target hellobeatles von den Targets johnpaul und georgeringo abhängt: hellobeatles: johnpaul georgeringo
Bei einer komplexeren Anwendung kann es auch viele verschiedene Abhängigkeiten zwischen der Programmdatei und den verwendeten Bibliotheken geben. Für jede dieser Bibliotheken müssen Sie dann eine Regel angeben, die angibt, von welcher Bibliothek diese Bibliothek direkt abhängt.
1.18 Ein komplexes Programm mit GNU make kompilieren | 91
Siehe auch Rezept 1.5, 1.10 und 1.13
1.19 Ein Makro definieren Problem Sie möchten das Präprozessorsymbol Name definieren. Dabei soll diesem entweder vom Compiler ein beliebiger Wert oder ein ganz bestimmter benutzerdefinierter Wert zugewiesen werden.
Lösung Die Compileroptionen zur Definition eines Makros auf der Kommandozeile sehen Sie in Tabelle 1-16. Eine Anleitung zur Definition eines Makros über Ihre IDE finden Sie in Tabelle 1-17. Wenn Sie mit Boost.Build ein Makro definieren möchten, müssen Sie dazu einfach nur, wie in Tabelle 1-15 und Beispiel 1-12 gezeigt, eine Einstellung der Form name[=value] in die Requirements Ihres Projekts aufnehmen. Tabelle 1-16: Ein Makro auf der Kommandozeile definieren Toolset
Option
Alle
-Dname[=value]
Tabelle 1-17: Ein Makro über eine IDE definieren IDE
Konfiguration
Visual C++
Lassen Sie sich die Eigenschaften Ihres Projekts anzeigen, wählen Sie dann KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ PRÄPROZESSOR, und geben Sie im Feld PRÄPROZESSORDEFINITIONEN Ihr Makro in der Form Name[=Wert] ein, wobei Sie mehrere Einträge durch Semikolons getrennt eingeben können.
CodeWarrior
Gehen Sie im Fenster TARGET SETTINGS auf LANGUAGE SETTINGS ➝ C/C++ PREPROCESSOR, und geben Sie dort im Feld PREFIX TEXT Ihr Makro in der Form #define Name[=Wert]
ein. C++Builder
Wählen Sie im Fenster PROJEKTOPTIONEN den Reiter VERZEICHNISSE/BEDINGUNGEN, und geben Sie Ihr Makro im Feld DEFINITION in der Form Name[=Wert] ein, wobei Sie mehrere Einträge durch Semikolons getrennt eingeben können.
Dev-C++
Wählen Sie im Fenster PROJEKT OPTIONEN den Reiter PARAMETER aus und geben Sie Ihr Makro im Textfeld C++COMPILER in der Form -DName[=Wert]
ein.
92 | Kapitel 1: C++-Programme kompilieren
Diskussion Präprozessorsymbole werden in C++-Quellcode häufig verwendet, um dieselben Quelldateien mit mehreren Build-Konfigurationen oder für verschiedene Betriebssysteme zu kompilieren. Nehmen Sie beispielsweise an, Sie möchten eine Funktion schreiben, die überprüft, ob es sich bei einer Datei um ein Verzeichnis handelt. Momentan fehlt die für diese Aufgabe benötigte Funktionalität in der C++-Standardbibliothek; somit ist es notwendig, dass Ihre Funktion auf plattformspezifische Features zurückgreift. Wenn Sie möchten, dass Ihr Code sowohl unter Windows als auch unter Unix funktioniert, müssen Sie sicherstellen, dass der Teil des Codes, der auf Windows-spezifische Features zurückgreift, für den Compiler unsichtbar ist, wenn das Programm unter Unix kompiliert wird, und umgekehrt. Normalerweise realisiert man dies durch das Verfahren der bedingten Kompilierung, das in Beispiel 1-25 demonstriert wird. Beispiel 1-25: Bedingte Kompilierung mit Hilfe vordefinierter Makros #ifdef _WIN32 # include #else // Nicht unter Windows – gehe davon aus, dass wir uns unter Unix befinden # include #endif bool is_directory(const char* path) { #ifdef _WIN32 // Windows-Implementierung #else // Unix-Implementierung #endif }
Unter Windows definieren mit Ausnahme des Cygwin-Ports von GCC alle Compiler standardmäßig das Makro _WIN32; Makros, die auf diese Weise automatisch definiert werden, werden als vordefinierte Makros bezeichnet. Beispiel 1-25 verwendet das vordefinierte Makro WIN32, um herauszufinden, unter welchem Betriebssystem es kompiliert wird, um dann den entsprechenden plattformspezifischen Code zu aktivieren. Allerdings sind die für eine derartige bedingte Kompilierung benötigten Konfigurationsinformationen oftmals nicht als vordefinierte Makros verfügbar. In solch einem Fall müssen Sie Ihre eigenen Makros einführen und ihnen mit Hilfe der Methoden aus den Tabellen 1-15, 1-16 und 1-17 die entsprechenden Werte geben. Ein gutes Beispiel ist auch der Code in Beispiel 1-2. Unter Windows soll die Funktion georgeringo( ) beim Erstellen der DLL georgeringo.dll mit dem Attribut _ _declspec(dllexport) deklariert werden, während andernfalls das Attribut _ _declspec(dllimport) verwendet werden soll. Wie ich bereits in Rezept 1.4 gesagt habe, können Sie das erreichen, indem Sie beim Erstellen der DLL auf der Kommandozeile das Präprozessorsymbol GEORGERINGO_DLL definieren, während Sie dieses Symbol beim Kompilieren von Code, der die DLL verwendet, einfach undefiniert lassen. 1.19 Ein Makro definieren | 93
Wenn Sie bei der Definition eines Makros keinen Wert angeben, weisen ihm die meisten Compiler den Wert 1 zu, wobei ihm manche aber auch einen leeren Wert zuordnen. Wenn Makros wie in Beispiel 1-25 zur bedingten Kompilierung verwendet werden, ist dieser Unterschied vollkommen egal; wenn Sie aber möchten, dass ein Makro zu einem bestimmten Wert expandiert, sollten Sie diesen Wert mit Hilfe der Syntax -D= explizit angeben.
Siehe auch Rezept 1.4, 1.9, 1.12 und 1.17
1.20 Einen Kommandozeilen-Parameter in einer IDE angeben Problem Sie möchten einen Kommandozeilen-Parameter an Ihren Compiler oder Linker übergeben, allerdings gibt es in Ihrer IDE keine Projekteinstellung, die diesem Parameter entspricht.
Lösung Viele IDEs ermöglichen es dem Benutzer, Kommandozeilen-Parameter direkt an den Compiler oder den Linker durchzureichen. Die für die verschiedenen IDEs notwendige Vorgehensweise finden Sie in den Tabellen 1-18 und 1-19. Tabelle 1-18: Eine Compileroption in der IDE angeben IDE
Konfiguration
Visual C++
Wählen Sie in den Eigenschaften Ihres Projekts KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ BEFEHLSZEILE, und geben Sie die Option im Feld ZUSÄTZLICHE OPTIONEN ein.
CodeWarrior
Nicht unterstützt
C++Builder
Nicht unterstützt
Dev-C++
Wählen Sie im Fenster PROJEKT OPTIONEN den Reiter PARAMETER aus, und geben Sie den Parameter im Feld C++COMPILER ein.
Tabelle 1-19: Eine Linkeroption in der IDE angeben IDE
Konfiguration
Visual C++
Wählen Sie in den Eigenschaften Ihres Projekts KONFIGURATIONSEIGENSCHAFTEN ➝ LINKER ➝ BEFEHLSZEILE, und geben Sie die Option im Feld ZUSÄTZLICHE OPTIONEN ein.
Metrowerks
Nicht unterstützt
94 | Kapitel 1: C++-Programme kompilieren
Tabelle 1-19: Eine Linkeroption in der IDE angeben (Fortsetzung) IDE
Konfiguration
C++Builder
Nicht unterstützt
Dev-C++
Wählen Sie im Fenster PROJEKT OPTIONEN den Reiter PARAMETER aus, und geben Sie den Parameter im Feld LINKER ein.
Diskussion Visual C++ bietet über seine grafische Benutzeroberfläche eine beträchtliche Zahl an Konfigurationsmöglichkeiten, dennoch ist es aber möglich, dem Compiler direkt Kommandozeilen-Parameter zu übergeben. Bei CodeWarrior und C++Builder ist es dagegen nicht möglich, Kommandozeilen-Parameter für den Compiler explizit anzugeben. Das ist im Allgemeinen aber kein Problem, da beide über ihre Benutzeroberfläche ähnlich umfassende Einstellungsmöglichkeiten bieten wie Visual C++. Andererseits gibt es aber auch IDEs, bei denen die meisten Einstellungen ausschließlich dadurch vorgenommen werden können, dass man die entsprechenden Kommandozeilen-Parameter in einem Textfeld eingibt. Dev-C++ bewegt sich irgendwo zwischen diesen beiden Extremen: Während Dev-C++ mehr grafische Konfigurationsmöglichkeiten bietet als die meisten anderen für das GCC Toolset entwickelten IDEs, ist es trotzdem häufig notwendig, bestimmte Kommandozeilen-Parameter explizit anzugeben.
1.21 Einen Debug Build erstellen Problem Sie möchten eine Version Ihres Programms erstellen, die sich einfach debuggen lässt.
Lösung Um einen Debug Build eines Programms zu erstellen, müssen Sie im Allgemeinen folgende Dinge erreichen: • Alle Optimierungen deaktivieren • Das Expandieren von Inline-Funktionen deaktivieren • Die Erzeugung von Debug-Informationen aktivieren In Tabelle 1-20 sehen Sie die Compiler- und Linkeroptionen, mit denen Sie Codeoptimierung und Inline-Expansion deaktivieren können, während Sie in Tabelle 1-21 die Compiler- und Linkeroptionen zur Aktivierung der Debug-Informationen finden können.
1.21 Einen Debug Build erstellen
| 95
Tabelle 1-20: Optimierungen und Inline-Expansion auf der Kommandozeile deaktivieren
a
Toolset
Optimierung
Inline-Expansion
GCC
-O0
-fno-inlinea
Visual C++ Intel (Windows)
-Od
-Ob0
Intel (Linux)
-O0
-Ob0
-opt off
-inline off
Comeau (Unix)
-O0
--no_inlining
Comeau (Windows)
Selbe Option wie beim Backend, nur dass ein Schrägstrich (/) anstatt des Bindestrichs (-) verwendet werden muss.
Borland
-Od
-vi-
Digital Mars
-o+none –S
-C
Dieser Parameter muss nur dann verwendet werden, wenn auch -O3 übergeben wird.
Tabelle 1-21: Kommandozeilen-Parameter zum Aktivieren der Debug-Informationen Toolset
Compileroptionen
Linkeroptionen
Comeau (Unix) GCC Intel (Linux) Metrowerks
-g
-g
Visual C++ Intel (Windows)
Siehe Tabelle 1-22.
Siehe Tabelle 1-22.
Comeau (Windows)
Selbe Option wie beim Backend, nur dass ein Schrägstrich (/) anstatt des Bindestrichs (-) verwendet werden muss.
Selbe Option wie beim Backend-Compiler, nur dass ein Schrägstrich (/) anstatt des Bindestrichs (-) verwendet werden muss.
Borland
-v
-v
Digital Mars
-g
-co
Tabelle 1-22: Debug-Informationen für Visual C++ oder Intel für Windows aktivieren Compileroptionen
Linkeroptionen
IDE-Optionena
Beschreibung
-Z7
-debug
C7-Kompatibel
Die Debug-Informationen werden in .obj- und .exe-Dateien gespeichert.
-Zi [-Fd]
-debug[-pdb:]
Programmdaten-bank
Die Debug-Informationen werden in .pdb-Dateien gespeichert; verwenden Sie die Optionen in eckigen Klammern, um die .pdb-Dateien anzugeben.
96 | Kapitel 1: C++-Programme kompilieren
Tabelle 1-22: Debug-Informationen für Visual C++ oder Intel für Windows aktivieren (Fortsetzung)
a
Compileroptionen
Linkeroptionen
IDE-Optionena
Beschreibung
-ZI [-Fd]
-debug [-pdb:]
Programmdatenbank zum Bearbeiten und Fortfahren
Die Debug-Informationen werden in .pdb-Dateien gespeichert; verwenden Sie die Optionen in eckigen Klammern, um die .pdb-Dateien anzugeben. Ihr Programm kann während des Debuggens neu kompiliert werden.
Auf diese Einstellungen können Sie über KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ ALLGEMEIN ➝ DEBUGINFORMATIONSFORMAT zugreifen.
Boost.Build bietet einen einfachen Mechanismus, um einen Debug Build zu erzeugen: Nehmen Sie in die Requirements Ihres Targets einfach die Einstellung debug auf, oder verwenden Sie den Kommandozeilen-Parameter variant=debug, der auch einfach mit debug abgekürzt werden kann. Manche IDEs bieten auch eine einfachere Möglichkeit, um einen Debug Build zu erzeugen. Wenn Sie beispielsweise mit Visual C++ ein neues Projekt erstellen, erzeugt die IDE automatisch eine Build-Konfiguration für einen Debug Build und eine für einen Release Build. Um nun einen Debug Build zu erstellen, klicken Sie einfach im Menü ERSTELLEN auf KONFIGURATIONS-MANAGER… und wählen dann DEBUG als aktive Konfiguration aus. Genauso gut können Sie auch in der Combo-Box mit den Konfigurationen in der Standard-Toolbar DEBUG auswählen. Wenn Sie Ihr Projekt nun das nächste Mal erstellen, wird dabei ein Debug Build erzeugt. Ähnlich verhält es sich, wenn Sie mit CodeWarrior ein neues Projekt erstellen und dabei eines von Metrowerks’ Projekt-Templates namens stationery verwenden. Auch in diesem Fall werden von der IDE automatisch ein Debug und ein Release Target erstellt. Der Name des Debug Targets kann variieren, allerdings enthält er immer das Wort »debug«. Um diesen Debug Build nun zu erstellen, wählen Sie im Menü PROJECT den Eintrag SET DEFAULT TARGET und wählen anschließend den entsprechenden Menüeintrag für das Debug Target. Sie können das Debug Target auch in der Combo-Box mit den Targets auswählen, die sich in Ihrem Projektfenster befindet. C++Builder bietet keine Unterstützung für verschiedene Build-Konfigurationen innerhalb eines Projekts, dennoch gibt es einen einfachen Weg, um einen Debug Build zu erstellen. Um einen Debug Build zu erzeugen, gehen Sie nach PROJEKTOPTIONEN ➝ COMPILER und klicken auf VOLL-DEBUG. Dadurch werden die Optimierungen und die InlineExpansion deaktiviert und die Debug-Informationen aktiviert. Wenn Sie eine IDE verwenden, die keine fertigen Debug- und Release-Konfigurationen bereitstellt, wie z.B. Dev-C++, oder falls Sie eine genauere Kontrolle über Ihre Projekteinstellungen haben möchten, finden Sie alle nötigen Informationen zur manuellen Konfiguration eines Debug Builds in den Tabellen 1-23 bis 1-25.
1.21 Einen Debug Build erstellen
| 97
Tabelle 1-23: Codeoptimierungen in einer IDE deaktivieren IDE
Konfiguration
Visual C++
Gehen Sie in den Eigenschaften Ihres Projekts auf KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ OPTIMIERUNG, und setzen Sie die Einstellung OPTIMIERUNG auf DEAKTIVIERT. Lassen Sie die anderen Einstellungen auf dieser Seite unverändert.
CodeWarrior
Gehen Sie im Fenster TARGET SETTINGS auf CODE GENERATION ➝ GLOBAL OPTIMIZATIONS, und wählen Sie die Einstellung OFF aus.
C++Builder
Gehen Sie im Dialog PROJEKTOPTIONEN auf den Reiter COMPILER, und wählen Sie unter CODE-OPTIMIERUNG die Option KEINE aus.
Dev-C++
Gehen Sie im Fenster PROJEKT OPTIONEN auf COMPILER ➝ OPTIMIERUNG, und setzen Sie die Einstellung KLEINE OPTIMIERUNGEN VORNEHMEN auf NO; gehen Sie als Nächstes auf COMPILER ➝ OPTIMIERUNG ➝ WEITERE OPTIMIERUNGEN, und setzen Sie die Einstellungen OPTIMIERE, MEHR OPTIMIEREN und BESTE OPTIMIERUNG auf NO.
Tabelle 1-24: Inline-Expansion in einer IDE deaktivieren IDE
Konfiguration
Visual C++
Gehen Sie in den Eigenschaften Ihres Projekts auf KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ OPTIMIERUNG, und setzen Sie die Einstellung INLINEFUNKTIONSERWEITERUNG auf STANDARD.
CodeWarrior
Gehen Sie im Fenster TARGET SETTINGS auf LANGUAGE SETTINGS ➝ C/C++ LANGUAGE, und setzen Sie die Einstellung INLINE DEPTH auf DON’T INLINE.
C++Builder
Gehen Sie im Dialog PROJEKTOPTIONEN auf den Reiter COMPILER, und aktivieren Sie unter DEBUGGEN die Einstellung INLINE-EXPANSION DEAKTIVIEREN.
Dev-C++
Suchen Sie in Tabelle 1-20 den Eintrag für GCC heraus, und befolgen Sie die Anleitung in Rezept 1.20.
Tabelle 1-25: Debug-Informationen in einer IDE aktivieren IDE
Konfiguration
Visual C++
Siehe Tabelle 1-22.
CodeWarrior
Gehen Sie im Fenster TARGET SETTINGS auf LANGUAGE SETTINGS ➝ LINKER ➝ PPC MAC OS X LINKER, und aktivieren Sie die beiden Einstellungen GENERATE SYM FILE und FULL PATH IN SYM FILES.
C++Builder
Gehen Sie im Dialog PROJEKTOPTIONEN auf den Reiter COMPILER, und aktivieren Sie die Einstellungen DEBUG-INFORMATIONEN und ZEILENNUMMER-INFORMATION.
Dev-C++
Suchen Sie in Tabelle 1-21 den Eintrag für GCC heraus, und befolgen Sie die Anleitung in Rezept 1.20.
Diskussion Mit allen Toolsets ist es möglich, zusätzliche Informationen in Objektdateien und Programmdateien aufzunehmen, mit deren Hilfe der Debugger das Programm Schritt für Schritt ausführen und dabei nützliche Laufzeitinformationen über das Programm gewinnen kann. Zu diesen Debug-Informationen gehören üblicherweise die Namen der Quelldateien und die Zeilennummern, die den einzelnen Speicherobjekten und Maschinencode-Anweisungen entsprechen, sowie Informationen über C++-Objekte, wie etwa deren Speicheradressen und ihre Namen und Typinformationen. Die meisten Toolsets speichern Debug-Informationen direkt in den Objekt- und Programmdateien, während es manche aber optional auch erlauben, die Debug-Informatio98 | Kapitel 1: C++-Programme kompilieren
nen in separaten Datenbankdateien abzulegen. So kann man Visual C++ beispielsweise mit der Compileroption -Z7 anweisen, die Debug-Informationen direkt in den Objektund Programmdateien abzulegen, während man es mit den Parametern -Zi und -ZI anweist, die Debug-Informationen in Programmdatenbank-Dateien mit der Endung .pdb abzulegen. Mit dem Parameter -ZI wird ein Feature namens Bearbeiten und Fortfahren aktiviert, durch das man das Programm in der IDE ändern und neu kompilieren kann, ohne dabei eine laufende Debug-Sitzung zu unterbrechen. Ähnlich geht auch CodeWarrior für Mac OS X vor, der Debug-Informationen standardmäßig in eigens dafür bestimmten .SYM-Dateien ablegt. Die meisten Toolsets können selbst dann Debug-Informationen erzeugen, wenn Optimierung und Inline-Expansion aktiviert sind. Jedoch können bestimmte Debug-Informationen mit bestimmten Optimierungen inkompatibel sein. Wenn Optimierungen aktiviert sind, kann der Compiler die Effizienz des Programms optimieren, indem er die Reihenfolge gewisser Anweisungen oder gar ganze Code-Abschnitte ändert, solange das zu beobachtende Verhalten des Programms dabei unverändert bleibt. Dadurch wird das Debuggen erschwert, da nun nicht mehr notwendigerweise eine genaue Übereinstimmung zwischen dem Quellcode und den erzeugten Maschinenbefehlen bestehen muss. Dasselbe gilt auch für die Inline-Expansion: Wenn der Compiler einen Funktionsaufruf durch den Code der Funktion expandiert, wird der dieser Funktion entsprechende Objektcode innerhalb der aufrufenden Funktion dupliziert. Somit wird bei der Ausführung dieses Codes kein Stackframe für die expandierte Funktion erzeugt; das bedeutet u.a., dass der Debugger die Werte der Funktionsparameter und der lokalen Variablen nicht anzeigen kann. Für gewöhnlich unternehmen Debugger noch nicht einmal den Versuch, die Quellcode-Zeilen zu ermitteln, die Code aus dem Rumpf einer expandierten Inline-Funktion entsprechen. Aus diesen Gründen deaktiviert man bei der Erstellung eines Debug Builds für gewöhnlich alle Optimierungen und die Inline-Expansion.
Siehe auch Rezept 1.22
1.22 Einen Release Build erstellen Problem Sie möchten eine möglichst kleine und schnelle Programmdatei oder dynamische Bibliothek erstellen, um sie an Ihre Kunden auszuliefern.
Lösung Allgemein müssen Sie dazu wie folgt vorgehen: • Aktivieren Sie die Optimierungen 1.22 Einen Release Build erstellen | 99
• Aktivieren Sie die Inline-Expansion • Deaktivieren Sie die Generierung von Debug-Informationen In Tabelle 1-26 sehen Sie die Compiler- und Linkeroptionen zum Aktivieren der Optimierungen und der Inline-Expansion. Es gibt keine Kommandozeilen-Parameter, um die Generierung von Debug-Informationen zu deaktivieren: Wenn Sie auf der Kommandozeile kompilieren, sind die Debug-Informationen standardmäßig deaktiviert. Wenn Sie das GCC Toolset verwenden, können Sie jedoch die Größe der Programmdateien und der dynamischen Bibliotheken verringern, indem Sie dem Linker den Parameter -s übergeben. Tabelle 1-26: Compileroptionen zur Aktivierung der Optimierungen und der Inline-Expansion
a
Toolset
Optimierung
Inline-Expansion
GCC
-O3
-finline-functionsa
Visual C++ Intel
-O2
-Ob1
Metrowerks
-opt full
-inline auto -inline level=8
Comeau (Unix)
-O3
Comeau (Windows)
Selbe Option wie beim Backend, nur dass ein Schrägstrich (/) anstatt des Bindestrichs (-) verwendet werden muss.
Borland
-O2
-vi
Digital Mars
-o+time
Standardmäßig aktiviert
--inlining
Diese Option wird automatisch aktiviert, wenn der Parameter -O3 angegeben wird.
Boost.Build bietet einen einfachen Mechanismus, um einen Release Build zu erzeugen: Nehmen Sie in die Requirements Ihres Targets einfach die Einstellung release auf, oder verwenden Sie den Kommandozeilen-Parameter variant=release, der auch einfach mit release abgekürzt werden kann. Manche IDEs bieten auch eine einfachere Möglichkeit, um einen Release Build zu erzeugen. Wie ich in Rezept 1.21 erwähnt habe, wird jedes Mal, wenn Sie in Visual C++ ein neues Projekt erstellen, automatisch eine Build-Konfiguration für einen Debug und eine für einen Release Build angelegt. Um nun einen Release Build zu erstellen, klicken Sie einfach im Menü ERSTELLEN auf KONFIGURATIONS-MANAGER… und wählen dann RELEASE als aktive Konfiguration aus. Genauso gut können Sie auch in der Combo-Box mit den Konfigurationen in der Standard-Toolbar RELEASE auswählen. Wenn Sie Ihr Projekt nun das nächste Mal erstellen, wird dabei ein Release Build erzeugt. Ähnlich verhält es sich, wenn Sie mit CodeWarrior ein neues Projekt erstellen und dabei eines von Metrowerks’ Projekt-Templates namens stationery verwenden. Auch in diesem Fall werden von der IDE automatisch ein Debug und ein Release Target erstellt. Der Name des Release Targets kann variieren, allerdings enthält er immer das Wort »release«
100 | Kapitel 1: C++-Programme kompilieren
oder »final«. Um diesen Release Build nun zu erstellen, wählen Sie im Menü PROJECT den Eintrag SET DEFAULT TARGET und wählen anschließend den entsprechenden Menüeintrag für das Release Target. Sie können das Release Target auch in der Combo-Box mit den Targets auswählen, die sich in Ihrem Projektfenster befindet. C++Builder bietet keine Unterstützung für verschiedene Build-Konfigurationen innerhalb eines Projekts, dennoch gibt es einen einfachen Weg, um einen Release Build zu erstellen. Um einen Release Build zu erzeugen, gehen Sie nach PROJEKTOPTIONEN ➝ COMPILER und klicken auf ENDGÜLTIG. Dadurch werden die Optimierungen und die InlineExpansion aktiviert und die Debug-Informationen deaktiviert. Wenn Sie eine IDE verwenden, die keine fertigen Debug- und Release-Konfigurationen bereitstellt, wie z.B. Dev-C++, oder falls Sie eine genauere Kontrolle über Ihre Projekteinstellungen haben möchten, finden Sie alle nötigen Informationen zur manuellen Konfiguration eines Release Builds in den Tabellen 1-27 bis 1-29. Tabelle 1-27: Codeoptimierungen in einer IDE aktivieren IDE
Konfiguration
Visual C++
Gehen Sie in den Eigenschaften Ihres Projekts auf KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ OPTIMIERUNG, und setzen Sie OPTIMIERUNG auf GESCHWINDIGKEIT MAXIMIEREN, GRÖßE ODER GESCHWINDIGKEIT BEVORZUGEN auf SCHNELLEN CODE BEVORZUGEN und KOMPLETTE PROGRAMMOPTIMIERUNG, SYSTEMINTERNE FUNKTIONEN AKTIVIEREN und FRAMEZEIGER UNTERDRÜCKEN auf JA. Lassen Sie die anderen Einstellungen auf dieser Seite unverändert.
CodeWarrior
Gehen Sie im Fenster TARGET SETTINGS auf CODE GENERATION ➝ GLOBAL OPTIMIZATIONS, und wählen Sie dort Level 4 aus.
C++Builder
Gehen Sie im Dialog PROJEKTOPTIONEN auf den Reiter COMPILER, und aktivieren Sie unter CODE-OPTIMIERUNG die Einstellung GESCHWINDIGKEIT.
Dev-C++
Suchen Sie in Tabelle 1-26 den Eintrag für GCC heraus, und befolgen Sie die Anleitung in Rezept 1.20.
Tabelle 1-28: Inline-Expansion in einer IDE aktivieren IDE
Konfiguration
Visual C++
Gehen Sie in den Eigenschaften Ihres Projekts auf KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ OPTIMIERUNG, und setzen Sie die Einstellung INLINEFUNKTIONSERWEITERUNG auf ALLES GEEIGNETE.
CodeWarrior
Gehen Sie im Fenster TARGET SETTINGS auf LANGUAGE SETTINGS ➝ C/C++ LANGUAGE. Setzen Sie die Einstellung INLINE DEPTH auf 8, aktivieren Sie AUTO-INLINE, und lassen Sie die anderen Inline-Optionen deaktiviert.
C++Builder
Gehen Sie im Dialog PROJEKTOPTIONEN auf den Reiter COMPILER, und deaktivieren Sie unter DEBUGGEN die Einstellung INLINE-EXPANSION DEAKTIVIEREN.
Dev-C++
Suchen Sie in Tabelle 1-26 den Eintrag für GCC heraus, und befolgen Sie die Anleitung in Rezept 1.20.
Tabelle 1-29: Debug-Informationen in einer IDE deaktivieren IDE
Konfiguration
Visual C++
Gehen Sie in den Eigenschaften Ihres Projekts auf KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ ALLGEMEIN, und setzen Sie die Einstellung DEBUGINFORMATIONSFORMAT auf DEAKTIVIERT.
Metrowerks
Gehen Sie im Fenster TARGET SETTINGS auf LANGUAGE SETTINGS ➝ LINKER ➝ X86 LINKER, und deaktivieren Sie die Einstellungen STORE FULL PATHS, LINK DEBUG INFO und DEBUG INLINE FUNCTIONS.
1.22 Einen Release Build erstellen | 101
Tabelle 1-29: Debug-Informationen in einer IDE deaktivieren (Fortsetzung) IDE
Konfiguration
C++Builder
Gehen Sie im Dialog PROJEKTOPTIONEN auf den Reiter COMPILER, und deaktivieren Sie die beiden Einstellungen DEBUG-INFORMATIONEN und ZEILENNUMMER-INFORMATION.
Dev-C++
Stellen Sie sicher, dass der Kommandozeilen-Parameter -g nicht übergeben wird (siehe Rezept 1.20).
Diskussion Die meisten Toolsets bieten mehrere Optionen zur Codeoptimierung an, während bei anderen gleich Dutzende zur Auswahl stehen. Welche Optimierungen Sie auswählen sollten, hängt sehr stark von den Anforderungen Ihres Projekts ab. In einer EmbeddedUmgebung bietet es sich beispielsweise eher an, eine Optimierung auszuwählen, durch die kleinere Programmdateien erzeugt werden, auch wenn dazu ein wenig Geschwindigkeit geopfert wird. In anderen Fällen kann es dagegen sein, dass die Geschwindigkeit für Sie absoluten Vorrang hat. Durch manche Optimierungen wird Ihr Programm auf der einen Plattform schneller, während es auf einer anderen langsamer wird. Vielleicht werden Sie sogar feststellen müssen, dass bestimmte Optionen Teile Ihres Programms schneller machen, während andere Teile dadurch langsamer werden. In den Tabellen 1-26 und 1-27 werden Optimierungen aufgeführt, mit denen Sie unter den meisten Umständen akzeptable Ergebnisse erzielen. Wenn Sie jedoch die für Ihr Projekt optimalen Optionen ermitteln möchten, sollten Sie sich genauestens über Ihre Anforderungen klar werden und dann die Dokumentation Ihres Toolsets zu Rate ziehen und ausgiebige Tests durchführen. Ähnlich sieht es auch bei der Inline-Expansion aus, wobei die Toolsets hier aber meist weniger Optionen anbieten als für andere Optimierungen.
Siehe auch Rezept 1.21
1.23 Eine Version der Laufzeitbibliothek auswählen Problem Ihr Toolset wird mit mehreren Versionen seiner Laufzeitbibliotheken ausgeliefert, und Sie möchten Ihren Compiler und Ihren Linker anweisen, eine ganz bestimmte Version der Laufzeitbibliothek zu verwenden.
Lösung Die Laufzeitbibliotheken, die mit einem bestimmten Toolset ausgeliefert werden, können sich darin unterscheiden, ob sie single- oder multithreaded sind, ob sie statische oder dynamische Bibliotheken sind und ob sie Debug-Informationenen enthalten oder nicht.
102 | Kapitel 1: C++-Programme kompilieren
Falls Sie Boost.Build verwenden, können Sie diese drei Einstellungen mit Hilfe der in Tabelle 1-15 aufgeführten Features threading, runtime-link und variant festlegen. Wenn Sie beispielsweise eine statisch gelinkte Laufzeitbibliothek verwenden möchten, nehmen Sie dazu einfach die Einstellung static in die Requirements Ihres Targets auf oder verwenden den Kommandozeilen-Parameter runtime-link=static. Um eine multithreading-fähige Laufzeitbibliothek zu verwenden, nehmen Sie die Einstellung multi in die Requirements Ihres Targets auf oder verwenden den Kommandozeilen-Parameter threading=multi. Falls Sie Ihr Projekt auf der Kommandozeile kompilieren, können Sie die in den Tabellen 1-30 bis 1-36 abgedruckten Compiler- und Linkeroptionen verwenden. Die Kommandozeilen-Parameter von Bibliotheksnamen für Debug- und Release-Konfigurationen sind meist recht ähnlich; die Buchstaben, die in den folgenden Tabellen in eckigen Klammern stehen, sollten nur im Falle einer Debug-Konfiguration angegeben werden. Die Namen der dynamischen Versionen der Laufzeitbibliotheken stehen in Klammern; diese Bibliotheken müssen zur Laufzeit verfügbar sein, falls das Projekt dynamisch gelinkt wird. Tabelle 1-30: Compileroptionen zur Auswahl der Laufzeitbibliothek mit Visual C++ oder Intel (Windows)
a b
Statisches Linken
Dynamisches Linken
Singlethreaded
-ML[d]a
Nicht verfügbar
Multithreaded
-MT[d]
-MD[d] ( msvcrt[d].dll, msvcr80[d].dll)b
Seit Visual Studio 2005 gelten die Optionen -ML und -MLd als veraltet und werden keine statisch gelinkten singlethreaded Laufzeitbibliotheken mehr ausgeliefert. Frühere Versionen von Visual C++ haben die DLLs msvcr71.dll, msvcr71d.dll, msvcr70.dll und msvcr70d.dll verwendet.
Tabelle 1-31: Compileroptionen zur Auswahl der Laufzeitbibliothek mit Metrowerks (Windows) Statisches Linken
Dynamisches Linken
Singlethreaded
-runtime ss[d]
Nicht verfügbar
Multithreaded
-runtime sm[d]
-runtime dm[d] (MSL_All-DLL90_x86[_D].dll)
Tabelle 1-32: Kommandozeilen-Parameter zur Auswahl der Laufzeitbibliothek mit CodeWarrior 10 für Max OS X Statisches Linken
Dynamisches Linken
Keine Angabe von Parametern nötig
Konsultieren Sie die Metrowerks-Dokumentation, um die Kommandozeilen-Parameter in Erfahrung zu bringen (MSL_All_Mach-O[_D].dylib).
1.23 Eine Version der Laufzeitbibliothek auswählen
| 103
Tabelle 1-33: Compiler- und Linkeroptionen zur Auswahl der Laufzeitbibliothek mit Borland
a
Statisches Linken
Dynamisches Linken
Singlethreaded
-WM
-WM- -WR -WC a (cc3260.dll)
Multithreaded
-WM
-WM -WR -WC a (cc3260mt.dll)
Der Parameter -WC muss nur dann angegeben werden, wenn eine Konsolenanwendung erstellt werden soll.
Tabelle 1-34: Compileroptionen zur Auswahl der Laufzeitbibliothek mit Digital Mars (alle Laufzeitbibliotheken sind multithreaded) Statisches Linken
Dynamisches Linken
Keine Angabe von Parametern nötig
-ND -D_STLP_USE_DYNAMIC_LIB (sccrt70.dll, stlp45dm.dll)
Tabelle 1-35: Linkeroptionen zur Auswahl der Laufzeitbibliothek mit GCC
a
Statisches Linken
Dynamisches Linken
-static a
Keine Angabe von Parametern nötig
Diese Option deaktiviert das dynamische Linken komplett, und nicht nur das dynamische Linken mit den Laufzeitbibliotheken.
Wenn Sie beispielsweise einen dynamisch gelinkten Release Build der Laufzeitbibliothek von Visual C++ verwenden möchten, müssen Sie dazu die Compileroption -MD angeben. Um unter Windows einen statisch gelinkten singlethreaded Debug Build von Metrowerks’ Laufzeitbibliothek zu verwenden, müssen Sie die Compileroption -runtime ssd übergeben. Um einen singlethreaded, dynamisch gelinkten Build von Borlands Laufzeitbibliothek zu verwenden, müssen Sie die Optionen -WM- -WR -WC an den Compiler und an den Linker übergeben. In Tabelle 1-36 finden Sie die nötigen Anleitungen, um in einer IDE die gewünschte Laufzeitbibliothek auszuwählen. Tabelle 1-36: Eine Laufzeitbibliothek in einer IDE auswählen IDE
Konfiguration
Visual C++
Gehen Sie in den Eigenschaften Ihres Projekts auf KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ CODEGENERIERUNG, und wählen Sie unter LAUFZEITBIBLIOTHEK den entsprechenden Eintrag aus.
CodeWarrior
Wenn es sich bei Ihrem Projekt um eine dynamische Bibliothek handelt, müssen Sie die Objektdatei /usr/lib/dylib1.o und die Bibliotheken MSL_Shared_AppAndDylib_Runtime[_D].lib und MSL_All_Mach-O[_D].dylib in Ihr Projekt aufnehmen und alle Bibliotheken der Form MSL__Mach-O[_D].lib entfernen. Bei einem Projekt mit einer ausführbaren Programmdatei nehmen Sie die Objektdatei /usr/lib/crt1.o und die Bibliotheken MSL_Shared_AppAndDylib_Runtime[_D].lib und MSL_All_Mach-O[_D].dylib in Ihr Projekt auf und entfernen alle Bibliotheken der Form MSL__Mach-O[_D].lib.
C++Builder
Ob ein Projekt single- oder multithreaded sein soll, müssen Sie schon beim Anlegen des Projekts festlegen. Um anzugeben, ob die Laufzeitbibliothek statisch oder dynamisch gelinkt werden soll, wählen Sie im Fenster PROJEKTOPTIONEN den Reiter LINKER AKTIVIEREN oder deaktivieren die Option DYNAMISCHE RTL verwenden.
104 | Kapitel 1: C++-Programme kompilieren
Tabelle 1-36: Eine Laufzeitbibliothek in einer IDE auswählen (Fortsetzung) IDE
Konfiguration
Dev-C++
Wenn Sie eine statisch gelinkte Laufzeitbibliothek auswählen möchten, geben Sie dazu einfach den Kommandozeilen-Parameter -static an (siehe Rezept 1.20).
Diskussion Eine Laufzeitbibliothek enthält Implementierungen von Hilfsfunktionen, die von einem Programm zur Laufzeit benötigt werden. Laufzeitbibliotheken enthalten meist Implementierungen der Funktionen der C-Standardbibliothek, plattformspezifische Funktionen zum Zugriff auf Betriebssystem-Dienste wie Threads und Dateisysteme sowie Funktionen, die die Infrastruktur für Sprach-Features von C++ wie Runtime Type Information (RTTI) und Exception Handling bereitstellen. In den meisten Fällen ist es gut, wenn man möglichst viele Optionen offen hat; die starke Zunahme verfügbarer Versionen von Laufzeitbibliotheken bringt aber einige Probleme mit sich. Das Hauptproblem besteht darin sicherzustellen, dass alle Komponenten einer Anwendung – statische Bibliotheken, dynamische Bibliotheken und ausführbare Programmdatei – dieselbe Version einer Laufzeitbibliothek verwenden. Andernfalls kann es zu Problemen beim Linken des Programms oder sogar zu nur schwer zu diagnostizierenden Laufzeitfehlern kommen. Wenn Sie Bibliotheken von Dritten verwenden, können Sie nicht immer selbst bestimmen, gegen welche Version der Laufzeitbibliothek gelinkt wird. In solchen Fällen kann es vorkommen, dass Sie gezwungen sind, mehrere Versionen der Laufzeitbibliothek in derselben Anwendung zu verwenden.
Wie entscheidet man sich nun für die richtige Laufzeitbibliothek? Die Entscheidung über zwei der zur Verfügung stehenden Optionen – nämlich single- oder multithreaded und Debug- oder Release-Version – lässt sich recht einfach treffen. Wenn Ihr Projekt mehrere Threads verwendet oder von einer Bibliothek abhängt, die mit mehreren Threads arbeitet, müssen Sie eine multithreaded Version der Laufzeitbibliothek verwenden, falls Ihr Toolset denn eine anbietet. Wenn Sie aus mehreren Threads heraus Funktionen der Laufzeitbibliothek aufrufen und die Laufzeitbibliothek ohne Unterstützung für Threads kompiliert wurde, kann das zu einem unberechenbaren Laufzeitverhalten führen. Ebenso sollten Sie, wenn Sie einen Debug Build erstellen, auch, wenn verfügbar, eine Debug-Version der Laufzeitbibliothek verwenden. Die letzte Entscheidung – ob man eine statische oder dynamische Laufzeitbibliothek verwenden soll – ist dagegen nicht ganz so leicht zu treffen. Die Verwendung einer statisch gelinkten Laufzeitbibliothek hat mehrere Vorteile. Erstens kann dadurch die Gesamtgröße Ihrer Anwendung kleiner werden – zumindest, wenn Sie sonst eine dynamische Laufzeitbibliothek ausliefern müssten –, da nur die Funktionen ins Programm gelinkt werden, die Sie auch verwenden. (Wenn Sie wissen, dass die dynamische Laufzeitbiblio-
1.23 Eine Version der Laufzeitbibliothek auswählen
| 105
thek auf dem Zielsystem bereits vorhanden ist, kann die Gesamtgröße der Anwendung durch das statische Linken mit der Laufzeitbibliothek möglicherweise sogar größer werden.) Zweitens können Sie durch die Verwendung der statischen Version der Laufzeitbibliothek Versionsproblemen aus dem Weg gehen, die auftreten können, wenn mehrere verschiedene Versionen derselben dynamischen Bibliothek auf demselben System vorhanden sind. Das Linken mit der dynamischen Laufzeitbibliothek hat aber auch seine Vorteile. Das liegt daran, dass es sehr effektiv ist, eine Anwendung als eine Sammlung dynamischer Bibliotheken zu organisieren. Das erlaubt es zum einen, Teile der Anwendung zu aktualisieren, ohne dass dazu die gesamte Anwendung neu installiert werden muss. Des Weiteren kann manchmal die Performance einer Anwendung signifikant verbessert werden, wenn man das unter Windows vorhandene Feature des verzögerten Ladens verwendet. Da aber alle Komponenten einer Anwendung dieselbe Version der Laufzeitbibliothek verwenden sollten, ist es zu empfehlen, das sobald eine Anwendung eine dynamische Bibliothek verwendet, alle Komponenten dieser Applikation eine dynamische Version der Laufzeitbibliothek verwenden. Demzufolge wird es durch die Verwendung einer dynamisch gelinkten Laufzeitbibliothek einfacher, eine Anwendung zu modularisieren. Ich empfehle Ihnen, Ihre Anwendungen nach Möglichkeit dynamisch zu linken. Wie ich zuvor schon erwähnt habe, ist das statische Linken manchmal jedoch vorzuziehen. Manchmal kann man unmöglich im Voraus wissen, welche Art des Linkens eher geeignet ist, weil Sie z.B. nicht wissen, wie eine Bibliothek, die Sie geschrieben haben, später verwendet wird. In diesem Fall sieht eine gängige Lösung so aus, dass man mehrere Versionen der Bibliothek bereitstellt, von der jede gegen eine andere Version der Laufzeitbibliothek gelinkt ist.
Siehe auch Rezept 1.4, 1.5, 1.21 und 1.25
1.24 Die strikte Einhaltung des C++-Standards erzwingen Problem Sie möchten erreichen, dass Ihr Compiler nur solche Programme akzeptiert, die sich an den Standard für die C++-Programmiersprache halten.
Lösung Kommandozeilen-Parameter, mit denen die strikte Standardkonformität erzwungen werden kann, finden Sie in Tabelle 1-37. Anleitungen zum Erzwingen strikter Standardkonformität in einer IDE finden Sie in Tabelle 1-38.
106 | Kapitel 1: C++-Programme kompilieren
Manche der Compileroptionen, die ich in Tabelle 1-6 vorgestellt habe, können auch zu den Optionen für Standardkonformität gezählt werden. Dazu gehören beispielsweise Optionen, die grundlegende Sprach-Features wie WideCharacter-Unterstützung, Exceptions und Runtime Type Information aktivieren. Diese Optionen werden in Tabelle 1-37 nicht noch einmal aufgeführt. Tabelle 1-37: Strikte Standardkonformität per Kommandozeilen-Parameter erzwingen
a
b
Toolset
Compileroptionen
GCC
-ansi -pedantic-errors
Visual C++
-Za
Intel (Windows)
-Za -Qms0
Intel (Linux)
-strict-ansi a
Metrowerks
-ansi strict -iso_templates on -msext off
Comeau (Windows)
--A
Comeau (Unix)
--strict oder -A
Borland
-Ab
Digital Mars
-A
Der Intel-Compiler für Linux hat vor Version 9.0 stattdessen die Option -strict_ansi verwendet. Wenn die Option -strict-ansi bzw. -strict_ansi verwendet wird, kann es notwendig sein, Intels Standardbibliothek mit der Option -cxxlib-icc zu aktivieren. Wenn der Parameter -A übergeben wird, kann es vorkommen, dass einige der Standardheader der STLPort-Bibliothek nicht fehlerfrei kompiliert werden können.
Tabelle 1-38: Strikte Standardkonformität in einer IDE erzwingen IDE
Konfiguration
Visual C++
Gehen Sie in den Eigenschaften Ihres Projekts auf KONFIGURATIONSEIGENSCHAFTEN ➝ C/C++ ➝ SPRACHE, und setzen Sie die Einstellungen SPRACHERWEITERUNGEN AKTIVIEREN, WCHAR_T ALS INTEGRIERTEN TYPEN BEHANDELN und ÜBEREINSTIMMUNG IN EINEM FOR-SCHLEIFENBEREICH ERZWINGEN auf JA.
Metrowerks
Gehen Sie im Fenster TARGET SETTINGS auf LANGUAGE SETTINGS ➝ C/C++ LANGUAGE, und aktivieren Sie die Einstellungen ISO TEMPLATE PARSER, ANSI STRICT und ANSI KEYWORDS ONLY. Achten Sie darauf, dass die Einstellungen ENABLE C++ EXCEPTIONS, ENABLE RTTI SUPPORT, ENABLE BOOL SUPPORT und ENABLE WCHAR_T SUPPORT aktiviert sind.
C++Builder
Wählen Sie im Dialog PROJEKTOPTIONEN den Reiter ERWEITERTE COMPILER-OPTIONEN, und aktivieren Sie unter SPRACHKONVENTION die Einstellung ANSI.
Dev-C++
Suchen Sie in Tabelle 1-37 den Eintrag für GCC heraus, und befolgen Sie die Anleitung in Rezept 1.20.
Diskussion Die C++-Programmiersprache wurde 1998 von der International Standards Organization (ISO) standardisiert; im selben Jahr wurde der ISO-Standard vom American National Standards Institute (ANSI) übernommen. Im Jahr 2003 wurde dann eine zweite Ausgabe des Standards freigegeben. In dieser zweiten Ausgabe wurden lediglich Fehler der ersten
1.24 Die strikte Einhaltung des C++-Standards erzwingen | 107
Ausgabe korrigiert und gewisse Unklarheiten beseitigt, aber es wurden keine neuen Sprach-Features eingeführt. Momentan wird an einer aktualisierten Version des C++Standards gearbeitet, die mehrere wichtige Neuerungen und eine erweiterte Standardbibliothek beinhalten wird. Als der Standard 1998 verabschiedet wurde, konnte kein Compiler die darin festgelegten Anforderungen auch nur annähernd erfüllen – und das, obwohl viele Compiler als »ANSI-konform« beworben wurden. Im Laufe der Jahre haben die Hersteller aber ausgiebig an der Anpassung ihrer Compiler an den Standard gearbeitet. Im September 2005 waren die neusten Versionen der Compiler von GNU, Microsoft, Intel, Metrowerks und Comeau hochgradig standardkonform. Comeau und Intel können mit ihrer Unterstützung exportierter Templates sogar behaupten, beinahe zu 100% standardkonform zu sein.5 Kein Compiler kann die strikte Einhaltung des Standards durchsetzen, wenn es darum geht, die Kompilierung jedes ungültigen Programms abzulehnen. Das liegt nicht nur daran, dass kein Compiler zu 100% standardkonform ist: Ein viel fundamentalerer Grund ist, dass der C++-Standard nicht vorschreibt, dass ein Compiler die Kompilierung jedes ungültigen Programms ablehnen muss. Es gibt eine genau festgelegte Liste von Fällen, in denen der Compiler eine Fehlermeldung (diagnostic) ausgeben muss, die dem Benutzer mitteilt, dass das Programm ungültig ist. Bei vielen ungültigen Programmen ist die Ausgabe einer Fehlermeldung jedoch nicht erforderlich. Diese Programme rufen zur Laufzeit das hervor, was der Standard als undefiniertes Verhalten bezeichnet. Und selbst in den Fällen, in denen der Compiler eine Fehlermeldung ausgeben muss, darf er anschließend dennoch mit der Kompilierung fortfahren und den Kompiliervorgang möglicherweise mit der erfolgreichen Erstellung einer ausführbaren Programmdatei oder einer Bibliothek abschließen. Der Hauptgrund, warum der Standard nicht vorschreibt, dass ein Compiler alle nichtkonformen Programme ablehnen muss, ist der, dass die Nicht-Konformität eines Programms oftmals schwierig – oder manchmal sogar unmöglich – zu entdecken ist. Ein anderer Grund, der später noch näher erläutert wird, ist der, dass nichtkonforme Programme manchmal recht nützlich sein können. Ich empfehle Ihnen, dass Sie Ihren Compiler, wann immer das möglich ist, mit der entsprechenden Option für die strikte Befolgung des Standards aufrufen. Allerdings gibt es auch Situationen, in denen es angebracht ist, ein Programm ohne diese Option zu kompilieren. Lassen Sie uns nun ein paar Beispiele für nichtkonformen Code anschauen, um diese Problematik besser zu verstehen. Zuerst einmal gibt es Code, der in einem früheren Dialekt von C++, also bevor die Sprache standardisiert wurde, gültig war. Beispielsweise war es in den Anfangszeiten von C++ so, dass sich der Gültigkeitsbereich einer Variablen, die im Initialisierungsteil einer 5 Warum nur beinahe? Der Grund hierfür ist, dass selbst die Produkte von Comeau und Intel Bugs haben, und darüber hinaus ist die Interpretation gewisser Teile des Standards umstritten.
108 | Kapitel 1: C++-Programme kompilieren
for-Schleife deklariert wurde, bis ans Ende desjenigen Blocks erstreckte, in dem sich die
Schleife befand: // WARNUNG: Ungültiger Code! int main( ) { for (int i = 0; i < 10; ++i) ; int j = i; // j == 10 }
Dieses Verhalten wird vom Standard nicht erlaubt und bietet auch keine Vorteile gegenüber den Regeln für Gültigkeitsbereiche, die im Standard definiert sind. Normalerweise ist es nur dann nötig, Code nach diesen Gültigkeitsregeln zu kompilieren, wenn man ältere Anwendungen pflegen muss. Eine andere Kategorie von nichtkonformem Code ist solcher Code, der experimentelle Sprach-Features verwendet, die möglicherweise in eine spätere Version des C++-Standards einfließen werden. Viele Compiler bieten beispielsweise einen ganzzahligen Datentyp long long an, der immer eine garantierte Größe von mindestens 64 Bit hat. Oder um noch ein anderes Beispiel zu nennen: Mehrere Compiler besitzen einen eingebauten Operator typeof, der dieselbe Syntax wie der Operator sizeof verwendet und den Typ eines Ausdrucks zurückliefert. Wahrscheinlich werden diese beiden Features in der nächsten Version des C++-Standards auftauchen, wobei sich der Name des Operators typeof wohl auch noch ändern wird; möglicherweise zu decltype. Seien Sie sehr vorsichtig mit der Verwendung derartiger Erweiterungen: Schneller als Sie denken kann es passieren, dass Sie Ihren Code auf eine andere Plattform portieren müssen, auf der diese Erweiterungen überhaupt nicht oder mit einer anderen Semantik implementiert sind. Eine dritte Kategorie nichtkonformen Codes stellt solcher Code dar, der plattformspezifische Spracherweiterungen verwendet, die notwendig sind, um bestimmte Betriebssystem-Features zu nutzen. In diese Kategorie fallen beispielsweise die Attribute _ _declspec(dllexport) und _ _declspec(dllimport), die unter Windows zum Erstellen dynamischer Bibliotheken mehr als nützlich sind, sowie die Attribute _ _stdcall, _ _fastcall und _ _cdecl, mit denen unter Windows die zu verwendende Aufrufkonvention festgelegt werden kann. Obwohl es sich dabei um Spracherweiterungen handelt, akzeptieren die meisten Windows-Compiler Code mit solchen Anweisungen auch dann, wenn sie sich im Modus für strikte Standardkonformität befinden. Die letzte Kategorie nichtkonformen Codes stellt Code dar, der gegen den C++-Standard verstößt, aber nach einem anderen sinnvollen Standard vollkommen gültig ist. Ein gewichtiges Beispiel für einen solchen Standard ist der Standard C++/CLI, der sich derzeit bei der ECMA in den letzten Stadien des Standardisierungsprozesses befindet. C++/ CLI ist eine Erweiterung von C++, die die Schnittstelle zwischen C++ und der Command Language Infrastructure, dem Kern von Microsofts .NET Framework, bildet. Wenn ein standardkonformer Compiler ein Programm kompiliert, das bestimmte Erweiterungen
1.24 Die strikte Einhaltung des C++-Standards erzwingen | 109
von C++/CLI verwendet, muss er einen Fehler erzeugen, darf aber dennoch eine gültige C++/CLI-Anwendung erstellen, sofern er den C++/CLI-Standard denn unterstützt. Wenn Sie nicht standardkonformen Code kompilieren müssen, sollten Sie zuerst versuchen, ihn mit Hilfe der Optionen in den Tabellen 1-37 und 1-38 zu kompilieren. Wenn das nicht funktioniert, dann können Sie es mit den abgestuften Einstellungen zur Standardkonformität versuchen, die die meisten Compiler anbieten. Dadurch können Sie den Compiler anweisen, dass er nur ganz bestimmte nichtkonforme Konstrukte kompiliert, während er andere nach wie vor nicht akzeptiert. Comeau bietet beispielswweise die Option --long_long an, mit der man den Compiler explizit anweisen kann, den Typ long long zuzulassen. Schließlich bieten manche Compiler noch Optionen an, mit denen man sie dazu veranlassen kann, bei vielen Verletzungen des Standards anstatt eines Fehlers nur eine Warnung zu melden. So besitzt GCC zu diesem Zweck beispielsweise die Option -pedantic, und Comeau verwendet dafür unter Windows die Option --a und auf anderen Plattformen die Option --strict_warnings oder -a .
Siehe auch Rezept 1.2
1.25 Eine Quelldatei automatisch gegen eine bestimmte Bibliothek linken lassen Problem Sie haben eine Bibliothek geschrieben und möchten diese in Form von Headerdateien und von vorkompilierten statischen oder dynamischen Bibliotheken vertreiben. Allerdings sollen die Benutzer Ihrer Bibliothek beim Linken ihrer Programme nicht die Namen der Binärdateien angeben müssen.
Lösung Wenn Sie für Windows programmieren und als Toolset Visual C++, Intel, Metrowerks, Borland oder Digital Mars verwenden, können Sie die Anweisung pragma comment in den Headerdateien Ihrer Bibliothek verwenden, um die Namen und optional auch die absoluten Dateinamen der Binärdateien anzugeben, gegen die der Code, der diese Headerdateien einbindet, gelinkt werden soll. Nehmen Sie beispielsweise an, Sie möchten die Bibliothek aus Beispiel 1-1 als statische Bibliothek libjohnpaul.lib zusammen mit der Headerdatei johnpaul.hpp vertreiben. In diesem Fall könnten Sie die Headerdatei so ändern wie in Beispiel 1-26.
110 | Kapitel 1: C++-Programme kompilieren
Beispiel 1-26: Verwendung von pragma comment #ifndef JOHNPAUL_HPP_INCLUDED #define JOHNPAUL_HPP_INCLUDED #pragma comment(lib, "libjohnpaul") void johnpaul( ); #endif // JOHNPAUL_HPP_INCLUDED
Mit dieser Änderung werden die Linker von Visual C++, Intel, Metrowerks, Borland und Digital Mars automatisch nach der Bibliothek libjohnpaul.lib suchen, wenn Code gelinkt werden soll, der die Headerdatei johnpaul.hpp einbindet.
Diskussion In mancher Hinsicht stellt das Linken beim Erstellen eines Programms eine kritischere Phase dar als das Kompilieren. Eines der häufigsten Probleme beim Linken entsteht dann, wenn der Linker die falsche Version einer Bibliothek findet. Dies ist besonders unter Windows ein Problem, da dort die Laufzeitbibliotheken – und die Bibliotheken, die von ihnen abhängen – oftmals in verschiedenen Versionen ausgeliefert werden. Aus diesem Grund haben Bibliotheken unter Windows oftmals Namen, die die Build-Konfiguration der jeweiligen Bibliotheksdatei mit angeben. Während dadurch die Versionskonflikte vermindert werden, wird aber auch das Linken schwieriger, da Sie dem Linker den richtigen Bibliotheksnamen übergeben müssen. Genau aus diesem Grund ist die Anweisung pragma comment ein sehr mächtiges Hilfsmittel. Unter anderem können Sie dadurch den korrekten Namen der Bibliotheksdatei in der Headerdatei angeben, so dass der Benutzer sich gar nicht erst um die von Ihnen gewählte Namenskonvention kümmern muss. Wenn Sie Ihren Installationsprozess zusätzlich noch so gestalten, dass die Binärdateien automatisch in ein Verzeichnis kopiert werden, das vom Linker automatisch durchsucht wird – wie z.B. das Unterverzeichnis lib in den Installationsverzeichnissen von Visual C++, CodeWarrior oder C++Builder –, werden andere Programmierer Ihre Bibliothek verwenden können, indem sie einfach nur Ihre Headerdateien einbinden. So weit, so gut. Es gibt nur noch ein Problem: pragma comment wird nicht von allen Compilern unterstützt. Wenn Sie portablen Code schreiben möchten, sollten Sie ein Pragma erst dann aufrufen, wenn Sie sichergestellt haben, das es vom verwendeten Toolset unterstützt wird. Dazu könnten Sie die Datei johnpaul.hpp beispielsweise wie folgt modifizieren: #ifndef JOHNPAUL_HPP_INCLUDED #define JOHNPAUL_HPP_INCLUDED #if defined(_MSC_VER) || \ defined(_ _ICL) || \ defined(_ _MWERKS_ _) && defined(_WIN32) || \
1.25 Eine Quelldatei automatisch gegen eine bestimmte Bibliothek linken lassen | 111
defined(_ _BORLANDC_ _) \ defined(_ _DMC_ _) \ /**/ # pragma comment(lib, "libjohnpaul") #endif void johnpaul( ); #endif // JOHNPAUL_HPP_INCLUDED
Dieses Beispiel ist bereits ziemlich kompliziert, aber leider ist es immer noch nicht vollkommen korrekt. Manche Compiler, die die Anweisung pragma comment nicht unterstützen, definieren das Makro _MSC_VER, um mit Visual C++ kompatibel zu sein. Glücklicherweise bietet Boost hier eine einfache Lösung: #ifndef JOHNPAUL_HPP_INCLUDED #define JOHNPAUL_HPP_INCLUDED #define BOOST_LIB_NAME libjohnpaul #define BOOST_AUTO_LINK_NOMANGLE #include void johnpaul( ); #endif // JOHNPAUL_HPP_INCLUDED
Die Zeile #define BOOST_LIB_NAME libjohnpaul
gibt den Namen der Bibliothek an, während die Zeile #define BOOST_AUTO_LINK_NOMANGLE
festlegt, dass Sie nicht die Namenskonvention von Boost verwenden möchten. Schließlich wird in der Zeile #include
das pragma comment nur für die Compiler aufgerufen, die es auch unterstützen.
Siehe auch Rezept 1.23
1.26 Exportierte Templates verwenden Problem Sie möchten ein Programm kompilieren, das exportierte Templates verwendet, d.h., es deklariert die Templates in Headerdateien und verwendet dazu das Schlüsselwort export, während die Templates selbst in .cpp-Dateien implementiert werden.
112 | Kapitel 1: C++-Programme kompilieren
Lösung Zuerst kompilieren Sie die .cpp-Dateien mit den Template-Implementierungen zu Objektdateien, wobei Sie dem Compiler die zur Verwendung exportierter Templates nötigen Kommandozeilen-Parameter übergeben. Als Nächstes kompilieren und linken Sie die .cpp-Dateien, die die exportierten Templates verwenden, wobei Sie dem Compiler und dem Linker die zur Verwendung exportierter Templates nötigen Kommandozeilen-Parameter übergeben und zusätzlich die Kommandozeilen-Parameter zur Angabe der Verzeichnisse mit den Template-Implementierungen. Die zur Verwendung exportierter Templates nötigen Parameter finden Sie in Tabelle 1-39. Die Parameter zur Angabe der Verzeichnisse mit den Template-Implementierungen finden Sie in Tabelle 1-40. Falls Ihr Toolset in diesen Tabellen nicht auftaucht, bietet es wahrscheinlich keine Unterstützung für exportierte Templates. Tabelle 1-39: Kommandozeilen-Parameter zur Aktivierung exportierter Templates Toolset
a
Parameter
Comeau (Unix)
--export, -A oder --strict
Comeau (Windows)
--export oder --A
Intel (Linux)
-export oder -strict-ansi a
Der Intel-Compiler für Linux hat vor Version 9.0 stattdessen den Parameter -strict_ansi verwendet.
Tabelle 1-40: Kommandozeilen-Parameter zur Angabe von Pfaden von Template-Implementierungen Toolset
Parameter
Comeau
--template_directory=
Intel (Linux)
-export_dir
Nehmen Sie beispielsweise an, Sie möchten das in Beispiel 1-27 gezeigte Programm kompilieren, das aus diesen drei Dateien besteht: • Die Datei plus.hpp enthält die Deklaration eines exportierten Funktions-Templates namens plus( ). • Die Datei plus.cpp enthält die Definition von plus( ). • Die Datei test.cpp bindet die Deklaration – aber nicht die Definition – von plus( ) ein und definiert eine Funktion main( ), in der plus( ) verwendet wird. Beispiel 1-27: Ein einfaches Programm, das exportierte Templates verwendet plus.hpp: #ifndef PLUS_HPP_INCLUDED #define PLUS_HPP_INCLUDED
1.26 Exportierte Templates verwenden
| 113
Beispiel 1-27: Ein einfaches Programm, das exportierte Templates verwendet (Fortsetzung) export template T plus(const T& lhs, const T& rhs); #endif // #ifndef PLUS_HPP_INCLUDED plus.cpp: #include "plus.hpp" template T plus(const T& lhs, const T& rhs) { return rhs + lhs; } test.cpp: #include #include "plus.hpp" int main( ) { std::cout