[Logo der Universität Bayreuth]
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:
[arrow down]Überblick zum Übersetzungsvorgang
[transparentes Bild für Abstand][arrow down]1. Phase (Präprozessor)
[transparentes Bild für Abstand][arrow down]2. Phase (eigentlicher Compiler ohne Assembler und ohne Linker)
[transparentes Bild für Abstand][arrow down]3. Phase (Assembler)
[transparentes Bild für Abstand][arrow down]4. Phase (Linker)
[arrow down]detailliertes Beispiel zum Übersetzungsvorgang
[transparentes Bild für Abstand][arrow down]1. Phase (Präprozessor)
[transparentes Bild für Abstand][arrow down]2. Phase (eigentlicher Compiler ohne Assembler und ohne Linker)
[transparentes Bild für Abstand][arrow down]3. Phase (Assembler)
[transparentes Bild für Abstand][arrow down]4. Phase (Linker)
[arrow down]gute Nachricht

Überblick zum Übersetzungsvorgang

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).

 

detailliertes Beispiel zum Übersetzungsvorgang

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
             


 

gute Nachricht

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!

top top

Verbesserungsvorschläge, Fragen und Anregungen an
Robert Baier ([e-mail-Adresse von Robert Baier])
© 2003 Robert Baier;
© 1999-2002 Robert Baier, Sascha Herrmann
Last modified: 22.07.2015