Im Katalog suchen

Linux - Wegweiser zur Installation & Konfiguration, 3. Auflage

Online-Version

Copyright © 2000 by O'Reilly Verlag GmbH & Co.KG

Bitte denken Sie daran: Sie dürfen zwar die Online-Version ausdrucken, aber diesen Druck nicht fotokopieren oder verkaufen. 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.

Wünschen Sie mehr Informationen zu der gedruckten Version des Buches Linux - Wegweiser zur Installation & Konfiguration oder wollen Sie es bestellen, dann klicken Sie bitte hier.


vorheriges Kapitel Inhaltsverzeichnis Stichwortverzeichnis nächstes Kapitel

Programmieren mit gcc

Die Programmiersprache C ist diejenige, die bei der Entwicklung von Unix-Software mit Abstand am häufigsten eingesetzt wird. Vielleicht liegt das daran, daß Unix selbst ursprünglich in C entwickelt wurde - C ist die »Muttersprache« von Unix. Die C-Compiler unter Unix haben schon immer die Standards für andere Sprachen und Hilfsmittel wie Linker, Debugger usw. gesetzt. Konventionen, die mit den ersten C-Compilern eingeführt wurden, haben sich in der gesamten Unix-Programmierung weitgehend unverändert erhalten. Wer den C-Compiler kennt, der kennt auch Unix selbst. Bevor wir allzusehr abheben, lassen Sie uns lieber auf die Details eingehen.

Der C-Compiler von GNU, gcc, ist einer der vielseitigsten und fortschrittlichsten aller verfügbaren Compiler. Anders als andere C-Compiler (zum Beispiel die mit AT&T- oder BSD-Distributionen ausgelieferten oder solche von Drittherstellern) unterstützt gcc alle modernen C-Standards - etwa ANSI-C - sowie viele Erweiterungen, die nur in gcc zu finden sind. Glücklicherweise kann gcc trotzdem zu älteren C-Compilern und älteren Methoden der C-Programmierung kompatibel gemacht werden. Es gibt sogar ein Werkzeug namens protoize, das Ihnen dabei hilft, Funktionsprototypen für C-Programme im alten Stil zu schreiben.

gcc ist gleichzeitig ein C++-Compiler. Für diejenigen unter Ihnen, die sich lieber in der undurchschaubaren objektorientierten Welt aufhalten, wird C++ mit allem Drum und Dran unterstützt - einschließlich solcher Eigenschaften wie Methoden-Templates aus dem C++ von AT&T. Es stehen auch komplette Klassenbibliotheken für C++ (etwa die vielen Programmierern bekannte iostream) zur Verfügung.

Wer sich gerne mit seltenen Dingen umgibt, findet in gcc auch Unterstützung für Objective-C, einen objektorientierten Ableger von C, der nie sehr populär wurde. Aber das ist noch längst nicht alles, wie wir sehen werden.

Es gibt noch einen weiteren Compiler, den egcs. egcs ist keine vollständige Neuentwicklung, sondern ein Abkömmling von gcc. egcs hat im Gegensatz zu gcc einige fortgeschrittene Optimierungsfunktionen und zeichnet sich insbesondere dann aus, wenn es um neuere C++-Sprachmerkmale wie Templates oder Namensräume geht. Wenn Sie ernsthaft mit C++ programmieren wollen, dann sollten Sie egcs in Betracht ziehen. Unglücklicherweise gibt es ein Problem mit Kerneln der Version 2.0, die sich nicht mit dem egcs kompilieren lassen. Neuere Kernel aus den 2.1-, 2.2- und 2.3-Reihen haben dieses Problem nicht. Manche Distributoren haben sich deswegen aber entschieden, den herkömmlichen gcc für die C-Kompilierung und egcs für die C++-Kompilierung vorzusehen. Näheres zu egcs erfahren Sie unter http://egcs.cygnus.com .

Die Free Software Foundation hat vor kurzem angekündigt, daß egcs ihr Default-Compiler werden und damit seinen eigenen Vorfahren gcc ersetzen soll. Damit wird die Teilung in egcs und gcc in der Zukunft dann wieder aufgehoben.

In diesem Abschnitt wollen wir zeigen, wie Sie mit gcc unter Linux Programme kompilieren und linken. Wir gehen davon aus, daß Sie mit der Programmierung in C/C++ vertraut sind, aber wir setzen nicht voraus, daß Sie bereits Erfahrungen mit der Programmierumgebung unter Unix gemacht haben. Diese Erfahrung sollen Sie hier mit uns machen.

Ein schneller Überblick

Bevor wir Ihnen auch die letzten Details des gcc selbst verraten, wollen wir ein einfaches Beispiel vorführen und alle Schritte zeigen, die Sie unternehmen, wenn Sie ein C-Programm auf einem Unix-System kompilieren.

Lassen Sie uns von folgendem Programmcode ausgehen - ein weiteres Exemplar des wohlbekannten »Hello, World!«-Programms:

#include <stdio.h> int main() { (void)printf("Hello, World!\n"); return 0; /* Just to be nice */ }

Um aus diesem Programm lebenden, ausführbaren Code zu machen, bedarf es mehrerer Schritte. Die meisten dieser Schritte lassen sich in einem gcc-Befehl zusammenfassen, aber auf die Details gehen wir erst später in diesem Kapitel ein.

Zunächst muß der gcc-Compiler aus diesem Quellcode eine Objektdatei erzeugen. In der Objektdatei steht im wesentlichen der Rechnercode, der diesem C-Code entspricht. Die Datei enthält Code, der den Stack für den main()-Aufruf einrichtet, die mysteriöse Funktion printf() aufruft, sowie Code, der den Wert 0 zurückliefert.

Der nächste Schritt besteht darin, die Objektdatei zu binden (linken), um eine ausführbare Datei zu erhalten. Es fällt nicht schwer zu erraten, daß dieser Schritt vom Linker erledigt wird. Die Aufgabe des Linkers besteht darin, Objektdateien mit Code aus den Libraries (Bibliotheken) zu binden und eine ausführbare Datei zu erzeugen. Der Objektcode aus unserem Beispiel stellt noch kein ausführbares Programm dar; es muß vor allen Dingen der Code für printf() dazugebunden werden. Außerdem gehören noch diverse Initialisierungsroutinen zu einem kompletten Programm, die ein Normalsterblicher nie zu sehen bekommt.

Wo kommt der Code für printf() her? Aus den Bibliotheken. Man kann nicht allzulange über den gcc schreiben, ohne die Libraries zu erwähnen. Eine Library ist im wesentlichen eine Sammlung von vielen Objektdateien mit einem Index. Wenn der Linker nach dem Code für printf() sucht, schaut er in den Indizes aller Bibliotheken nach, die er dazubinden soll. Der Linker findet die Objektdatei, in der die Funktion printf() enthalten ist, extrahiert diese Objektdatei (die ganze Datei, die noch viel mehr als nur die Funktion printf() enthalten kann) und bindet sie mit der ausführbaren Datei.

In Wirklichkeit sind die Dinge viel komplizierter. Wir haben bereits erwähnt, daß Linux zwei Arten von Bibliotheken kennt - static (statisch) und shared (etwa: gemeinsam genutzt). Was wir soeben beschrieben haben, sind statische Bibliotheken - Bibliotheken, in denen der eigentliche Code der Subroutinen mit der ausführbaren Datei gebunden wird. Der Code für Routinen wie printf() kann allerdings ziemlich lang sein. Weil viele Programme häufig aufgerufene Routinen aus den Libraries benutzen, ist es sinnlos, daß jede ausführbare Datei eine eigene Kopie des Library-Codes enthalten soll. Hier kommen die Shared Libraries ins Spiel.

Bei der Benutzung von Shared Libraries ist der gesamte Code für die gemeinsam genutzten Routinen in einer einzigen Bibliothek auf der Festplatte enthalten - der »Library-Image-Datei« (etwa: Library-Abbild). Wenn ein Programm mit einer Shared Library gebunden wird, bekommt die ausführbare Datei lediglich »Stub Code« (etwa: Stummel-Code) statt des eigentlichen Codes der Routinen mit. Dieser Stub-Code teilt dem Programm-Lader (program loader) mit, an welcher Stelle der Platte er zur Laufzeit den Library-Code finden kann (in der Image-Datei). Wenn unser nettes »Hello, World!«-Programm ausgeführt wird, bemerkt der Programm-Lader also, daß das Programm mit einer Shared Library gebunden wurde. Er findet dann die Image-Datei und lädt den Code für die Bibliotheksroutinen (zum Beispiel printf()) zusammen mit dem eigentlichen Programmcode. Der Stub-Code teilt dem Loader mit, wo er den Code für printf() innerhalb der Image-Datei finden kann.

Selbst dies ist noch eine stark vereinfachte Darstellung dessen, was wirklich passiert. Die Shared Libraries von Linux benutzen »Sprungtabellen« (jump tables). Diese Tabellen machen es möglich, daß neue Bibliotheken eingebunden und ihr Inhalt verändert wird, ohne daß die ausführbaren Programme, die diese Bibliotheken benutzen, erneut gebunden werden müssen. Der Stub-Code in der ausführbaren Datei sucht sich in der Bibliothek einen weiteren Referenzpunkt - und zwar in der Sprungtabelle. Auf diese Weise können der Inhalt der Bibliothek und die zugehörige Sprungtabelle geändert werden, ohne daß der Stub-Code in der ausführbaren Datei geändert werden muß.

Sie sollten sich von diesen ziemlich abstrakten Informationen nicht verwirren lassen. Wir werden weiter unten anhand eines Beispiels aus dem richtigen Leben zeigen, wie Sie Ihre Programme kompilieren, binden und debuggen. Das ist eigentlich recht einfach - um viele Details kümmert sich der gcc-Compiler selbst. Trotzdem kann es nicht schaden, wenn man versteht, was hinter den Kulissen vorgeht.

Eigenschaften des Compilers gcc

gcc bietet mehr Möglichkeiten, als wir hier jemals aufzählen können. Später stellen wir einige davon vor. Gleichzeitig verweisen wir die Neugierigen auf die Manpage und die Info-Seiten zum gcc, wo Sie ohne Zweifel einige interessante Dinge über diesen Compiler erfahren werden. Weiter unten in diesem Abschnitt geben wir Ihnen einen kurzen Überblick über die nützlichsten Eigenschaften von gcc mit auf den Weg. Mit Hilfe dieser Informationen sollten Sie in der Lage sein, selbst herauszufinden, wie Sie die vielen anderen Eigenschaften des Compilers zu Ihrem Vorteil nutzen können.

Zunächst einmal unterstützt gcc die moderne »Standard«-C-Syntax, wie sie im wesentlichen vom ANSI-C-Standard beschrieben wird. Die wichtigste Eigenschaft dieses Standards sind die Funktionsprototypen (function prototypes). Konkret bedeutet das: Wenn Sie eine Funktion foo() definieren, die ein int zurückgibt und die beiden Argumente a (vom Typ char *) und b (vom Typ double) akzeptiert, können Sie diese Funktion folgendermaßen definieren:

int foo(char *a, double b) { /* Ihr Code ... */ }

Im Gegensatz dazu sieht die alte Syntax für die Definition einer Funktion ohne Funktionsprototypen so aus:

int foo(a, b) char *a; double b; { /* Ihr Code ... */ }

Icon

[42]

Auch diese Syntax wird von gcc unterstützt. Natürlich beschreibt ANSI-C viele andere Konventionen, aber die Funktionsprototypen fallen einem neuen Programmierer zuerst auf. Wer mit dem C-Programmierstil vertraut ist, wie er in modernen Büchern (zum Beispiel die zweite Auflage von Kernighan und Ritchies Programmieren in C) gepflegt wird, sollte keine Probleme haben, mit dem gcc zu programmieren. (Die C-Compiler, die mit einigen anderen Unix-Systemen ausgeliefert werden, unterstützen solche ANSI-Eigenschaften wie das Prototyping nicht.)

Der Compiler gcc kommt mit einem ziemlich beeindruckenden Optimierer (optimizer) daher. Während die meisten C-Compiler lediglich den Schalter -O kennen, um die Optimierung einzuschalten, unterstützt gcc etliche Stufen (level) der Optimierung. Auf der höchsten Optimierungsstufe zieht gcc solche Tricks aus dem Ärmel wie die gemeinsame Nutzung von Programmcode und statischen Daten. Das bedeutet: Wenn in Ihrem Programm eine feste Zeichenfolge wie Hello, World! auftaucht, und die ASCII-Darstellung dieser Zeichenfolge entspricht zufälligerweise einer Befehlsfolge in Ihrem Programm, dann wird gcc diesen Speicherplatz sowohl als Zeichenfolge als auch für den Programmcode nutzen. Wie schlau kann ein Compiler werden?

Selbstverständlich können Sie mit gcc auch Debugging-Informationen in die Objektdateien einbinden, so daß der Debugger (und damit der Programmierer) das Programm schrittweise abarbeiten kann. Der Compiler fügt dazu in die Objektdatei Markierungen ein, damit der Debugger im kompilierten Code bestimmte Zeilen, Variablen und Funktionen finden kann. Wenn Sie also mit einem Debugger wie gdb arbeiten (mit dem wir uns später in diesem Kapitel beschäftigen werden), haben Sie die Möglichkeit, das kompilierte Programm in kleinen Schritten ablaufen zu lassen und gleichzeitig den Quelltext zu verfolgen.

Einer der anderen Tricks, die der gcc beherrscht, ist seine Fähigkeit, Assembler-Code zu erzeugen - im wahrsten Sinne des Wortes durch das Setzen eines Schalters. Statt gcc Ihren Quelltext zu Maschinensprache kompilieren zu lassen, können Sie ihn anweisen, Assembler-Code zu erzeugen, der für das menschliche Auge viel einfacher zu lesen ist. Das stellt auch eine bequeme Methode dar, unter Linux die Feinheiten der Assembler-Programmierung für den Protected Mode zu erlernen - schreiben Sie C-Code, lassen Sie gcc daraus Assembler-Code erzeugen, und studieren Sie diesen.

Falls Sie sich wundern, woher dieser Assembler-Code kommt: gcc verfügt über einen eigenen Assembler (der unabhängig von gcc benutzt werden kann). Sie können auch Assembler-Code in Ihre C-Quellen einfügen (inline assembler), falls Sie einmal besonders heftig tricksen müssen, aber nicht alles in Assembler schreiben möchten.

gcc-Grundlagen

Sicherlich wollen Sie jetzt endlich wissen, wie Sie alle diese wunderbaren Eigenschaften nutzen können. Besonders für neue Unix- und C-Programmierer ist es wichtig zu lernen, wie sie gcc effektiv einsetzen. Die Arbeit mit einem Compiler, der von der Befehlszeile aus gesteuert wird, unterscheidet sich deutlich von der Arbeit mit einem Entwicklungssystem wie zum Beispiel Borland-C unter MS-DOS. Zwar ist die Syntax der beiden Sprachen ähnlich, aber die Vorgehensweise beim Kompilieren und Binden von Programmen ist völlig anders.

Wie sollten Sie also vorgehen, wenn Sie unser harmlos aussehendes »Hello, World!«-Beispiel kompilieren und linken möchten?

Als erstes muß natürlich der Quellcode eingegeben werden. Dazu benutzen Sie einfach einen Texteditor wie Emacs oder vi. Der angehende Programmierer sollte den Quelltext eingeben und in einer Datei, zum Beispiel mit dem Namen hello.c, speichern. (Wie die meisten C-Compiler ist auch gcc wählerisch, wenn es um die Dateinamensuffixe geht - daran kann er ablesen, ob es sich um C-Quelltext, Assembler-Code, eine Objektdatei usw. handelt.) Für Standard-C-Quelltexte sollten Sie das Suffix .c verwenden.

Um aus dem Quelltext das ausführbare Programm hello zu erzeugen, würde der Programmierer folgendes eingeben:

papaya$ gcc -o hello hello.c

Vorausgesetzt, daß keine Fehler enthalten waren, würde gcc mit einem einzigen Streich aus dem Quellcode eine Objektdatei erzeugen, diese mit den entsprechenden Libraries binden und dann das lauffähige Programm hello ausspucken - voilà! Falls Sie das als vorsichtiger Programmierer testen möchten:

papaya$ hello Hello, World! papaya$

Genauso freundlich, wie man das erwarten konnte.

Offensichtlich ist hinter den Kulissen eine ganze Menge passiert, als wir diesen einzelnen gcc-Befehl aufgerufen haben. Zunächst mußte gcc aus Ihrer Quelldatei hello.c die Objektdatei hello.o erzeugen. Anschließend mußte hello.o mit den Standard-Libraries gebunden sowie eine ausführbare Datei erzeugt werden.

gcc geht normalerweise davon aus, daß Sie nicht nur die angegebenen Quelldateien kompilieren wollen, sondern daß sie auch gebunden werden sollen (untereinander sowie mit den Standard-Libraries), um eine ausführbare Datei zu erzeugen. gcc kompiliert zuerst alle Quelldateien zu Objektdateien. Anschließend wird automatisch der Linker aufgerufen, um alle Objektdateien und Libraries zu einem ausführbaren Programm zusammenzufügen. (Richtig, der Linker ist ein eigenständiges Programm namens ld, das nicht zu gcc gehört - man kann allerdings sagen, daß gcc und ld gute Freunde sind.) gcc kennt außerdem die »Standard«-Bibliotheken, die von den meisten Programmen benutzt werden, und weist ld an, diese einzubinden. Natürlich gibt es mehrere Methoden, solche Voreinstellungen zu ignorieren.

Sie haben die Möglichkeit, mit einem gcc-Befehl mehrere Dateinamen zu übergeben, aber in großen Projekten werden Sie wahrscheinlich jeweils nur wenige Dateien kompilieren und die Objektdateien (.o) aufbewahren. Wenn Sie aus einer Quelldatei nur die Objektdatei erzeugen wollen, ohne das Ganze auch binden zu lassen, können Sie in gcc den Schalter -c setzen:

papaya$ gcc -c hello.c

Damit erzeugen Sie die Objektdatei hello.o - und sonst nichts.

Der Linker ist so voreingestellt, daß er eine ausführbare Datei ausgerechnet unter dem Namen a.out erzeugt. Mit dem Schalter -o können Sie gcc zwingen, die zu erzeugende Datei anders zu benennen; in diesem Fall hello. Es handelt sich hierbei lediglich um übriggebliebenen Müll aus frühen Unix-Versionen - nichts besonders Aufregendes.

Mehrere Quelldateien benutzen

Der nächste Schritt auf Ihrem Pfad der Erleuchtung mit gcc besteht darin, daß Sie die Kompilierung mit mehreren Quelldateien verstehen. Nehmen wir an, daß Ihr Programm aus den beiden Quelldateien foo.c und bar.c besteht. Natürlich würden Sie eine oder mehrere Header-Dateien benutzen (etwa foo.h), in denen Funktionsdeklarationen enthalten sind, die von beiden Programmen benutzt werden. Auf diese Weise kennt foo.c die Funktionen in bar.c und umgekehrt.

Geben Sie folgenden Befehl ein, um diese Quellcodedateien kompilieren und linken zu lassen (mit den Libraries natürlich) und daraus die ausführbare Datei baz zu erstellen:

papaya$ gcc -o baz foo.c bar.c

Dies entspricht etwa den drei Befehlen:

papaya$ gcc -c foo.c papaya$ gcc -c bar.c papaya$ gcc -o baz foo.o bar.o

gcc bildet also ein bequemes Frontend zum Linker und zu anderen »versteckten« Utilities, die während der Kompilierung aufgerufen werden.

Offensichtlich kann das Kompilieren eines Programms, das aus mehreren Quelldateien besteht, mit nur einem Befehl eine ganze Weile dauern. Wenn Ihr Programm zum Beispiel aus fünf oder mehr Quelldateien besteht, würde der eben gezeigte gcc-Befehl vor dem Linken alle Quelldateien nacheinander noch einmal kompilieren. Das wäre eine große Zeitverschwendung - insbesondere, wenn Sie nach der letzten Kompilierung nur eine Quelldatei geändert hatten. Die anderen Dateien bräuchten nicht noch einmal kompiliert zu werden, da ihre weiterhin gültigen Objektdateien noch vorhanden sind.

Die Lösung dieses Problems bieten Programme zur Projektverwaltung, wie zum Beispiel make, das wir im Abschnitt »Die Makefiles« später in diesem Kapitel besprechen werden.

Optimierung

Die Anweisung an gcc, den Code während der Kompilierung zu optimieren, ist recht einfach; benutzen Sie dazu in der Befehlszeile den Schalter -O:

papaya$ gcc -O -o fishsticks fishsticks.c

Etwas weiter oben haben wir erwähnt, daß gcc verschiedene Stufen der Optimierung kennt. Wenn Sie statt -O beispielsweise -O2 angeben, werden verschiedene »teure« Optimierungen eingeschaltet, die einerseits den Kompilierungsvorgang verlangsamen können, andererseits aber Ihren Code (hoffentlich) wesentlich effektiver machen.

Sie haben bei Ihren Streifzügen durch die Linux-Welt vielleicht bemerkt, daß einige Programme mit dem Schalter -O6 kompiliert werden (der Linux-Kernel ist ein gutes Beispiel dafür). Die aktuelle Version des gcc unterstützt die Optimierung auf der Stufe -O6 nicht, so daß hier - zur Zeit - mit der Stufe -O2 optimiert wird. Trotzdem wird gelegentlich -O6 angegeben, um Kompatibilität zu kommenden Versionen von gcc herzustellen; somit ist sichergestellt, daß in Zukunft die höchste Optimierungsstufe benutzt wird.

Den Code debuggen

Der Schalter -g in gcc fügt Debugging-Code in die kompilierten Objektdateien ein. Das bedeutet, daß sowohl in die Objektdatei als auch in die entstehende ausführbare Datei zusätzliche Informationen eingefügt werden. Diese Informationen ermöglichen es, mit einem Debugger wie gdb (keine Bange, wir kommen später in diesem Kapitel darauf zu sprechen) das Programm schrittweise zu durchlaufen. Der Nachteil beim Einbinden von Debugging-Code ist, daß die Größe der entstehenden Objektdateien erheblich zunimmt. In der Regel ist es sinnvoll, -g nur zu benutzen, solange Sie Ihre Programme entwickeln und testen; bei der »endgültigen« Kompilierung sollten Sie diesen Schalter nicht setzen.

Glücklicherweise schließen sich Debugging-Code und Optimierung nicht gegenseitig aus. Sie können also ohne weiteres den Befehl

papaya$ gcc -O -g -o mumble mumble.c

aufrufen. Allerdings kann es vorkommen, daß sich das Programm nach bestimmten Optimierungen mit -O oder -O2 anscheinend unberechenbar verhält, wenn es vom Debugger unter die Fittiche genommen wird. In der Regel empfiehlt es sich, entweder -O oder -g zu setzen, aber nicht beide.

Mehr Spaß mit Libraries

Bevor wir die Gefilde von gcc verlassen, wollen wir noch ein paar Worte zum Thema Linken und Libraries sagen. Es ist gar nicht so schwierig, eigene Bibliotheken zu erzeugen. Wenn Sie eine Reihe von Routinen geschrieben haben, die Sie häufig aufrufen, sollten Sie diese vielleicht zu einer Gruppe von Quelltextdateien zusammenfassen, aus jeder Quelldatei eine Objektdatei erzeugen und dann aus diesen Objektdateien eine Bibliothek erstellen. Anschließend brauchen Sie diese Routinen nicht mit jedem Programm, das darauf zugreift, erneut zu kompilieren.

Nehmen wir an, daß Sie einige Quelldateien haben, in denen häufig benutzte Routinen wie zum Beispiel

float square(float x) { /* Code für square()... */ } int factorial(int x, int n) { /* Code für factorial()... */ }

usw. stehen. (Natürlich sind in den Standardbibliotheken von gcc solche gebräuchlichen Routinen bereits enthalten; lassen Sie sich also durch dieses Beispiel nicht verwirren.) Der Einfachheit halber wollen wir weiterhin annehmen, daß der Code für square() in der Datei square.c steht und der Code für factorial() in factorial.c.

Um eine Bibliothek zu erzeugen, die diese Routinen enthält, müssen Sie nur diese Quelldateien kompilieren:

papaya$ gcc -c square.c factorial.c

Damit erhalten Sie square.o und factorial.o. Als nächstes erzeugen Sie aus diesen Objektdateien die Bibliothek. Dabei zeigt sich, daß eine Bibliothek einfach eine Archivdatei ist, die mit dem Befehl ar erzeugt wird (einem engen Verwandten von tar). Wir wollen unsere Bibliothek libstuff.a nennen, und so gehen wir dabei vor:

papaya$ ar r libstuff.a square.o factorial.o

Wenn Sie eine solche Bibliothek aktualisieren möchten, sollten Sie vielleicht vorher die alte libstuff.a löschen. Als letzten Schritt erstellen wir einen Index zu dieser Bibliothek, damit der Linker die Routinen darin finden kann. Benutzen Sie dazu den Befehl ranlib, etwa so:

papaya$ ranlib libstuff.a

Dieser Befehl fügt zusätzliche Informationen in die Bibliothek selbst ein; es wird keine eigenständige Indexdatei erzeugt. Sie könnten die letzten beiden Schritte mit ar und ranlib auch kombinieren, indem Sie bei ar den Schalter s angeben:

papaya$ ar rs libstuff.a square.o factorial.o

Mit libstuff.a haben Sie jetzt eine statische Library, die Ihre Routinen enthält. Bevor Sie Programme mit dieser Bibliothek binden können, müssen Sie noch eine Header-Datei schreiben, die den Inhalt der Bibliothek beschreibt. Wir könnten zum Beispiel die Datei libstuff.h mit folgendem Inhalt erstellen:

/* libstuff.h: routines in libstuff.a */ extern float square(float); extern int factorial(int, int);

In jede Quellcodedatei, die Routinen aus libstuff.a aufruft, sollten Sie die Zeile #include "libstuff.h" einfügen, wie Sie das mit den Standard-Header-Dateien auch tun würden.

Wie kompilieren wir Programme, die auf die soeben fertiggestellte Bibliothek samt Header-Datei zugreifen? Zunächst müssen wir beide an einer Stelle abspeichern, wo der Compiler sie finden kann. Viele Programmierer legen eigene Bibliotheken im Unterverzeichnis lib ihres Home-Verzeichnisses ab und eigene Include-Dateien unter include.

Wir gehen davon aus, daß dies bereits geschehen ist, und können dann das ominöse Programm wibble.c mit folgendem Befehl kompilieren:

papaya$ gcc -Iinclude -Llib -o wibble wibble.c -lstuff

Mit der Option -I weisen Sie gcc an, das Verzeichnis include in den Include-Pfad einzufügen, in dem gcc nach Include-Dateien sucht. Die Option -L funktioniert ganz ähnlich, indem sie gcc anweist, das Verzeichnis lib in den Library-Pfad einzutragen.

Das letzte Argument auf der Befehlszeile ist -lstuff; damit wird der Linker angewiesen, die Bibliothek libstuff.a einzubinden (solange sie irgendwo im Library-Pfad zu finden ist). Das lib am Anfang des Dateinamens wird für Bibliotheken automatisch angenommen.

Sie sollten den Schalter -l auf der Befehlszeile jedesmal benutzen, wenn Sie andere als die Standardbibliotheken einbinden wollen. Wenn Sie beispielsweise mathematische Routinen aus math.h benutzen möchten, sollten Sie am Ende der gcc-Befehlszeile -lm anhängen, womit libm eingebunden wird. Bedenken Sie aber, daß die Reihenfolge der -l-Optionen von Bedeutung ist. Ein Beispiel: Wenn Ihre Bibliothek libstuff Routinen aus libm aufruft, dann muß in der Befehlszeile -lm hinter -lstuff stehen:

papaya$ gcc -Iinclude -Llib -o wibble wibble.c -lstuff -lm

Damit zwingen Sie den Linker, libm nach libstuff zu binden; dabei können die noch nicht aufgelösten Verweise in libstuff bearbeitet werden.

Wo sucht gcc nach Bibliotheken? Per Voreinstellung werden die Bibliotheken in verschiedenen Verzeichnissen gesucht; das wichtigste davon ist /usr/lib. Wenn Sie einen Blick auf den Inhalt von /usr/lib werfen, werden Sie feststellen, daß dort eine ganze Reihe von Library-Dateien abgelegt sind - einige der Dateinamen enden auf .a, andere auf .so.version. Dabei verbergen sich hinter den .a-Dateien die statischen Bibliotheken, zum Beispiel auch unsere libstuff.a. Die .so-Dateien sind die dynamischen Bibliotheken, die sowohl den zur Laufzeit dazugelinkten Code als auch den Stub-Code enthalten, den der Laufzeit-Linker (ld.so) benötigt, um die dynamische Bibliothek zu finden.

Zur Laufzeit sucht das Ladeprogramm die dynamischen Bibliotheken an verschiedenen Stellen, darunter /lib. Wenn Sie das Verzeichnis /lib ansehen, werden Sie Dateien wie libc.so.6 sehen. Das ist die Image-Datei, die den Code für die dynamische Bibliothek libc enthält (eine der Standardbibliotheken, die zu den meisten Programmen hinzugelinkt werden).

Der Linker ist so voreingestellt, daß er versucht, Shared Libraries einzubinden. Es gibt allerdings auch Situationen, in denen die statischen Bibliotheken benutzt werden sollen. Sie können mit dem Schalter -static von gcc das Einbinden von statischen Bibliotheken ausdrücklich anfordern.

Shared Libraries erzeugen

Wo Sie jetzt schon wissen, wie man statische Bibliotheken erzeugt und verwendet, ist es auch nicht mehr weiter schwer, Shared Libraries zu verwenden. Diese haben eine Reihe von Vorteilen. Sie verringern den Speicherverbrauch, wenn sie von mehr als einem Prozeß benutzt werden, aber auch die Größe der ausführbaren Datei selbst. Außerdem machen sie die Entwicklung einfacher: Wenn Sie Shared Libraries benutzen und etwas in einer Bibliothek ändern, müssen Sie nicht jedesmal Ihre Applikation selbst neu kompilieren und linken. Das ist nur notwendig, wenn Sie inkompatible Änderungen vornehmen, also beispielsweise ein Argument zu einem Aufruf oder ein Feld zu einer Struktur hinzufügen.

Bevor Sie jetzt aber anfangen, all Ihre Entwicklung auf Shared Libraries umzustellen, möchten wir Sie noch kurz vorwarnen, daß das Debuggen mit Shared Libraries etwas mühsamer ist als mit statischen Bibliotheken, weil der gewöhnlich unter Linux verwendete Debugger gdb seine Schwierigkeiten mit Shared Libraries hat.

Aller Code in Shared Libraries muß positionsunabhängig (position independent) sein. Dabei handelt es sich lediglich um eine Konvention für den Objektcode, mit der dieser in Shared Libraries verwendet werden kann. Sie können gcc veranlassen, positionsunabhängigen Code auszugeben, indem Sie einen der Kommandozeilenschalter -fpic oder -fPIC angeben (ersteres ist besser, es sei denn, Ihre Module sind so groß, daß die Relokationstabelle zu klein wird, in welchem Fall der Compiler eine Fehlermeldung ausgibt und Sie -fPIC verwenden müssen). Nehmen wir das Beispiel aus dem letzten Abschnitt wieder auf:

papaya$ gcc -c -fPIC square.c factorial.c

Wenn das erledigt ist, ist es nur noch ein kleiner Schritt zur Shared Library: Fußnoten 1

papaya$ gcc -shared -o libstuff.so square.o factorial.o

Beachten Sie den Compiler-Schalter -shared. Es ist keine Indizierung wie bei statischen Bibliotheken notwendig.

Die Verwendung unserer frisch kompilierten und gelinkten Shared Library ist sogar noch einfacher. Der Compiler-Befehl muß nicht einmal geändert werden:

papaya$ gcc -Iinclude -Llib -o wischel wischel.c -lstuff -lm

Sie fragen sich jetzt vielleicht, was der Linker macht, wenn es sowohl eine Shared Library libstuff.so als auch eine statische Bibliothek libstuff.a gibt. In diesem Fall nimmt der Linker immer automatisch die Shared Library. Wenn Sie trotzdem die statische Bibliothek verwenden wollen, müssen Sie das dem Compiler explizit auf der Kommandozeile mitteilen:

papaya$ gcc -Iinclude -Llib -o wischel wischel.c libstuff.a -lm

Ein weiteres nützliches Werkzeug bei der Arbeit mit Shared Libraries ist das Programm ldd. Es zeigt Ihnen, welche Shared Libraries ein ausführbares Programm verwendet. Ein Beispiel:

papaya$ ldd wischel
libstuff.so => libstuff.so (0x400af000) libm.so.6 => /lib/libm.so.6 (0x400ba000) libc.so.6 => /lib/libc.so.6 (0x400c3000)

Die drei Felder jeder Zeile geben den Namen der Bibliothek an, den vollständigen Pfad zu der Instanz der Bibliothek, die verwendet wird, und an welcher Stelle die Bibliothek in den virtuellen Adreßraum eingeblendet wird.

Wenn ldd für eine bestimmte Bibliothek not found ausgibt, haben Sie ein Problem und können das betroffene Programm nicht ausführen. Sie müssen nach dieser Bibliothek suchen. Vielleicht ist es eine Bibliothek, die mit Ihrer Distribution geliefert wird und die Sie nur nicht installiert haben, oder aber sie befindet sich schon auf Ihrer Festplatte, wird aber vom Loader (der Teil des Systems, der jede ausführbare Datei lädt) nicht gefunden.

Im letzteren Fall können Sie versuchen, die Bibliotheken selbst zu finden und zu kontrollieren, ob sie sich in einem nicht standardmäßig verwendeten Verzeichnis befinden. Per Voreinstellung sucht der Loader nur in /lib und /usr/lib. Wenn Sie Bibliotheken in einem anderen Verzeichnis liegen haben, dann legen Sie die Umgebungsvariable LD_LIBRARY_PATH an und weisen Sie dieser die durch Doppelpunkte getrennten Verzeichnisse zu.

Programmieren mit C++

Für den Fall, daß Sie lieber objektorientiert programmieren, bietet gcc die vollständige Unterstützung sowohl für C++ als auch für Objective-C. Es gibt nur wenige Dinge, die Sie bedenken müssen, wenn Sie mit dem gcc in C++ programmieren möchten.

Zunächst sollten die Namen der C++-Quelldateien auf .C oder .cc enden. Damit werden sie von den Dateien mit einfachem C-Code unterschieden, deren Namen auf .c enden.

Zweitens sollten Sie den Shell-Befehl g++ statt gcc benutzen, wenn Sie C++-Code kompilieren. g++ ruft den gcc mit einigen zusätzlichen Parametern auf, um etwa die C++-Standardbibliotheken einzubinden. g++ akzeptiert dieselben Argumente und Optionen wie gcc.

Wenn Sie g++ nicht aufrufen, aber C++-Klassen wie die E/A-Objekte cout und cin benutzen möchten, müssen Sie sicherstellen, daß die C++-Bibliotheken eingebunden werden. Vergewissern Sie sich auch, daß die C++-Bibliotheken und Include-Dateien tatsächlich installiert sind; einige Distributionen enthalten nur die Standard-C-Libraries. Während gcc Ihre C++-Programme noch problemlos kompiliert, bekommen Sie ohne die C++-Bibliotheken jedesmal Fehlermeldungen vom Linker, wenn Sie versuchen, die Standardobjekte zu benutzen.

 Fußnoten 1
In der Frühgeschichte von Linux war das Erzeugen einer Shared Library eine gewaltige und schwierige Aufgabe, vor der selbst die Profis Angst hatten. Seit der Einführung des Objektdateiformats ELF vor einigen Jahren muß man einfach nur noch den richtigen Compiler-Schalter auswählen. Wenn das kein Fortschritt ist!


vorheriges Kapitel Inhaltsverzeichnis Stichwortverzeichnis nächstes Kapitel


Weitere Informationen zum Linux - Wegweiser zur Installation & Konfiguration

Weitere Online-Bücher & Probekapitel finden Sie in unserem Online Book Center


O'Reilly Home | O'Reilly-Partnerbuchhandlungen | Bestellinformationen | Kontaktieren Sie uns
International | Über O'Reilly | Tochterfirmen

© 2000, O'Reilly Verlag