C++-Programmierung/ Weitere Grundelemente/ Vorarbeiter des Compilers

Aus Wikibooks

Wechseln zu: Navigation, Suche


Bevor der Compiler eine C++-Datei zu sehen kriegt, läuft noch der Präprozessor durch. Er überarbeitet den Quellcode, sodass der Compiler daraus eine Objektdatei erstellen kann. Diese werden dann wiederum vom Linker zu einem Programm gebunden. In diesem Kapitel soll es um den Präprozessor gehen, wenn Sie allgemeine Informationen über Präprozessor, Compiler und Linker brauchen, dann lesen Sie das Kapitel „Compiler“.

Inhaltsverzeichnis

[Bearbeiten] #include

Die Präprozessordirektive #include haben Sie schon häufig benutzt. Sie fügt den Inhalt der angegebenen Datei ein. Dies ist nötig, da der Compiler immer nur eine Datei übersetzen kann. Viele Funktionen werden aber in verschiedenen Dateien benutzt. Daher definiert man die Prototypen der Funktionen (und einige andere Dinge, die Sie noch kennenlernen werden) in so genannten Headerdateien. Diese Headerdateien werden dann über #include eingebunden, weshalb Sie die Funktionen usw. Aufrufen können.

Symbol move vote.svg
Thema wird später näher erläutert…

Ausführlich Informationen über Headerdateien erhalten Sie im gleichnamigen Kapitel.

#include bietet zwei Möglichkeiten Headerdateien einzubinden:

Crystal Clear app terminal.png
#include "name" // Sucht im aktuellen Verzeichnis und dann in den Standardpfaden des Compilers
#include <name> // Sucht gleich in den Standardpfaden des Compilers

„Aktuelles Verzeichnis“ bezieht sich immer auf das Verzeichnis, in welchem die Datei liegt.

Die erste Syntax funktioniert immer, hat aber den Nachteil, dass dem Compiler nicht mitgeteilt wird, dass es sich um eine Standardheaderdatei handelt. Wenn sich im aktuellen Verzeichnis beispielsweise eine Datei namens iostream befände und Sie versuchten über die erste Syntax, die Standardheaderdatei iostream einzubinden, bänden Sie stattdessen die Datei im aktuellem Verzeichnis ein, was sehr unwahrscheinlich sein sollte, da Sie hoffentlich immer Dateiendungen wie .hpp oder .h für Ihre eigenen Header verwenden. Außerdem verlängert das Einbinden von Standardheadern in Anführungszeichen den Präprozessordurchlauf, je nach Anzahl der Dateien im aktuellen Quellpfad.

Aus diesem Grund ist es wichtig zu wissen, ob der eingebundene Header für eine Bibliotheksfunktionalität in den vordefinierten Pfaden des verwendeten Compilers steht, oder ob es eigener Inhalt ist, oder es sich um Zusatzbibliotheken handelt, deren Include-Verzeichnisse an anderen Stellen zu finden sind. Es sollte die Variante gewählt werden, bei der sich die Headerdatei vom Präprozessor am schnellsten finden lässt.

Symbol kept vote.svg
Tipp

Verwenden Sie für Verweise auf Ihre eigenen Includes immer eine relative Pfadangabe mit normalen Slashes '/' als Verzeichnisseparator, damit Sie Ihre Quellcodeverzeichnisse auch an anderen Stellen und in anderen Entwicklungsumgebungen schneller kompiliert bekommen. Eine Verzeichnisangabe wie "/home/ichuser/code/cpp/projekt/zusatzlib/bla.h" oder "c:\users\manni\Eigene Dateien\code\cpp\projekt\zusatzlib\bla.h" ist sehr unangenehm und sorgt für große Verwirrung. Schreiben Sie es nun in "../zusatzlib/bla.h" um, können Sie später Ihr gesamtes Projekt leichter in anderen Pfaden kompilieren und sparen sich selbst und Ihrem Präprozessor einiges an Ärger und Verwirrung.

[Bearbeiten] #define und #undef

#define belegt eine Textsubstitution mit dem angegebenen Wert, z.B.:

Crystal Clear app terminal.png
#define BEGRUESSUNG "Hallo Welt!\n"
cout << BEGRUESSUNG;

Makros sind auch möglich, z.B.:

Crystal Clear app terminal.png
#define ADD_DREI(x,y,z) x+y+z+3
int d(1),e(20),f(4),p(0);
p = ADD_DREI(d,e,f);              // p = d+e+f+3;

Diese sind allerdings mit Vorsicht zu genießen, da durch die strikte Textsubstitution des Präprozessors ohne jegliche Logikprüfungen des Compilers, fatale Fehler einfließen können und sollten eher durch ( inline)-Funktionen, Konstanten oder sonstige Konzepte realisiert werden. Manchmal lässt es sich allerdings nicht umgehen, ein Makro (weiter) zu verwenden. Beachten Sie bitte immer, dass es sich hierbei um Anweisungen an eine Rohquelltextvorbearbeitungsstufe handelt und nicht um Programmcode.

Da anstelle von einfachen Werten auch komplexere Terme als Parameter für das Makro verwendet werden können und das Makro selbst auch in einen Term eingebettet werden kann, sollten immer Klammern verwendet werden. Bsp.:

Crystal Clear app terminal.png
#define MUL_NOK(x,y) x*y
#define MUL_OK(x,y) ((x)*(y))

resultNok = a * MUL_NOK(b,c+d); // a * b*c+d      = a*b*c+d        <= falsch
resultOk = a * MUL_OK(b,c+d);   // a * ((b)*(c+d)) = a*b*c + a*b*d  <= richtig

#undef löscht die Belegung einer Textsubstitution/Makro, z.B.:

Crystal Clear app terminal.png
#undef BEGRUESSUNG
Symbol redirect vote.svg
Buchempfehlung

Bitte denken Sie bei allen Ideen, auf die Sie nun kommen, dass Sie immer eine Alternative zur Makroprogrammierung haben. Verwenden Sie diese Makros vor allem als Zustandsspeicher für den Präprozessordurchlauf an sich und nicht um Funktionalitäten Ihres Programms zu erweitern.

Einer statischen, konstanten Defintion, sowie Templatefunktionen ist immer der Vorzug zu geben. Wenn Sie dies nicht so sehen, lesen Sie bitte hier weiter: C-Programmierung.

[Bearbeiten] #

Der #-Ausdruck erlaubt es, den einem Makro übergebenen Parameter als Zeichenkette zu interpretieren:

Crystal Clear app terminal.png
#define STRING(s) #s
cout << STRING(Test) << endl;

Das obige Beispiel gibt also den Text "Test" auf die Standard-Ausgabe aus.

[Bearbeiten] ##

Der ##-Ausdruck erlaubt es, in Makros definierte Strings innerhalb des Präprozessorlaufs miteinander zu kombinieren, z.B.:

Crystal Clear app terminal.png
#define A "Hallo"
#define B "Welt"
#define A_UND_B A##B

Als Resultat beinhaltet die Konstante A_UND_B die Zeichenkette "HalloWelt". Der ##-Ausdruck verbindet die Namen der symbolischen Konstanten, nicht deren Werte. Ein weiteres Anwendungsbeispiel:

Crystal Clear app terminal.png
#define MAKE_CLASS( NAME )       \
class NAME                       \
{                                \
  public:                        \
    static void Init##NAME() {}; \
};

...
MAKE_CLASS( MyClass )
...
MyClass::InitMyClass();

[Bearbeiten] #if, #ifdef, #ifndef, #else, #elif und #endif

Direktiven zur bedingten Übersetzung, d.h. Programmteile werden entweder übersetzt oder ignoriert.

Crystal Clear app terminal.png
#if ''Ausdruck1''
// Programmteil 1
#elif ''Ausdruck2''
// Programmteil 2
/*
   ...
 */

#else
// Programmteil sonst
#endif

Die Ausdrücke hinter #if bzw. #elif werden der Reihe nach bewertet, bis einer von ihnen einen von 0 verschiedenen Wert liefert. Dann wird der zugehörige Programmteil wie üblich verarbeitet, die restlichen werden ignoriert. Ergeben alle Ausdrücke 0, wird der Programmteil nach #else verarbeitet, sofern vorhanden.

Als Bedingungen sind nur konstante Ausdrücke erlaubt, d.h. solche, die der Präprozessor tatsächlich auswerten kann. Definierte Makros werden dabei expandiert, verbleibende Namen durch 0L ersetzt. Insbesondere dürfen keine Zuweisungen, Funktionsaufrufe, Inkrement- und Dekrementoperatoren vorkommen. Das spezielle Konstrukt

Crystal Clear app terminal.png
#defined ''Name''

wird durch 1L bzw. 0L ersetzt, je nachdem, ob das Makro Name definiert ist oder nicht.

#ifdef ist eine Abkürzung für #if defined.

#ifndef ist eine Abkürzung für #if ! defined.

Beispiel: Nehmen wir an, dass Sie Daten in zusammengesetzten Strukturen immer an 4-Byte Grenzen ausrichten wollen. Verschiedene Compiler bieten hierfür verschiedene compilerspezifische Pragma-Direktiven. Da diese Konfiguration nicht im C++-Standard definiert ist, kann nicht sichergestellt werden, dass es eine allgemeingültige Anweisung hierfür gibt. Sie definieren daher mit #define in einer Headerdatei, die überall eingebunden ist, welchen Compiler Sie verwenden. Z.B. mit #define FLAG_XY_COMPILER_SUITE in einer Datei namens "compiler-config-all.hpp". Das gibt Ihnen die Möglichkeit, an Stellen, an denen Sie compilerspezifisches Verhalten verwenden wollen, dieses auch auszuwählen.

Crystal Clear app terminal.png
#include "compiler-config-all.hpp"
#ifdef WIN32
#pragma pack(4)
#elif USING_GCC
#pragma align=4
#elif FLAG_XY_COMPILER_SUITE
#pragma ausrichtung(byte4)
#endif

[Bearbeiten] #error und #warning

#error gibt eine Fehlermeldung während des Compilerlaufs aus und bricht den Übersetzungsvorgang ab, z.B.:

Crystal Clear app terminal.png
#error Dieser Quellcode-Abschnitt sollte nicht mit compiliert werden!

#warning ist ähnlich wie #error, mit dem Unterschied, dass das Kompilieren nicht abgebrochen wird. Es ist allerdings nicht Teil von ISO-C++, auch wenn die meisten Compiler es unterstützen. Meistens wird es zum Debuggen eingesetzt:

Crystal Clear app terminal.png
#warning Dieser Quellcode-Abschnitt muss noch überarbeitet werden.

[Bearbeiten] #line

Setzt den Compiler-internen Zeilenzähler auf den angegebenen Wert, z.B.:

Crystal Clear app terminal.png
#line 100

[Bearbeiten] #pragma

Das #pragma-Kommando ist vorgesehen, um eine Reihe von Compiler-spezifischen Anweisungen zu implementieren, z.B.:

Crystal Clear app terminal.png
#pragma comment(lib, "advapi32.lib")

Kennt ein bestimmter Compiler eine Pragma-Anweisung nicht, so gibt er üblicherweise eine Warnung aus, ignoriert diese nicht für ihn vorgesehene Anweisung aber ansonsten.

Um z.B. bei MS VisualC++ eine "störende" Warnung zu unterdrücken, gibt man folgendes an:

Crystal Clear app terminal.png
#pragma warning( disable: 4010 ) // 4010 ist Nummer der Warnung

[Bearbeiten] Vordefinierte Präprozessor-Variablen

  • __LINE__: Zeilennummer
  • __FILE__: Dateiname
  • __DATE__: Datum des Präprozessoraufrufs im Format Monat/Tag/Jahr
  • __TIME__: Zeit des Präprozessoraufrufs im Format Stunden:Minuten:Sekunden
  • __cplusplus: Ist nur definiert, wenn ein C++-Programm verarbeitet wird


Persönliche Werkzeuge