C-Entwicklung unter Linux - Erstellen eines Programms - X.
Einleitung
Nach all der Theorie und dem Gerede beginnen wir damit, den Code zu erstellen, der in den letzten neun Teilen dieser Serie geschrieben wurde. Dieser Teil unserer Serie könnte Ihnen auch dann nützlich sein, wenn Sie C woanders gelernt haben oder wenn Sie der Meinung sind, dass Ihre praktische Seite der C-Entwicklung ein wenig Kraft braucht. Wir werden sehen, wie Sie die notwendige Software installieren, was diese Software tut und vor allem, wie Sie Ihren Code in Nullen und Einsen umwandeln können. Bevor wir beginnen, sollten Sie einen Blick auf unsere neuesten Artikel werfen, in denen Sie erfahren, wie Sie Ihre Entwicklungsumgebung anpassen können:
- Einführung in den VIM-Editor
- Einführung in den Emacs
- Anpassen von VIM für die Entwicklung
- Anpassen des Emacs für die Entwicklung
Erstellen Ihres Programms
Erinnern Sie sich an den ersten Teil unserer C Development Serie? Dort haben wir den grundlegenden Prozess beschrieben, der abläuft, wenn Sie Ihr Programm kompilieren. Aber wenn Sie nicht in der Compiler-Entwicklung oder in anderen wirklich niedrigen Dingen arbeiten, wird es Sie nicht interessieren, wie viele JMP-Befehle die generierte Assembler-Datei enthält, wenn überhaupt. Sie werden nur wissen wollen, wie Sie so effizient wie möglich sein können. Darum geht es in diesem Teil des Artikels, aber wir kratzen nur an der Oberfläche, wegen der Ausführlichkeit des Themas. Aber ein Einsteiger in die C-Programmierung wird nach der Lektüre dieses Artikels alles wissen, was für ein effizientes Arbeiten erforderlich ist.
Die Werkzeuge
Sie müssen nicht nur genau wissen, was Sie erreichen wollen, sondern auch mit den Werkzeugen vertraut sein, um das zu erreichen, was Sie wollen. Und Linux-Entwicklungstools haben viel mehr zu bieten als gcc, obwohl es allein ausreichen würde, Programme zu kompilieren, aber es wäre eine mühsame Aufgabe, wenn die Größe Ihres Projekts zunimmt. Aus diesem Grund wurden andere Instrumente entwickelt, und wir werden hier sehen, was sie sind und wie man sie bekommt. Ich habe Ihnen bereits mehr als empfohlen, das gcc-Handbuch zu lesen, daher werde ich nur vermuten, dass Sie es getan haben.
machen
Stellen Sie sich vor, Sie haben ein Projekt mit mehreren Dateien, mit vielen Quelldateien, den Werken. Stellen Sie sich nun vor, dass Sie eine Datei (etwas Kleines) ändern und einer anderen Quelldatei Code hinzufügen müssen. Es wäre schmerzhaft, das gesamte Projekt deswegen neu aufzubauen. Hier ist der Grund, warum make entwickelt wurde: Anhand von Datei-Zeitstempeln erkennt es, welche Dateien neu erstellt werden müssen, um zu den gewünschten Ergebnissen (ausführbare Dateien, Objektdateien...), benannte Ziele, zu gelangen. Wenn das Konzept immer noch unklar aussieht, machen Sie sich keine Sorgen: Nachdem Sie ein Makefile und die allgemeinen Konzepte erklärt haben, wird alles einfacher erscheinen, obwohl fortgeschrittene Make-Konzepte Kopfschmerzen verursachen können.
make hat diesen genauen Namen auf allen Plattformen, an denen ich gearbeitet habe, das sind ziemlich viele Linux-Distributionen, *BSD und Solaris. Unabhängig davon, welchen Paketmanager Sie verwenden (falls vorhanden), sei es apt*, yum, zypper, pacman oder emerge, verwenden Sie einfach den jeweiligen Installationsbefehl und make als Argument und das war's. Ein anderer Ansatz wäre, bei Distributionen mit Paketmanagern, die Gruppenunterstützung haben, die gesamte C/C++-Entwicklungsgruppe/das gesamte C/C++-Entwicklungsmuster zu installieren. Apropos Sprachen, ich wollte hier einen Mythos entlarven, der besagt, dass Makefiles (die Regeln, die make befolgen muss, um das Ziel zu erreichen) nur von C/C++-Entwicklern verwendet werden. Falsch. Jede Sprache mit einem Compiler/Interpreter, der von der Shell aus aufgerufen werden kann, kann die Funktionen von make verwenden. Tatsächlich kann jedes Projekt, das eine abhängigkeitsbasierte Aktualisierung benötigt, make verwenden. Eine aktualisierte Definition eines Makefiles wäre also eine Datei, die die Beziehungen und Abhängigkeiten zwischen den Dateien eines Projekts beschreibt, mit dem Ziel, zu definieren, was aktualisiert/neu kompiliert werden soll, falls sich eine oder mehrere Dateien in der Abhängigkeitskette ändern. Zu verstehen, wie make funktioniert, ist für jeden C-Entwickler, der unter Linux oder Unix arbeitet, unerlässlich – ja, kommerzielle Unix-Angebote bieten auch make an, obwohl wahrscheinlich eine Version sich von GNU make unterscheidet, worum es bei uns geht. "Unterschiedliche Version" bedeutet mehr als nur Zahlen, es bedeutet, dass ein BSD-Makefile nicht mit einem GNU-Makefile kompatibel ist. Stellen Sie also sicher, dass Sie GNU make installiert haben, wenn Sie nicht auf einem Linux-Rechner arbeiten.
Im ersten Teil dieses Artikels und einigen nachfolgenden haben wir Teile von yest verwendet und darüber gesprochen, ein kleines Programm, das standardmäßig das Datum von gestern anzeigt, aber viele nette Dinge im Zusammenhang mit Datum und Uhrzeit tut. Nach der Zusammenarbeit mit der Autorin Kimball Hawkins wurde ein kleines Makefile geboren, mit dem wir arbeiten werden.
Sehen wir uns zunächst einige Grundlagen über das Makefile an. Der kanonische Name sollte GNUmakefile sein, aber wenn keine solche Datei existiert, sucht es nach Namen wie makefile und Makefile, in dieser Reihenfolge, oder so steht es auf der Handbuchseite. Übrigens, natürlich solltet ihr es lesen, und noch einmal lesen, dann noch mehr lesen. Es ist nicht so groß wie das von gcc und Sie können viele nützliche Tricks lernen, die später nützlich sein werden. Der in der Praxis am häufigsten verwendete Name ist jedoch Makefile, und ich habe noch nie eine Quelle mit einer Datei namens GNUmakefile gesehen, um ehrlich zu sein. Wenn Sie aus verschiedenen Gründen einen anderen Namen angeben müssen, verwenden Sie make's -f, wie folgt:
$ make -f mymakefileHier ist das Makefile von euch, das ihr zum Kompilieren und Installieren des Programms verwenden könnt, da es noch nicht von Sourceforge hochgeladen wurde. Obwohl es nur ein Programm mit zwei Dateien ist – der Quellcode und die Manpage – werden Sie sehen, dass make bereits nützlich ist.
Makefile for compiling and installing yest
UNAME := $(shell uname -s)
CC = gcc
CFLAGS = -Wall
CP = cp
RM = rm
RMFLAGS = -f
GZIP = gzip
VERSION = yest-2.7.0.5
yest:
ifeq ($(UNAME), SunOS)
$(CC) -DSUNOS $(CFLAGS) -o yest $(VERSION).c
else
$(CC) $(CFLAGS) -o yest $(VERSION).c
endif
all: yest install maninstall
install: maninstall
$(CP) yest /usr/local/bin
maninstall:
$(CP) $(VERSION).man1 yest.1
$(GZIP) yest.1
$(CP) yest.1.gz /usr/share/man/man1/
clean:
$(RM) $(RMFLAGS) yest yest.1.gz
deinstall:
$(RM) $(RMFLAGS) /usr/local/bin/yest /usr/share/man/man1/yest1.gz
Wenn Sie sich den obigen Code genau ansehen, werden Sie bereits eine Reihe von Dingen beobachten und lernen. Kommentare beginnen mit Hashes, und da Makefiles ziemlich kryptisch werden können, sollten Sie Ihre Makefiles besser kommentieren. Zweitens können Sie Ihre eigenen Variablen deklarieren, und dann können Sie sie sinnvoll nutzen. Als nächstes kommt der wesentliche Teil: Ziele. Die Wörter, auf die ein Doppelpunkt folgt, werden als Ziele bezeichnet, und man verwendet sie wie make [-f Makefile-Name] target_name. Wenn Sie jemals von der Quelle installiert haben, haben Sie wahrscheinlich "make install" eingegeben. Nun, 'install' ist eines der Ziele im Makefile, und andere häufig verwendete Ziele sind 'clean', 'deinstall' oder 'all'. Ein weiterer wichtiger Punkt ist, dass das erste Ziel standardmäßig immer ausgeführt wird, wenn kein Ziel angegeben ist. In unserem Fall, wenn ich 'make' eingegeben hätte, wäre das das Äquivalent zu 'make yest' gewesen, wie Sie sehen können, was bedingte Kompilierung (wenn wir Solaris/SunOS verwenden, benötigen wir ein zusätzliches gcc-Flag) und die Erstellung einer ausführbaren Datei namens 'yest' bedeutet. Ziele wie 'all' in unserem Beispiel tun von sich aus nichts, sagen Sie nur, dass sie auf andere Dateien/Ziele angewiesen sind, um auf dem neuesten Stand zu sein. Achten Sie auf die Syntax, nämlich Dinge wie Leerzeichen und Tabulatoren, da make bei solchen Dingen ziemlich prätentiös ist.
Hier ist ein kurzes Makefile für ein Projekt mit zwei Quelldateien. Die Dateinamen sind src1.c und src2.c und der Name der ausführbaren Datei muss exec lauten. Einfach, oder?
exec: src1.o src2.o
gcc -o exec src1.o src2.o
src1.o: src1.c
gcc -c src1.c
src2.o: src2.c
gcc -c src2.cDas einzige Ziel, das praktisch verwendet wird und das auch die Standardeinstellung ist, ist 'exec'. Dies hängt von src1.o und src2.o ab, die wiederum von den jeweiligen .c-Dateien abhängen. Wenn Sie also z.B. src2.c ändern, müssen Sie nur make erneut ausführen, wodurch festgestellt wird, dass src2.c neuer ist als der Rest und entsprechend vorzugehen. Hier gibt es viel mehr zu machen, als abgedeckt ist, aber mehr Platz ist nicht. Wie immer ist ein gewisses Selbststudium erwünscht, aber wenn Sie nur grundlegende Funktionen benötigen, werden Ihnen die oben genannten Dienste gute Dienste leisten.
Das configure-Skript
Normalerweise ist es nicht nur 'make & make install', denn vor diesen beiden gibt es einen Schritt, der das Makefile erzeugt, was besonders nützlich ist, wenn es um größere Projekte geht. Grundsätzlich prüft dieses Skript, ob Sie die für die Kompilierung benötigten Komponenten installiert haben, nimmt aber auch verschiedene Argumente an, die Ihnen helfen, das Ziel der installierten Dateien zu ändern, und verschiedene andere Optionen (z. B. Qt4- oder GTK3-Unterstützung, PDF- oder CBR-Dateiunterstützung und so weiter). Schauen wir uns in einem kurzen Blick an, was es mit diesen Konfigurationsskripten auf sich hat.
Normalerweise schreiben Sie das configure-Skript nicht von Hand. Dazu verwenden Sie autoconf und automake. Wie der Name schon sagt, generieren sie Konfigurationsskripte bzw. Makefiles. In unserem vorherigen Beispiel mit dem Programm yest könnten wir zum Beispiel tatsächlich ein configure-Skript verwenden, das die Betriebssystemumgebung erkennt und einige make-Variablen ändert und schließlich ein Makefile generiert. Wir haben gesehen, dass das yest Makefile prüft, ob wir unter SunOS laufen, und wenn ja, ein Compiler-Flag hinzufügt. Ich würde das erweitern, um zu überprüfen, ob wir an einem BSD-System arbeiten, und wenn ja, gmake (GNU make) anstelle des nativen make aufrufen, das, wie gesagt, nicht mit GNU Makefiles kompatibel ist. Beides geschieht mit autoconf: Wir schreiben eine kleine configure.in Datei, in der wir autoconf mitteilen, was wir überprüfen müssen, und normalerweise möchten Sie nach mehr als der Betriebssystemplattform suchen. Möglicherweise hat der Benutzer keinen Compiler installiert, kein Make, keine Entwicklungsbibliotheken, die für die Kompilierzeit wichtig sind und so weiter. Eine Zeile, die das Vorhandensein von time.h in den Standardheader-Positionen des Systems überprüft, würde z. B. wie folgt aussehen:
AC_CHECK_HEADERS(time.h)Wir empfehlen Ihnen, mit einer nicht zu großen Anwendung zu beginnen, den Inhalt des Quell-Tarballs zu überprüfen und die configure.in und/oder configure.ac Dateien zu lesen. Für Tarballs, die über diese verfügen, ist Makefile.am auch eine gute Möglichkeit, um zu sehen, wie eine Automake-Datei aussieht. Es gibt ein paar gute Bücher zu diesem Thema, und eines davon ist Robert Mecklenburgs "Managing Projects with GNU Make".
gcc-Tipps und übliche Befehlszeilen-Flags
Ich weiß, dass das gcc-Handbuch groß ist und ich weiß, dass viele von Ihnen es noch nicht einmal gelesen haben. Ich bin stolz darauf, alles zu lesen (alles, was sowieso über IA-Hardware gilt) und ich muss gestehen, dass ich danach Kopfschmerzen hatte. Andererseits gibt es einige Optionen, die Sie kennen sollten, auch wenn Sie im Laufe der Zeit mehr lernen werden.
Sie sind bereits auf das -o-Flag gestoßen, das gcc mitteilt, was die resultierende Outfile ist, und -c, das gcc anweist, den Linker nicht auszuführen, wodurch das erzeugt wird, was der Assembler ausspuckt, nämlich Objektdateien. Apropos, es gibt Optionen, die die Phasen steuern, in denen gcc die Ausführung stoppen soll. Um also vor der Assemblierungsphase nach der Kompilierung an sich zu stoppen, verwenden Sie -S. In gleicher Weise ist -E zu verwenden, wenn Sie gcc direkt nach der Vorverarbeitung stoppen möchten.
Es ist eine gute Praxis, einem Standard zu folgen, wenn nicht aus Gründen der Uniformität, sondern wegen guter Programmiergewohnheiten. Wenn Sie sich in der Entstehungsphase als C-Entwickler befinden, wählen Sie einen Standard (siehe unten) und befolgen Sie diesen. Die Sprache C wurde erst standardisiert, nachdem Kernighan und Ritchie (RIP) 1978 "The C Programming Language" veröffentlicht hatten. Es war ein nicht-formaler Standard, aber er wurde kurz als K&R bezeichnet und respektiert. Aber jetzt ist es veraltet und wird nicht empfohlen. Später, in den 80er und 90er Jahren, entwickelten ANSI und ISO einen offiziellen Standard, C89, gefolgt von C99 und C11. GCC unterstützt auch andere Standards, wie z. B. GNUXX, wobei xx beispielsweise 89 oder 99 sein kann. Schauen Sie im Handbuch nach Details, und die Option ist '-std=', "erzwungen" durch '-pedantic'.
Warnungsbezogene Optionen beginnen mit »-W«, wie »-Wall« (es weist gcc an, alle Fehler zu aktivieren, obwohl sie noch nicht ganz aktiviert sind) oder »-Werror« (Warnungen als Fehler behandeln, immer empfohlen). Sie können zusätzliche Argumente an die Programme übergeben, die bei den Zwischenschritten helfen, z. B. Präprozessor, Assembler oder Linker. So übergeben Sie beispielsweise eine Option an den Linker:
$ gcc [other options...] -Wl,option [yet another set of options...]In ähnlicher Weise und intuitiv können Sie 'Wa' für den Assembler und 'Wp' für den Präprozessor verwenden. Achten Sie auf das Komma und die Leerzeichen, die dem Compiler mitteilen, dass der Präprozessor/Assembler/Linker-Teil beendet ist. Weitere nützliche Optionsfamilien sind '-g' und Friends zum Debuggen, '-O' und Friends zur Optimierung oder '-IVerzeichnis' – kein Leerzeichen – zum Hinzufügen eines Header-haltigen Speicherorts.
Schlussfolgerung
Ich empfehle Ihnen, sich Zeit zu nehmen, diesen Artikel zu lesen, mit den Beispielen zu spielen und dann Ihre eigenen zu schreiben, um die Komplexität im Laufe der Zeit zu erhöhen.
Hier ist, was Sie als nächstes erwarten können:
- I.C.-Entwicklung unter Linux – Einführung
- II. Vergleich zwischen C und anderen Programmiersprachen
- III. Typen, Operatoren, Variablen
- IV. Flusskontrolle
- V. Funktionen
- VI. Zeiger und Arrays
- VII. Strukturen
- VIII. Grundlegende I/O
- IX. Codierungsstil und Empfehlungen
- X. Erstellen eines Programms
- XI. Paketierung für Debian und Fedora
- XII. Ein Paket in die offiziellen Debian-Repositories bekommen