C Language
Zusammenstellung
Suche…
Einführung
Die C-Sprache ist traditionell eine kompilierte Sprache (im Gegensatz zu interpretiert). Der C-Standard definiert Übersetzungsphasen , und das Produkt ihrer Anwendung ist ein Programmbild (oder ein kompiliertes Programm). In c11 sind die Phasen in §5.1.1.2 aufgeführt.
Bemerkungen
Dateinamenerweiterung | Beschreibung |
---|---|
.c | Quelldatei Enthält normalerweise Definitionen und Code. |
.h | Header-Datei. Enthält normalerweise Deklarationen. |
.o | Objektdatei Kompilierter Code in Maschinensprache. |
.obj | Alternative Erweiterung für Objektdateien. |
.a | Bibliotheksdatei. Paket von Objektdateien. |
.dll | Dynamic Link Library unter Windows. |
.so | Shared Object (Bibliothek) auf vielen Unix-ähnlichen Systemen. |
.dylib | Dynamic-Link Library unter OSX (Unix-Variante). |
.exe , .com | Ausführbare Windows-Datei. Wird durch Verknüpfen von Objektdateien und Bibliotheksdateien gebildet. In Unix-ähnlichen Systemen gibt es keine spezielle Dateinamenerweiterung für ausführbare Dateien. |
POSIX c99-Compiler-Flags | Beschreibung |
---|---|
-o filename | Name der Ausgabedatei, z. ( bin/program.exe , program ) |
-I directory | Suche nach Headern im direrctory . |
-D name | definieren name |
-L directory | Suche nach Bibliotheken im directory . |
-l name | libname . |
Compiler auf POSIX-Plattformen (Linux, Mainframes, Mac) akzeptieren diese Optionen normalerweise, auch wenn sie nicht als c99
.
- Siehe auch c99 - Standard-C-Programme übersetzen
GCC-Flags (GNU Compiler Collection) | Beschreibung |
---|---|
-Wall | Aktiviert alle Warnmeldungen, die allgemein akzeptiert werden, als nützlich. |
-Wextra | Aktiviert weitere Warnmeldungen, kann zu laut sein. |
-pedantic | Erzwingen Sie Warnungen, wenn der Code den gewählten Standard verletzt. |
-Wconversion | Aktivieren Sie Warnungen bei der impliziten Konvertierung. |
-c | Kompiliert Quelldateien ohne Verknüpfung. |
-v | Gibt Informationen zur Zusammenstellung aus. |
-
gcc
akzeptiert die POSIX-Flags und viele andere. - Viele andere Compiler auf POSIX-Plattformen (
clang
, herstellerspezifische Compiler) verwenden ebenfalls die oben aufgeführten Flags. - Siehe auch GCC aufrufen für viele weitere Optionen.
TCC (Tiny C Compiler) -Flaggen | Beschreibung |
---|---|
-Wimplicit-function-declaration | Warnung vor impliziter Funktionsdeklaration |
-Wunsupported | Warnung vor nicht unterstützten GCC-Funktionen, die von TCC ignoriert werden. |
-Wwrite-strings | Machen Sie String-Konstanten vom Typ const char * anstelle von char *. |
-Werror | Kompilierung abbrechen, wenn Warnungen ausgegeben werden. |
-Wall | Aktivieren Sie alle Warnungen mit Ausnahme der -Werror , " -Wunusupported und " -Wwrite strings . |
Der Linker
Die Aufgabe des Linkers besteht darin, eine Reihe von Objektdateien ( .o
Dateien) zu einer binären ausführbaren Datei zu verknüpfen. Bei der Verknüpfung werden hauptsächlich symbolische Adressen in numerische Adressen aufgelöst . Das Ergebnis des Verknüpfungsprozesses ist normalerweise ein ausführbares Programm.
Während des Verbindungsvorgangs nimmt der Linker alle auf der Befehlszeile angegebenen Objektmodule auf, fügt systemspezifischen Startcode hinzu und versucht, alle externen Verweise im Objektmodul mit externen Definitionen in anderen Objektdateien (Objektdateien) aufzulösen kann direkt in der Befehlszeile angegeben werden oder implizit durch Bibliotheken hinzugefügt werden). Es wird dann Last - Adressen für die Objektdateien zugeordnet werden , das heißt, es gibt an, wo der Code und die Daten werden in dem Adressraum des fertigen Programms enden. Sobald sie die Ladeadressen erhalten hat, kann sie alle symbolischen Adressen im Objektcode durch "echte" numerische Adressen im Adressraum des Ziels ersetzen. Das Programm kann jetzt ausgeführt werden.
Dies umfasst sowohl die Objektdateien, die der Compiler aus Ihren Quellcodedateien erstellt hat, als auch Objektdateien, die für Sie vorkompiliert und in Bibliotheksdateien gesammelt wurden. Diese Dateien haben Namen, die auf .a
oder .so
.a
.so
müssen Sie sie nicht kennen, da der Linker weiß, wo sich die meisten befinden, und er wird sie bei Bedarf automatisch einbinden.
Implizites Aufrufen des Linkers
Wie der Vorprozessor ist der Linker ein separates Programm, das häufig als ld
(Linux verwendet collect2
zum Beispiel collect2
). Ebenso wie der Vorprozessor wird der Linker automatisch aufgerufen, wenn Sie den Compiler verwenden. Die normale Art, den Linker zu verwenden, lautet daher wie folgt:
% gcc foo.o bar.o baz.o -o myprog
Diese Zeile weist den Compiler an, drei Objektdateien ( foo.o
, bar.o
und baz.o
) zu einer binären ausführbaren Datei namens myprog
zu myprog
. Jetzt haben Sie eine Datei namens myprog
, die Sie ausführen können und die hoffentlich etwas myprog
und / oder Nützliches tun wird.
Expliziter Aufruf des Linkers
Es ist möglich, den Linker direkt aufzurufen, dies ist jedoch selten ratsam und in der Regel sehr plattformspezifisch. Das heißt, Optionen, die unter Linux funktionieren, funktionieren unter Solaris, AIX, MacOS, Windows und anderen Plattformen nicht unbedingt. Wenn Sie mit GCC arbeiten, können Sie mit gcc -v
sehen, was in Ihrem Namen ausgeführt wird.
Optionen für den Linker
Der Linker benötigt auch einige Argumente, um sein Verhalten zu ändern. Der folgende Befehl würde gcc bar.o
, foo.o
und bar.o
zu verknüpfen, aber auch die Bibliothek ncurses
.
% gcc foo.o bar.o -o foo -lncurses
Dies ist eigentlich (mehr oder weniger) äquivalent zu
% gcc foo.o bar.o /usr/lib/libncurses.so -o foo
(obwohl libncurses.so
sein libncurses.a
, was nur ein Archiv ist, das mit ar
). Beachten Sie, dass Sie die Bibliotheken (entweder über Pfadnamen oder über -lname
Optionen -lname
) nach den Objektdateien -lname
. Bei statischen Bibliotheken spielt die Reihenfolge, in der sie angegeben werden, eine Rolle. Bei gemeinsam genutzten Bibliotheken spielt die Reihenfolge oft keine Rolle.
Beachten Sie, dass auf vielen Systemen, wenn Sie mathematische Funktionen verwenden (von <math.h>
), müssen Sie angeben , -lm
die Mathematik - Bibliothek zu laden - aber Mac OS X und Mac OS Sierra dies nicht erforderlich ist . Es gibt andere Bibliotheken, die separate Bibliotheken auf Linux und anderen Unix-Systemen sind, jedoch nicht auf macOS - POSIX-Threads und POSIX-Echtzeit und Netzwerkbibliotheken. Folglich variiert der Verknüpfungsprozess zwischen den Plattformen.
Andere Zusammenstellungsoptionen
Dies ist alles, was Sie wissen müssen, um Ihre eigenen C-Programme zu erstellen. Im Allgemeinen empfehlen wir auch die Verwendung der -Wall
:
% gcc -Wall -c foo.cc
Die Option -Wall
bewirkt, dass der Compiler Sie vor legalen, aber zweifelhaften Code-Konstrukten warnt, und hilft Ihnen, sehr früh Fehler zu finden.
Wenn Sie möchten, dass der Compiler weitere Warnungen ausgibt (einschließlich deklarierter, aber nicht verwendeter Variablen, vergessen Sie die Rückgabe eines Werts usw.), können Sie diese Optionen verwenden, da sich -Wall
trotz des Namens nicht dreht alle möglichen Warnungen zu:
% gcc -Wall -Wextra -Wfloat-equal -Wundef -Wcast-align -Wwrite-strings -Wlogical-op \
> -Wmissing-declarations -Wredundant-decls -Wshadow …
Beachten Sie, dass clang
eine Option hat - -Weverything
was wirklich alle Warnungen in clang
-Weverything
.
Datentypen
Zum Kompilieren von C-Programmen müssen Sie mit fünf Arten von Dateien arbeiten:
Quelldateien : Diese Dateien enthalten Funktionsdefinitionen und haben Namen, die nach Konvention auf
.c
enden. Hinweis:.cc
und.cpp
sind C ++ - Dateien. keine C-Dateien.
zBfoo.c
Header-Dateien : Diese Dateien enthalten Funktionsprototypen und verschiedene Präprozessoranweisungen (siehe unten). Sie werden verwendet, um Quellcodedateien den Zugriff auf extern definierte Funktionen zu ermöglichen. Header-Dateien enden nach Konvention in
.h
.
zBfoo.h
Objektdateien : Diese Dateien werden als Ausgabe des Compilers erzeugt. Sie bestehen aus Funktionsdefinitionen in binärer Form, sind jedoch nicht selbst ausführbar. Objektdateien enden nach der Konvention
.o
, obwohl sie bei einigen Betriebssystemen (z. B. Windows, MS-DOS) häufig auf.obj
.
zBfoo.o
foo.obj
Binäre ausführbare Dateien : Diese werden als Ausgabe eines Programms erzeugt, das als "Linker" bezeichnet wird. Der Linker verknüpft mehrere Objektdateien miteinander, um eine Binärdatei zu erzeugen, die direkt ausgeführt werden kann. Binäre ausführbare Dateien haben unter Unix-Betriebssystemen kein spezielles Suffix, obwohl sie unter Windows
.exe
auf.exe
enden.
zBfoo
foo.exe
Bibliotheken : Eine Bibliothek ist eine kompilierte Binärdatei, ist jedoch keine ausführbare Datei (dh es gibt keine
main()
Funktion in einer Bibliothek). Eine Bibliothek enthält Funktionen, die von mehr als einem Programm verwendet werden können. Eine Bibliothek sollte mit Header-Dateien geliefert werden, die Prototypen für alle Funktionen in der Bibliothek enthalten. Diese Header-Dateien sollten in jeder Quelldatei, die die Bibliothek verwendet, referenziert werden (z. B.#include <library.h>
). Der Linker muss dann auf die Bibliothek verwiesen werden, damit das Programm erfolgreich kompiliert werden kann. Es gibt zwei Arten von Bibliotheken: statisch und dynamisch.- Statische Bibliothek : Eine statische Bibliothek (
.a
Dateien für POSIX-Systeme und.lib
Dateien für Windows - nicht zu verwechseln mit DLL-Import-Bibliotheksdateien , die ebenfalls die Erweiterung.lib
) ist statisch in das Programm integriert. Statische Bibliotheken haben den Vorteil, dass das Programm genau weiß, welche Version einer Bibliothek verwendet wird. Andererseits sind die ausführbaren Dateien größer, da alle verwendeten Bibliotheksfunktionen enthalten sind.
zBlibfoo.a
foo.lib
- Dynamische Bibliothek : Eine dynamische Bibliothek (
.so
Dateien für die meisten POSIX-Systeme,.dylib
für.dylib
und.dll
Dateien für Windows) wird vom Programm zur Laufzeit dynamisch verknüpft. Diese werden manchmal auch als gemeinsam genutzte Bibliotheken bezeichnet, da ein Bibliotheksbild von vielen Programmen gemeinsam genutzt werden kann. Dynamische Bibliotheken haben den Vorteil, dass weniger Speicherplatz beansprucht wird, wenn die Bibliothek von mehr als einer Anwendung verwendet wird. Sie ermöglichen auch Bibliotheksaktualisierungen (Fehlerbehebungen), ohne dass ausführbare Dateien neu erstellt werden müssen.
zBfoo.so
foo.dylib
foo.dll
- Statische Bibliothek : Eine statische Bibliothek (
Der Präprozessor
Bevor der C-Compiler mit dem Kompilieren einer Quellcodedatei beginnt, wird die Datei in einer Vorverarbeitungsphase verarbeitet. Diese Phase kann durch ein separates Programm erfolgen oder vollständig in eine ausführbare Datei integriert werden. In jedem Fall wird es automatisch vom Compiler aufgerufen, bevor die eigentliche Kompilierung beginnt. Die Vorverarbeitungsphase konvertiert Ihren Quellcode in einen anderen Quellcode oder eine andere Übersetzungseinheit, indem Sie Text ersetzen. Sie können es sich als "modifizierten" oder "erweiterten" Quellcode vorstellen. Diese erweiterte Quelle kann als echte Datei im Dateisystem vorhanden sein oder nur für kurze Zeit im Speicher gespeichert werden, bevor sie weiter verarbeitet wird.
Präprozessor-Befehle beginnen mit dem Nummernzeichen ("#"). Es gibt mehrere Präprozessor-Befehle. zwei der wichtigsten sind:
Definiert :
#define
wird hauptsächlich zur Definition von Konstanten verwendet. Zum Beispiel,#define BIGNUM 1000000 int a = BIGNUM;
wird
int a = 1000000;
#define
wird auf diese Weise verwendet, um nicht explizit einen konstanten Wert an vielen verschiedenen Stellen in einer Quellcodedatei schreiben zu müssen. Dies ist wichtig, wenn Sie den konstanten Wert später ändern müssen. Es ist viel weniger fehleranfällig, es einmal in#define
zu ändern, als es an mehreren Stellen im gesamten Code ändern zu müssen.Da
#define
nur erweiterte Such- und Ersetzungsvorgänge durchführt, können Sie auch Makros deklarieren. Zum Beispiel:#define ISTRUE(stm) do{stm = stm ? 1 : 0;}while(0) // in the function: a = x; ISTRUE(a);
wird:
// in the function: a = x; do { a = a ? 1 : 0; } while(0);
Auf den ersten Näherungswert ist dieser Effekt ungefähr derselbe wie bei Inline-Funktionen, aber der Präprozessor bietet keine Typüberprüfung für
#define
Makros. Dies ist bekanntermaßen fehleranfällig, und ihre Verwendung erfordert große Vorsicht.Beachten Sie auch hier, dass der Präprozessor auch Kommentare durch Leerzeichen ersetzt, wie unten erläutert.
Beinhaltet :
#include
wird verwendet, um auf Funktionsdefinitionen zuzugreifen, die außerhalb einer Quellcodedatei definiert sind. Zum Beispiel:#include <stdio.h>
bewirkt, dass der Präprozessor den Inhalt von
<stdio.h>
in die Quellcodedatei an der Position der#include
Anweisung#include
, bevor er kompiliert wird.#include
wird fast immer verwendet, um Header-Dateien#define
.#define
sind Dateien, die hauptsächlich Funktionsdeklarationen und#include
#define
Anweisungen enthalten. In diesem Fall verwenden wir#include
, um Funktionen wieprintf
undscanf
, deren Deklarationen sich in der Dateistdio.h
. C-Compiler erlauben keine Verwendung einer Funktion, es sei denn, sie wurde zuvor in dieser Datei deklariert oder definiert.#include
Anweisungen sind daher die Möglichkeit, zuvor geschriebenen Code in Ihren C-Programmen wiederzuverwenden.Logikoperationen :
#if defined A || defined B variable = another_variable + 1; #else variable = another_variable * 2; #endif
wird geändert in:
variable = another_variable + 1;
wenn A oder B zuvor im Projekt definiert wurden. Wenn dies nicht der Fall ist, führt der Präprozessor dies natürlich aus:
variable = another_variable * 2;
Dies wird häufig für Code verwendet, der auf verschiedenen Systemen läuft oder auf verschiedenen Compilern kompiliert wird. Da es globale Definitionen gibt, die compiler- / systemspezifisch sind, können Sie diese Definitionen testen und den Compiler immer nur den Code verwenden lassen, den er sicher kompiliert.
Bemerkungen
Der Präprozessor ersetzt alle Kommentare in der Quelldatei durch einzelne Leerzeichen. Kommentare werden durch
//
bis zum Ende der Zeile oder durch eine Kombination aus öffnendem/*
und schließendem*/
-Kommentar angegeben.
Der Compiler
Nachdem der C-Vorprozessor alle Header-Dateien enthalten und alle Makros erweitert hat, kann der Compiler das Programm kompilieren. Dazu wird der C-Quellcode in eine Objektcodedatei umgewandelt, die auf .o
endet und die binäre Version des Quellcodes enthält. Der Objektcode ist jedoch nicht direkt ausführbar. Um eine ausführbare Datei zu machen, müssen Sie auch Code hinzufügen , für alle Bibliotheksfunktionen , die waren #include
d in die Datei (dies ist nicht das gleiche wie die Erklärungen , einschließlich, das ist , was #include
tut). Dies ist die Aufgabe des Linkers .
Im Allgemeinen hängt die genaue Reihenfolge, in der ein C-Compiler aufgerufen wird, stark von dem System ab, das Sie verwenden. Hier verwenden wir den GCC-Compiler, wobei zu beachten ist, dass viele weitere Compiler existieren:
% gcc -Wall -c foo.c
%
ist die Eingabeaufforderung des Betriebssystems. Dies weist den Compiler an, den Vorprozessor für die Datei foo.c
und ihn dann in die Objektcodedatei foo.o
. Die Option -c
bedeutet, die Quellcodedatei in eine Objektdatei zu kompilieren, den Linker jedoch nicht aufzurufen. Diese Option -c
ist auf POSIX-Systemen wie Linux oder macOS verfügbar. andere Systeme verwenden möglicherweise eine andere Syntax.
Wenn sich Ihr gesamtes Programm in einer Quellcode-Datei befindet, können Sie stattdessen Folgendes tun:
% gcc -Wall foo.c -o foo
Dadurch wird der Compiler foo.c
, den Vorprozessor auf foo.c
, zu kompilieren und dann zu verknüpfen, um eine ausführbare Datei namens foo
zu erstellen. Die Option -o
gibt an, dass das nächste Wort in der Zeile der Name der ausführbaren binären Datei (Programm) ist. Wenn Sie nicht -o
angeben (wenn Sie nur gcc foo.c
), wird die ausführbare Datei aus historischen Gründen als a.out
bezeichnet.
Im Allgemeinen führt der Compiler beim Konvertieren einer .c
Datei in eine ausführbare Datei vier Schritte aus:
- Vorverarbeitung - erweitert textlich die Anweisungen
#include
und#include
#define
Makros in Ihrer.c
Datei - Kompilierung - wandelt das Programm in eine Assembly um (Sie können den Compiler bei diesem Schritt durch Hinzufügen der Option
-S
stoppen) - Assembly - wandelt die Assembly in Maschinencode um
- Verknüpfung - Verknüpft den Objektcode mit externen Bibliotheken, um eine ausführbare Datei zu erstellen
Beachten Sie auch, dass der Name des Compilers, den wir verwenden, GCC ist, der je nach Kontext sowohl für "GNU C-Compiler" als auch für "GNU-Compiler-Collection" steht. Andere C-Compiler existieren. Bei Unix-ähnlichen Betriebssystemen haben viele von ihnen den Namen cc
für "C-Compiler", der häufig eine symbolische Verbindung zu einem anderen Compiler darstellt. Auf Linux-Systemen ist cc
häufig ein Alias für GCC. Unter Mac OS oder OS-X deutet es auf Lärm.
Der POSIX-Standard c99
derzeit c99
als Namen eines C-Compilers - er unterstützt standardmäßig den C99-Standard. In früheren Versionen von POSIX war c89
als Compiler vorgeschrieben. POSIX verlangt auch, dass dieser Compiler die oben genannten Optionen -c
und -o
versteht.
Hinweis: Die Option -Wall
in beiden gcc
Beispielen weist den Compiler an, Warnungen zu fragwürdigen Konstruktionen zu drucken. -Wall
wird dringend empfohlen. Es ist eine gute Idee , auch andere hinzufügen Warnoptionen , zB -Wextra
.
Die Übersetzungsphasen
Seit dem C 2011-Standard, aufgeführt in §5.1.1.2 Übersetzungsphasen , wird die Übersetzung des Quellcodes in ein Programmabbild (z. B. die ausführbare Datei) so aufgelistet, dass sie in 8 geordneten Schritten erfolgt.
- Die Eingabe der Quelldatei wird (falls erforderlich) dem Quellzeichensatz zugeordnet. Trigraphs werden in diesem Schritt ersetzt.
- Fortsetzungszeilen (Zeilen, die mit
\
enden) werden mit der nächsten Zeile verbunden. - Der Quellcode wird in Whitespace- und Vorverarbeitungstoken geparst.
- Der Präprozessor wird angewendet, der Anweisungen ausführt, Makros erweitert und Pragmas anwendet. Jede durch
#include
eingezogene Quelldatei durchläuft die Übersetzungsphasen 1 bis 4 (wenn nötig rekursiv). Alle Anweisungen für den Präprozessor werden dann gelöscht. - Quellzeichensatzwerte in Zeichenkonstanten und String-Literalen werden dem Ausführungszeichensatz zugeordnet.
- Nebeneinander liegende String-Literale werden verkettet.
- Der Quellcode wird in Token analysiert, aus denen die Übersetzungseinheit besteht.
- Externe Referenzen werden aufgelöst und das Programmbild wird gebildet.
Eine Implementierung eines C-Compilers kann mehrere Schritte kombinieren, das resultierende Image muss sich jedoch so verhalten, als ob die oben genannten Schritte in der oben aufgelisteten Reihenfolge separat aufgetreten wären.