Universität Bayreuth
Mathematisches
Institut
Einleitung
Erste Schritte
Mail und News
Drucken
KDE
LaTeX/TeX
Linksammlung
Linuxtools
Netzwerk
Programmieren
Windows
X Window
Anträge
Kontakt
|
Programmieren
Übersetzungsvorgang
Hier wird beschrieben, wie prinzipiell aus einem C- oder C++-Programm
ein ausführbares Executable entsteht.
Inhaltsverzeichnis:
Überblick
zum Übersetzungsvorgang
1.
Phase (Präprozessor)
2.
Phase (eigentlicher Compiler ohne Assembler und ohne Linker)
3.
Phase (Assembler)
4.
Phase (Linker)
detailliertes
Beispiel zum Übersetzungsvorgang
1.
Phase (Präprozessor)
2.
Phase (eigentlicher Compiler ohne Assembler und ohne Linker)
3.
Phase (Assembler)
4.
Phase (Linker)
gute
Nachricht
-
1. Phase (Präprozessor)
-
Erstellung eines C-/C++-Sourcecodes ohne Präprozessorbefehle
Der Präprozessor hat folgende Aufgaben:
-
Beendigung jeder Zeile mit Zeilenumbruch
-
Umwandlung von Trigraph- und Escape-Sequenzen in zugehörige
Zeichen
-
Zusammenfassen jeder mit "\" (Backslash) abgeschlossenen
Zeile mit der Folgezeile zu einer Zeile
-
Ersetzen aller Kommentare durch ein Leerzeichen
-
Ausführung der Präprozessor-Direktiven, die mit "#"
beginnen und stets eine Zeile lang sind, z.B. Textersetzung und
Erweiterung der Makros sowie das Einladen aller Headerfiles,
die mit "#include" eingeladen wurden.
Als Ausgabe erstellt der Präprozessor einen C- bzw. C++-Code ohne
Präprozessorbefehle.
-
2. Phase (eigentlicher Compiler ohne Assembler und ohne Linker)
-
eigentlicher Übersetzungsvorgang mit Erstellung eines Assemblercodes
Der (eigentliche) Compiler erstellt aus der Ausgabe des Präprozessors (enthält
keinerlei Präprozessor-Befehle mehr und nur noch C-/C++-Code) einen
Assembler-Code (eine lesbare Variante der
Prozessorbefehle).
Die meisten Compiler (IDEs wie Watcom, ..., auch der GCC) sind
allerdings standardmäßig so
konfiguriert, dass sie den Präprozessor, den eigentlichen
Compiler, den Assembler und den Linker automatisch nacheinander
aufrufen, vgl. Abschnitt gute Nachricht.
-
3. Phase (Assembler)
-
Erstellung einer Objectdatei
In der 3. Phase erzeugt der Assembler aus dem Output des eigentlichen
Compilers (den Assembler-Code, eine lesbare Variante der
Prozessorbefehle) ein binäres Objectfile (eine binäre
Auflistung aller Prozessorbefehle als eine Folge von Bytes, wobei
die Bytes entweder für einen Prozessorbefehl stehen oder
reine Datenbytes sind).
Ein Objectfile ist selbst noch nicht ausführbar, da u.a. die
Aufrufe von Funktionen aus externen Libraries (Dateien, die Objectcode
verschiedener Funktionen zu einer Datei zusammenfassen) noch
nicht aufgelöst sind.
-
4. Phase (Linker)
-
Erstellung eines ausführbaren Programmes (Binaries, Executables)
durch Linken der benötigten Libraries zur Objectdatei
Die in der 3. Phase noch nicht vom Assembler aufgelösten
Aufrufe von Funktionen aus externen Libraries werden hier aufgelöst.
Dabei gibt es zwei Konzepte, die statisch gelinkten Executables
und die dynamisch gelinkten. Bei ersteren ist der benötigte
compilierte Code dieser Funktionen aus externen Libraries in das
Executable (statisch) mit aufgenommen worden (Vorteil: Executable
hängt nicht vom Vorhandensein der richtigen dynamischen Libraries
ab, Nachteil: Executable wird wesentlich größer). Im zweiten
Fall wird beim Start des Executables (dynamisch, also zur Laufzeit)
der Code aus einer dynamischen Library angesprungen (Vorteil:
Executable wird wesentlich kleiner, Nachteil: Executable
hängt vom Vorhandensein der richtigen Version der
dynamischen Libraries ab).
Der Übersetzungsvorgang wird anhand von Befehlen zum C-Sourcecode
"Prog3.c" gezeigt.
Dieses File enthält vier Präprozessorbefehle (alle beginnen mit
"#"):
#include <stdio.h> /* Einladen eines Headerfiles */
#define N 10 /* Definition einer Konstante N=10 */
#define quadrat(x) x*x /* Definition eines Makros quadrat(..)
mit dem Argument x
(x*x ist die Realisierung des Makros)
*/
#define quadrat_besser(x) (x)*(x) /* bessere Variante vom Makro
quadrat(..)
*/
-
1. Phase (Präprozessor)
-
Der Präprozessor wird unter Linux mit "cpp" gestartet (oder
implizit durch Aufruf von "gcc" bzw. "g++").
Aufruf:
cpp -E Prog3.c > Prog3_mit_nr.i
Die Dateiumlenkung durch ">" ist notwendig, da sonst der
Output des Präprozessors am Bildschirm angezeigt wird.
Wirkung des Präprozessors:
-
Am Anfang wird das Headerfile "/usr/include/stdio.h" eingeladen,
das aber selbst wieder Headerfiles durch "#include"
nachlädt. Wie man im File
"Prog3_mit_nr.i"
sieht, sind das die Headerfiles
-
"/usr/include/features.h"
-
"/usr/include/sys/cdefs.h"
-
"/usr/include/gnu/stubs.h"
-
"/usr/lib/gcc-lib/i486-suse-linux/2.95.2/include/stddef.h"
-
"/usr/lib/gcc-lib/i486-suse-linux/2.95.2/include/stdarg.h"
-
"/usr/include/bits/types.h"
-
"/usr/include/libio.h"
-
"/usr/include/_G_config.h"
-
"/usr/include/bits/stdio_lim.h"
Diese Headerfiles enthalten entweder C-Code, der dann eingeladen
wird oder tragen für diese Compilieroptionen nichts Neues bei. Die
entstandenen Zeilen der Form
# Definition von neuen Bezeichnungen schon definierter/
# in C vordefinierter Datentypen
typedef unsigned int size_t;
...
typedef void *__gnuc_va_list;
...
# Definition neuer Datentypen (meist Strukturen)
typedef struct
{
long int __val[2];
} __quad_t;
...
# Funktionsprototypen (Deklaration von extern vorhandenen Funktionen)
extern int __underflow (_IO_FILE *) ;
...
extern int printf (const char * __format, ...) ;
...
extern int scanf (const char * __format, ...) ;
...
# Deklaration von extern vorhandenen Variablen
extern FILE *stdin;
...
sind durch das Einladen der oben genannten Headerfiles entstanden.
Der Prototyp für "printf(..)" bzw.
"scanf(..)" rechtfertigt das Verwenden dieser Methoden
in der "main(..)"-Funktion.
-
Er ersetzt jedes Auftreten von N
außerhalb eines Strings oder Zeichens durch den Wert 10.
-
Er ersetzt jedes Auftreten des Makros
quadrat(<Argument>)
durch
<Argument> * <Argument>,
also z.B.
quadrat(seiten[i]) durch
seiten[i]*seiten[i],
quadrat(10.0) durch
10.0*10.0,
quadrat(test_seite+1.0) durch
test_seite+1.0*test_seite+1.0.
Am letzten Beispiel sieht man die Problematik von
Präprozessormakros.
-
Er ersetzt jedes Auftreten des Makros
quadrat_besser(<Argument>)
durch
(<Argument>) *
(<Argument>),
also z.B.
quadrat_besser(test_seite+1.0) durch
(test_seite+1.0)*(test_seite+1.0).
Am letzten Beispiel sieht man den Vorteil dieses Makros
gegenüber dem Makro quadrat(..).
Das File
"Prog3_mit_nr.i"
enthält Kommentare der Form
# <Zeilennummer> <Filename>
Diese kann man mit der Option "-P" vermeiden, also durch
cpp -E -P Prog3.c > Prog3.i
(man vergleiche das File
"Prog3.i").
-
2. Phase (eigentlicher Compiler ohne Assembler und ohne Linker)
-
Der C- bzw. C++-Compiler von GCC wird unter Linux mit
"gcc" bzw. "g++" gestartet.
Aufruf ausgehend vom Präprozessor-Output und Erzeugung
des Assembler-Codes:
# Start vom Präprozessor-Output aus
gcc -x cpp-output -c -S Prog3.i
Die Option "-S" verhindert den (automatischen) Aufruf des
Assemblers, die Option "-c" verhindert die Erstellung eines
Executables (eines ausführbaren Programms).
Da ausgehend vom Präprozessor-Output
"Prog3.i"
die Compilierung stattfindet (und der Präprozessor übersprungen wird),
wird die Option "-x cpp-output" verwendet.
Es entsteht der Assembler-Code
"Prog3.s" mit
den Prozessorbefehlen in lesbarer Form.
Ausgehend vom C-Sourcefile kann man den Präprozessor und den
eigentlichen Compiler zusammen durch
# Start vom C-Sourcefile aus
gcc -c -S Prog3.c
aufrufen.
-
3. Phase (Assembler)
-
Der Assembler wird unter Linux mit "as" gestartet (oder
implizit durch Aufruf von "gcc" bzw. "g++").
Der Assembler erzeugt aus dem Assemblercode die Objectdatei.
Es entsteht das Objectfile
"Prog3.o",
das aber ein Binärfile ist (ein File mit allen möglichen Bytewerten,
das normalerweise keinen Aufschluss bringt, wenn man es im Editor
betrachtet).
Aufruf des Assemblers:
# Aufruf des Assemblers
as -o Prog3.o Prog3.s
Die Option "-o Prog3.o" bewirkt, dass das Objectfile nicht den
Namen "a.out" erhält, sondern den Filenamen
"Prog3.o".
Ein Objectfile besteht zwar schon aus Prozessorbefehlen, doch fehlt
der Code für bzw. der Sprung zu der Codestelle von verwendeten
Funktionen, die nicht im C-Sourcecode selbst enthalten sind
(im Beispiel die Funktionen "printf(..)" und
"scanf(..)"). Das Objectfile ist also noch nicht vom
Betriebssystem ausführbar.
-
4. Phase (Linker)
-
Der Linker wird unter Linux mit "ld" gestartet (oder
implizit durch Aufruf von "gcc" bzw. "g++").
Da der C-Compiler unter Unix die Math-Library nicht automatisch zum
Objectfile linkt (merkt man, wenn kein Executable und nur ein
Objectfile erstellt wird), muss man beim Compileraufruf normalerweise
die Math-Library (Programmsammlung mit mathematischen Funktionen)
explizit mit der Option
-lm
hinzulinken, d.h.
# gcc linkt diese Library automatisch mit dazu
gcc -ansi -Wall -pedantic dreieck_sin.c -lm
# g++ linkt diese Library automatisch mit dazu
g++ -ansi -Wall -pedantic dreieck_sin.cpp
Beim Linker muss man unterscheiden, ob ein Executable statisch
gelinkt werden soll (enthält selbst alle externen Funktionen, also auch
die Funktionen, die in C vordefiniert sind) oder ob es dynamisch
gelinkt werden soll (enthält nur ein Sprungziel zu den dynamischen
Libraries, unter Linux Dateien der Form "*.so*").
Statisch gelinkte Executables sind im Normalfall wesentlich größer
(im Speicherplatzbedarf, also in der Bytezahl),
hängen aber nicht vom Vorhandensein der passenden Version einer
dynamischen Library ab und sind immer noch ausführbar.
Dynamisch gelinkte Executables sind im Normalfall wesentlich kleiner,
suchen aber beim Start nach der dynamischen Library mit derselben
Version, die beim Übersetzen vorhanden war. Sollte diese in der
Zwischenzeit nicht mehr vorhanden sein, kann das Executable nicht
mehr starten.
In beiden Fällen müssen noch weitere Objectfiles und statische/dynamische
Libraries dazugelinkt werden, um alle benötigten Informationen zum
Objectfile hinzuzufügen. Der direkte Aufruf des Linkers "ld"
ist daher sehr kompliziert (normalerweise erledigt dies der Aufruf
des Compilers automatisch mit).
-
Erzeugung eines statisch gelinkten Executables
-
# Erstellung eines statisch gelinkten Executables
ld -static /usr/lib/crt1.o /usr/lib/crti.o -o Prog3_stat.out \
Prog3.o -L/usr/lib/gcc-lib/i486-suse-linux/2.95.2 -lc -lgcc \
/usr/lib/crtn.o
Es entsteht das Executable
"Prog3_stat.out"
(natürlich ein Binärfile, knapp 1132 KBytes groß), das auch wirklich
gestartet werden kann.
Die Option "-static" erzwingt die Erstellung eines
statisch gelinkten Executables, die Option "-o Prog3_stat.out"
erstellt als Output die Datei "Prog3_stat.out". Der
Standardfilename ist "a.out", dieser wird verwendet, wenn man
diese Option einfach weglässt.
Durch die Option "-lc" wird die statische Library
"/usr/lib/libc.a" zum Objectfile dazugelinkt. Da sich diese
im Standardsuchpfad "/usr/lib" befindet und mit
"lib" beginnt und nach "c" keine Zeichen mehr folgen
vor der Standardendung ".a", kann man statt des Librarynamen
auch kurz "-lc" schreiben.
Die Option "-L/usr/lib/gcc-lib/i486-suse-linux/2.95.2" bewirkt,
dass dem Standardsuchpfad das Verzeichnis
"/usr/lib/gcc-lib/i486-suse-linux/2.95.2" vorangestellt wird.
Damit kann dann auch die Option "-lgcc" verwendet werden, weil
die statische Library "libgcc.a" in diesem Verzeichnis liegt.
Alternativ hätte man auch folgenden Aufruf nehmen können (mit expliziten
Namen der Libraries):
# Erstellung eines statisch gelinkten Executables
ld -static /usr/lib/crt1.o /usr/lib/crti.o -o Prog3_stat.out \
Prog3.o /usr/lib/libc.a \
/usr/lib/gcc-lib/i486-suse-linux/2.95.2/libgcc.a \
/usr/lib/crtn.o
Ganz kurz geht es mit "gcc" selbst (allerdings ruft
man damit implizit den Linker "ld" und einen
vergleichbaren Befehl zu oben genannten auf).
# Erstellung eines statisch gelinkten Executables mit gcc
gcc -static -o Prog3_stat.out Prog3.o
-
Erzeugung eines dynamisch gelinkten Executables
-
# Erstellung eines dynamisch gelinkten Executables
ld -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o \
-o Prog3_dyn.out \
Prog3.o -lc /usr/lib/crtn.o
Es entsteht das Executable
"Prog3_dyn.out"
(natürlich ein Binärfile, knapp 11 KByte groß), das auch wirklich
gestartet werden kann.
Die Option "-dynamic-linker /lib/ld-linux.so.2" schreibt die
dynamische Library "/lib/ld-linux.so.2"
als dynamischen Linker vor
(was laut Informationsseite von "ld" eigentlich nicht
erforderlich sein sollte).
Das Weglassen der Option "-static"
bewirkt den Standard, nämlich die Erstellung dynamisch gelinkter
Executables.
Die Option "-o Prog3_dyn.out"
erstellt als Output die Datei "Prog3_dyn.out". Der
Standardfilename ist "a.out", dieser wird verwendet, wenn man
diese Option einfach weglässt.
Die Option "-lc" bewirkt jetzt
das Linken der dynamischen Library
"/lib/libc.so.6" zum Objectfile. Da sich diese
im Standardsuchpfad "/lib" befindet und mit
"lib" beginnt und nach "c" keine Zeichen mehr folgen
vor der Standardendung ".so*", kann man statt des Librarynamen
auch kurz "-lc" schreiben.
Die Optionen "-L/usr/lib/gcc-lib/i486-suse-linux/2.95.2"
und "-lgcc" sind überflüssig geworden.
Alternativ hätte man auch folgenden Aufruf nehmen können (mit expliziten
Namen der Library):
# Erstellung eines dynamisch gelinkten Executables
ld -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o \
-o Prog3_dyn.out Prog3.o \
/lib/libc.so.6 /usr/lib/crtn.o
Ganz kurz geht es wieder mit "gcc" selbst (allerdings ruft
man damit implizit den Linker "ld" und einen
vergleichbaren Befehl zu oben genannten auf).
# Erstellung eines dynamisch gelinkten Executables mit gcc
gcc -o Prog3_dyn.out Prog3.o
Und hier kommt die gute Nachricht:
Diese vier Phasen werden automatisch durchlaufen, wenn man den Compiler
ganz normal aufruft!
Man muss also gar nicht (genau) wissen, dass während der Compilerung
diese Phasen durchlaufen werden (höchstens bei der Beurteilung von
Fehlermeldungen von Compiler oder Linker ist das hilfreich).
Unter Unix wird der Compiler (und anschließend der Linker) mit dem
Befehl "gcc" bzw. "g++" aufgerufen. Ohne die
Option "-c" erzeugt der Compiler sofort ein Executable.
# Compiler gcc erstellt nur ein Objectfile
# namens "Prog3.o"
gcc -c Prog3.c
# Compiler gcc erstellt sofort ein Executable
# namens "a.out"
gcc Prog3.c
# Compiler g++ erstellt nur ein Objectfile
# namens "hello.o"
g++ -c hello.cpp
# Compiler g++ erstellt sofort ein Executable
# namens "a.out"
g++ hello.cpp
Es ergeben sich die einfache(re)n Aufrufe zur Erzeugung statisch
gelinkter Executables
# Erstellung eines statisch gelinkten Executables
gcc -static Prog3.c
# Erstellung eines statisch gelinkten Executables
# namens "Prog3_stat.out"
gcc -o Prog3_stat.out Prog3.c
# Erstellung eines dynamisch gelinkten Executables
# namens "a.out"
gcc Prog3.c
# Erstellung eines dynamisch gelinkten Executables
# namens "Prog3_dyn.out"
gcc -o Prog3_dyn.out Prog3.c
bzw. dynamisch gelinkter Executables:
# Erstellung eines dynamisch gelinkten Executables
# namens "a.out"
gcc Prog3.c
# Erstellung eines dynamisch gelinkten Executables
# namens "Prog3_dyn.out"
gcc -o Prog3_dyn.out Prog3.c
Den Unterschied der beiden Executables zeigt der Befehl
"ldd", der alle zugehörigen dynamischen Libraries
auflistet, die zum Start des dynamisch gelinkten Executables nötig sind.
ldd Prog3_dyn.out
# typische Ausgabe:
libc.so.6 => /lib/libc.so.6 (0x40022000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Damit wird aufgezeigt, welche dynamische Library durch die Option
"-lc" gelinkt wurde und dass als dynamischer Linker die Library
"/lib/ld-linux.so.2" verwendet wird.
Dagegen zeigt der Befehl für das statisch gelinkte Executable
keine solche Liste von dynamischen Libraries.
ldd Prog3_stat.out
# typische Ausgabe:
not a dynamic executable
Tipp:
Es empfehlen sich die folgenden Optionen beim Aufruf des Compilers
und des Linkers, um die ANSI C- bzw. ANSI C++-Kompatibilität zu
sichern:
# GNU-C-Compiler für ANSI C++-Programme, erstellt gleich ein Executable
# namens "a.out"
gcc -ansi -Wall -pedantic Prog3.c
# GNU-C++-Compiler für ANSI C++-Programme, erstellt gleich ein Executable
# namens "a.out"
g++ -ansi -Wall -pedantic hello.cpp
Grundsätzlich sollte man auf ANSI-Kompatibilität beim Compilieren
achten!
|