Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

6 Präprozessordirektiven

6 Präprozessordirektiven

Bisher haben Sie unmerklich einen Mechanismus benutzt, welcher zu den Präprozessordirektiven gehört, nämlich die "includes". Jede dieser Direktiven ist dadurch gekennzeichnet, dass sie zum einen mit einer "#" beginnen und in den meisten Fällen nicht mit einem Semikolon enden. Doch was hat es mit diesen Direktiven auf sich? Dazu muss ich ein wenig ausholen.

Immer dann, wenn Sie Ihren Quelltext kompilieren wollen, laufen mehrere Mechanismen im Hintergrund ab. Vor der Syntaxprüfung und dem eigentlichen Kompilieren, wird der s.g. Präprozessor aktiv. Er bereitet den Quelltext für die weitere Verarbeitung vor. Neben dem Zusammensetzen von Dateien, werden auch Quelltextstücke ersetzt bzw. sogar generiert (wie ich Ihnen am Ende des Tutorials noch zeigen werde). Mit den Präprozessordirektiven gibt man eben genau diesem Prozess an, was er wie machen soll und genau darum wird es in diesem Kapitel gehen.

Diese Direktiven werden immer zu Beginn einer Datei deklariert und haben nichts in einer Funktion zu suchen. Oftmals ist es sogar klug, sich eine separate Datei zu erstellen, in welche man nur solche Definitionen vereinbart (gerade wenn es sehr viele solcher Direktiven gibt).

Zum Seitenanfang
Zum Inhaltsverzeichnis

6.2 Bedingtes Kompilieren mit #if, #elif und #endif

6.2 Bedingtes Kompilieren mit #if, #elif und #endif

Jetzt wird die ganze Sache wesentlich interessanter. Mit den folgenden Direktiven, kann man steuern, wie oder besser gesagt welcher Teil eines Quelltextes kompiliert werden soll, indem man ein paar Bedingungen und ein paar Schalter einbaut, was gerade im Zusammenhang mit "defines" interessant ist. So kann man beispielsweise ein Programm in mehreren Sprachen bauen. Die Syntax erinnert sehr stark an die normale if Anweisung, nur dass man hier keine geschweiften Klammern setzen braucht/darf und dass man mit "elif" mehrere Fälle behandeln kann, so wie das in etwa bei einer switch Anweisung der Fall ist. Man braucht zwar keine Klammern zu setzen, aber trotzdem muss man dem Präprozessor sagen, wo der Anweisungsblock zu Ende ist. Dies geschieht mittels der Direktive "endif".

Im folgendem Beispiel werde ich eine einfache Begrüßung ausgeben und der Programmierer, also Sie, können entscheiden, in welcher Sprache das Programm kompiliert werden soll.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
					
#define DEF_SPRACHE spanisch

#if DEF_SRPACHE == spanisch
	#define DEF_STRING_AUSGABE "Ola\n"
#elif DEF_SPRACHE == englisch
	#define DEF_STRING_AUSGABE "Hello\n"
#else
	#define DEF_STRING_AUSGABE "Hallo\n"
#endif



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	printf(DEF_STRING_AUSGABE);

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Ausgabe:

Ola
		

Wenn Sie also hinter DEF_SPRACHE z.B. englisch schreiben, würde Hello ausgegeben werden.

Zum Seitenanfang
Zum Inhaltsverzeichnis

6.4 Bedingtes Kompilieren mit #ifdef und #ifndef

6.4 Bedingtes Kompilieren mit #ifdef und #ifndef

Satt zu prüfen, ob ein "define" einen bestimmten Wert hat, kann man auch prüfen, ob eine Marke überhaupt definiert wurde oder nicht. "ifdef" steht für "if defined", also "wurde etwas definiert" und "ifndef" steht für "if not defined", also "wurde etwas nicht definiert". So könnte man also obiges Beispiel auch wie folgt bauen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
					
#define DEF_SPANISCH

#ifdef DEF_SPANISCH
	#define DEF_STRING_AUSGABE "Ola\n"
#elif defined DEF_ENGLISCH
	#define DEF_STRING_AUSGABE "Hello\n"
#else
	#define DEF_STRING_AUSGABE "Hallo\n"
#endif



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	printf(DEF_STRING_AUSGABE);

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Ausgabe:

Ola
		

Das selbe Spiel funktioniert auch umgekehrt, also mit "ifndef", welches in der Regel sehr häufig mit Header-Dateien benutzt wird und was ich im nächsten großen Kapitel ausführlich behandeln werde.

Zum Seitenanfang
Zum Inhaltsverzeichnis

6.5 Importieren von Dateien mit #include

6.5 Importieren von Dateien mit #include

Mit dieser Direktive kann man sich ganze Quelltextdateien genau an die Stelle hereinholen, an welcher die Anweisung steht. Es ist also ganz grob gesagt eine Importfunktion, um aus mehreren Quelltextdateien eine zu machen und genau das wird mit Header-Dateien gemacht, worum es im nächsten großen Kapitel gehen wird.

Wichtig an dieser Stelle ist nur, wie man so ein "include" definiert. Es gibt zwei Arten, nämlich einmal mit einer Suche in den festgelegten Includeverzeichnissen des Projektes bzw. auch der Entwicklungsumgebung oder mit der Suche im aktuellen Projektverzeichnis bzw. auch an absoluten Pfadadressen.

Das Schema sieht so aus: #include [<|"]<Dateipfad>["|>]

Und hier noch ein kleines Beispiel mit beiden Varianten.

 1
 2
 3
 4
 5
 6
					
// Sucht in den Includeverzeichnissen von MS Visual Studio
#include <stdio.h>
// Sucht im aktuellen Programmverzeichnis, wo auch die main.cpp liegt
#include "res.h"
// Sucht Auf Laufwerk C
#include "C:\test.h"
					
Zum Seitenanfang
Zum Inhaltsverzeichnis

6.3 Compiler spezifische Direktiven mit #pragma

6.3 Compiler spezifische Direktiven mit #pragma

Wie die Überschrift schon vermuten lässt, handelt es sich bei einem "#pragma", um keinen Standard, was zur Folge hat, dass ein Quelltext mit dieser Direktive, zwar in Visual Studio, aber nicht mit dem "gcc" kompiliert werden kann und umgekehrt. Was Sie alles genau mit dieser Direktive anfangen können, müssen Sie in der Beschreibung des Compilers nachlesen, den Sie einsetzen. Hier sei nur schon einmal so viel erwähnt, dass z.B. Visual Studio diese Direktive in Zusammenhang mit Klassen und Header-Dateien benutzt, aber dazu auch später mehr.

Zum Seitenanfang
Zum Inhaltsverzeichnis

6.1 Quelltextbausteine mit #define

6.1 Quelltextbausteine mit #define

Ein sehr nützliches Werkzeug sind die s.g. "defines". Mit ihnen kann man Werte/Konstanten, ganze Textbausteine, oder sogar Codefragmente generieren. Der größte Vorteil liegt meines Erachtens darin, dass man eine zentrale Stelle hat, an der Sachen vereinbart werden können, die z.B. dann für das gesamte Projekt gelten. So spart man sich stundenlanges suchen nach Ausgabetexten, um Rechtschreibfehler zu verbessern - man hat alles auf einen Blick. Des Weiteren wird dafür kein zusätzlicher Speicherplatz verbraucht, da der Präprozessor im gesamten Quelltext nach diesen Bausteinen sucht und sie mit dem ersetzt, was Sie definiert haben. Die eigentlichen Definitionen sind dann am Ende nicht mehr in Ihrem Programm. Es handelt sich also um ein reines Suchen und Ersetzen.

Das Schema sieht so aus: #define <SuchMarke> <ErsetzterQuelltext>

Hier mal ein kleines Beispiel.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
					
#define	DEF_STRING_EINGABE	"Bitte geben Sie einen Wert ein: "
#define	DEF_STRING_AUSGABE	"Sie haben %i eingegeben!\n"



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	int iWert;
	printf(DEF_STRING_EINGABE);
	scanf("%i", &iWert);
	printf(DEF_STRING_AUSGABE, iWert);
	
	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Ausgabe:

Bitte geben Sie einen Wert ein: 15
Sie haben 15 eingegeben!
		

Wie Sie vielleicht schon erkennen, wird der Textbaustein DEF_STRING_EINGABE mit der dazugehörigen Definition ersetzt, nämlich "Bitte geben Sie einen Wert ein". Dabei ist wichtig, dass der Name eines "defines", immer nur aus einem Wort bestehen darf. Dahinter darf so viel stehen, wie Sie lusstig sind und das wird dann wirklich eins zu eins ersetzt (also auch alle Sonderzeichen wie Semikolon oder Anführungszeichen). Des Weiteren sollten Sie "defines" immer komplett in Großbuchstaben schreiben, damit man dann später im weiteren Quelltext sofort sieht, dass dort etwas ersetzt wird. Ich bevorzuge sogar immer noch den Typ mit einfließen zu lassen, aber das ist Geschmackssache.

Mit diesem Werkzeug kann man aber auch, wie gesagt, Quelltext zusammen bauen, da man "defines" auch ineinander verschachteln kann. Allerdings sollte man mit so etwas vorsichtig umgehen, da man so schnell Verwirrung stiften kann. Trotzdem möchte ich Ihnen kurz zeigen, wie das geht.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
					
#define	DEF_INT_ERROR		1
#define	DEF_STRING_ERROR	"Der Fehler %i ist aufgetreten!", DEF_INT_ERROR
#define	DEF_PRESS_KEY		printf("Druecken Sie <ENTER> um fortzufahren!");\
				fflush(stdin);\
				getchar();



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	printf(DEF_STRING_ERROR);
	DEF_PRESS_KEY
	
	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Ausgabe:

Der Fehler 1 ist aufgetreten
Druecken Sie <ENTER> um fortzufahren!
		

Ein "define" muss normalerweise innerhalb einer Zeile stehen. Möchte man aber, aus Gründen der Übersichtlichkeit, doch einen Zeilenumbruch machen, dann setzt man ein "\" am Ende der Zeile und signalisiert somit, dass die Definition in der nächsten Zeile weiter geht.

Hin und wieder kommt es vor, dass man so ein Textbaustein zwischendurch neu belegen möchte. Auch dies ist möglich und sieht wie folgt aus.

 1
 2
 3
					
#define	DEF_STRING	"Hallo"
#undef	DEF_STRING
#define	DEF_STRING	"Hallo Welt"
					

Aber trotz der ganzen Bequemlichkeit, sollten Sie für Quelltextfragmente entweder wirklich Funktionen bauen oder, wie ich später noch zeigen werde, Templates bauen. Dieser Mechanismus wurde vorwiegend in der Vergangenheit gerne mal eingesetzt um das Programm noch ein wenig schneller laufen zu lassen, da ein Funktionsaufruf zeit kostet (was auf den heutigen schnellen Rechnern nicht mehr von Bedeutung ist). Für Konstanten und Textbausteine wie Ein - und Ausgaben, sind "defines" allerdings sehr genial.

Abschließend möchte ich noch erwähnen, dass es einen ganz großen Haken an diesem Mechanismus gibt. Wenn man ein Programm debuggen will, kann man nicht sehen, welcher Wert oder welche Codezeilen sich hinter einem "define" verbergen. Das kann sehr nervig sein und die Arbeit ungemein schwerer machen. Das ist genau der Grund, warum dieser Mechanismus langsam "ausstirbt". Fangen Sie jetzt also nicht an, nur noch "defines" zu benutzen – setzen Sie diese mit Bedacht ein, wenn Sie sie verwenden möchten.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012