| #include <pronix.de> |
|
|
In diesem Kapitel erfahren Sie einiges zum Präprozessor. Der Präprozessor ist ein Teil des Compilers, der noch vor der Übersetzung einige Änderungen am Quelltext vornimmt. Wenn in der Sprache C ein Programm übersetzt (kompiliert und gelinkt) werden soll, dann wird, bevor der Compiler den Quelltext verarbeitet, von einem besonderen Teil des Compilers, dem Präprozessor, ein zusätzlicher Übersetzungslauf durchgeführt. Präprozessor-Direktiven beginnen immer mit dem Zeichen # am Anfang der Zeile. Außerdem darf pro Zeile nur eine Direktive eingesetzt werden. Folgendes ist also nicht erlaubt: #include <stdio.h> #include <stdlib.h> Kommentare hingegen dürfen sehr wohl hinter einer Direktive stehen. #include <stdio.h> /* Headerdatei für Standardfunktionen */ Die folgenden Arbeiten fallen für den Präprozessor neben der Quelltextersetzung ebenfalls an:
Des Weiteren gibt es Aufgaben für den Präprozessor, die vom Programmierer gesteuert werden können:
Auf die Präprozessor-Direktiven, die Sie als Programmierer selbst steuern kön-nen, soll auf den folgenden Seiten eingegangen werden.
13.1. Einkopieren von Dateien mittels #include
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Headerdatei | Bedeutung |
| assert.h | Fehlersuche und Debugging |
| ctype.h | Zeichentest und Konvertierung |
| errno.h | Fehlercodes |
| float.h | Limits/Eigenschaften für Gleitpunkttypen |
| limits.h | Implementierungskonstanten |
| locale.h | Länderspezifische Eigenschaften |
| math.h | Mathematische Funktionen |
| setjmp.h | Unbedingte Sprünge |
| signal.h | Signale |
| stdarg.h | Variable Parameterübergabe |
| stddef.h | Standard-Datentyp |
| stdio.h | Standard-I/O |
| stdlib.h | Nützliche Funktionen |
| string.h | Zeichenkettenoperationen |
| time.h | Datum und Uhrzeit |
Mit #define ist es möglich, Zeichenketten anzugeben, die vor der Übersetzung des Programms gegen eine andere Zeichenkette ausgetauscht werden. Sie erinnern sich sicherlich aus dem vorangegangenen Kapitel daran, wie ein Programm übersetzt wird. Auch hier wird durch das Zeichen # bewirkt, dass der Präprozessor zuerst seine Arbeit verrichtet, bevor das werdende Programm vom Compiler in Assembler und dann in Maschinensprache übersetzt wird.
Hierzu die Syntax der define-Direktive:
#define Bezeichner Ersatzbezeichner #define Bezeichner(Bezeichner_Liste) Ersatzbezeichner
Bei der ersten Syntaxbeschreibung wird eine symbolische Konstante und im zweiten Fall ein Makro definiert.
13.2.1 Symbolische Konstanten mit #define
Ein erstes Programmbeispiel, welches eine symbolische Konstante definiert:
#include <stdio.h>
#define EINS 1
int main()
{
printf("%d\n",EINS);
return 0;
}
Im Programm wird jede symbolische Konstante EINS mit dem Wert 1 definiert. Wenn Sie das Programm übersetzen, werden vor der Kompilierung alle Namen mit EINS im Quelltext vom Präprozessor durch den Wert 1 ersetzt. Die Konstante EINS müssen Sie nicht wie im Beispiel ausdrücklich in großen Buchstaben schreiben. Dies dient nur der besseren Übersicht. Aber Achtung, Folgendes funktioniert nicht:
printf("EINS");
In diesem Fall wird tatsächlich der String "EINS" auf dem Bildschirm ausgegeben und nicht der Wert 1. Das bedeutet, hier wird die Konstante EINS nicht durch 1 ersetzt.
|
Merke |
|
Beachten Sie, dass #define-Makros Konstanten sind. Einmal festgelegte Konstanten können zur Laufzeit des Programms nicht mehr geändert werden. |
|
Welchen Vorteil haben solche Defines? Das soll das folgende Programm demonstrieren:
#include <stdio.h>
/* Bei Linux muss für math.h der Compilerflag -lm
mit angegeben werden:
gcc -o synkonst2 symkonst2.c -lm
*/
#include <math.h>
#define PI 3.1415926f
/* Programm zur Berechnung von */
/* Kreisfläche(A), Durchmesser(d) und Umfang(U) */
/* und Durchmesser aus Umfang */
void kreisflaeche(void)
{
float A,d;
printf("Durchmesser des Kreises eingeben: ");
scanf("%f", &d);
A = d*d*PI / 4;
printf("Die Kreisfläche beträgt %f\n", A);
}
void durchmesser(void)
{
float A, d;
printf("Kreisfläche des Kreises eingeben: ");
scanf("%f", &A);
d =(float) sqrt((double)4*A/PI);
printf("Der Duchmesser des Kreises ist %f\n", d);
}
void kreisumfang(void)
{
float U, d;
printf("Durchmesser des Kreises eingeben: ");
scanf("%f", &d);
U = d * PI;
printf("Der Umfang des Kreises beträgt %f\n", U);
}
void d2umfang(void)
{
float U,d;
printf("Umfang des Kreises eingeben: ");
scanf("%f",&U);
d = U/PI;
printf("Der Durchmesser des Kreises beträgt %f\n", d);
}
int main()
{
kreisflaeche();
durchmesser();
kreisumfang();
d2umfang();
return 0;
}
In diesem Programm werden einfache Berechnungen von kreisförmigen Flächen durchgeführt. Statt PI an jeder Stelle im Programm erneut festzulegen, ist hier die textliche Ersetzung mittels define besser geeignet. Dadurch wird auch garantiert, dass stets der gleiche Wert überall im Programm verwendet wird. Sollten Sie z.B. eine genauere Angabe von PI benötigen, so müssen Sie nur die symbolische Konstante ändern.
Einen weiteren Vorteil bietet z.B. die Verwendung bestimmter Konstanten, etwa einer Landeswährung. Falls eine Änderung erforderlich wird, kann diese ohne viel Aufwand für das gesamte Programm an einer zentralen Stelle vorgenommen werden. Sie können bei Makrodefinitionen auch auf früher definierte Namen zurückgreifen, wie im folgenden Beispiel:
#define PI 3.141592653 #define PI_2 PI*2
Hier wird zuerst PI definiert und in der nächsten Zeile der Wert von PI*2, der textlich durch PI_2 ersetzt wird.
|
Tipp |
|
Verzichten Sie bei textlichen Ersetzungen auf überflüssige Berechnungen. So führt zum Beispiel ein Define der Art #define PI atan(1)*4 dazu, dass dieser Wert im Programm jedes Mal erneut berechnet wird. Verwenden Sie für solche Fälle besser eine const-Variable wie zum Beispiel: |
|
Mit der #define-Direktive können nicht nur Zahlen als symbolische Konstanten festgelegt werden, sondern auch Strings. Beispiel:
#include <stdio.h>
#define GANZZAHL int
#define SCHREIB printf(
#define END );
#define EINGABE scanf(
#define ENDESTART return 0;
#define NEUEZEILE printf("\n");
#define START int main()
#define BLOCKANFANG {
#define BLOCKENDE }
Mit diesen Festlegungen wurde mit minimalem Aufwand eine eigene kleine Programmiersprache erzeugt! Ein Programm in der neuen Sprache könnte zum Beispiel so aussehen:
START BLOCKANFANG GANZZAHL zahl; SCHREIB "Hallo Welt" END NEUEZEILE SCHREIB "Zahleingabe: " END EINGABE "%d", &zahl END SCHREIB "Die Zahl war %d", zahl END ENDESTART BLOCKENDE
Hier wurde nicht wirklich eine neue Programmiersprache erzeugt. Statt int main() wird in dem Programm einfach START oder statt return 0 wird ENDESTART geschrieben. Der Präprozessor ersetzt vor der Übersetzung des Compilers die Pseudo-Sprache wieder nach C.
Diese Pseudo-Sprache soll jetzt in eine eigene Headerdatei gepackt werden. Legen Sie dazu eine neue Quelldatei mit folgendem Inhalt an:
#ifndef MYSYNTAX_H
#define MYSYNTAX_H
#include <stdio.h>
#define GANZZAHL int
#define SCHREIB printf(
#define END );
#define EINGABE scanf(
#define ENDESTART return 0;
#define NEUEZEILE printf("\n");
#define START int main()
#define BLOCKANFANG {
#define BLOCKENDE }
#endif /*MYSYNTAX_H*/
Speichern Sie diese Codezeilen unter dem Namen MYSYNTAX.H
Jetzt noch das Hauptprogramm inklusive der neuen Headerdatei:
#include "MYSYNTAX.H"
START
BLOCKANFANG
GANZZAHL zahl;
SCHREIB "Hallo Welt" END
NEUEZEILE
SCHREIB "Zahleingabe: " END
EINGABE "%d", &zahl END
SCHREIB "Die Zahl war %d", zahl END
NEUEZEILE
ENDESTART
BLOCKENDE
Speichern Sie das Hauptprogramm im selben Verzeichnis, in dem sich auch MYSYNTAX.H befindet. Den Namen für das Hauptprogramm können Sie frei wählen, zum Beispiel: mein_C.c. Übersetzen Sie dieses Programm. Befindet sich die Headerdatei MYSYNTAX.H in einem anderen Verzeichnis als das Hauptprogramm, muss dies dem Compiler mitgeteilt werden. Befindet sich die Headerdatei z.B. in /HOME/MYHOME/MYHEADER, wird dies dem Präprozessor wie folgt mitgeteilt: entweder
#include "/home/myhome/myheader/MYSYNTAX.H"
oder auf MS Windows-Systemen (C:\ sei Ihr Arbeitslaufwerk)
#include "c:\Programme\MYSYNTAX.H"
13.2.2 Makros mit #define
Weiterhin haben Sie die Möglichkeit, mit der define-Direktive parametrisierte Makros zu schreiben. Ein Beispiel:
#include <stdio.h>
#define KLEINER_100(x) ((x) < 100)
void klHundert(int zahl)
{
if(KLEINER_100(zahl))
printf("Yep! Die Zahl ist kleiner als 100!\n");
else
printf("Die Zahl ist größer als 100!\n");
}
int main()
{
int b=99;
klHundert(b);
return 0;
}
Ein parametrisiertes Makro erkennen Sie daran, dass unmittelbar nach dem Makronamen eine Klammer folgt:
#define KLEINER_100(x) ((x) < 100)
Allein stehende Makros benötigen bei Verwendung im Programm kein Semikolon am Ende der Zeile. Daran lassen sich Makros auch oft erkennen. Es wird zwar nicht vom Compiler moniert, wenn Sie dennoch Semikolons setzen, es ist aber nicht erforderlich.
Im betrachteten Fall haben Sie den formalen Parameter x. Dieser kann auf der rechten Seite des Makros beliebig oft verwendet werden. Dabei muss beachtet werden, dass dieser formale Parameter ebenfalls auf der rechten Seite in Klammern stehen muss. Folgende Definition wäre falsch:
#define KLEINER_100(x) (x < 100)
da sich hier der Parameter x nicht zwischen Klammern befindet. Die Zeile
if(KLEINER_100(zahl))
sieht nach dem Präprozessorlauf, also vor der eigentlichen Kompilierung, so aus:
if((zahl) < 100)
Eine weitere, häufig eingesetzte Variante dieser Art ist:
#define MAX(x,y) ( (x)<=(y) ?(y) :(x) )
Hier werden gleich zwei Argumente als Parameter verwendet. Beide Parameter werden durch ein Komma voneinander getrennt. Bei diesem Makro wird die größere der beiden Dezimalzahlen ermittelt. Ein weiteres Beispiel:
#define TAUSCHE(x,y) { \
int j; \
j=x; x=y; y=j; \
}
Mit diesem Makro werden zwei Integer-Werte vertauscht. Wie sich ein Makro mit mehreren Statements über mehrere Zeilen erstrecken kann, lässt sich an diesem Beispiel ebenfalls erkennen. Bei der Makrodefinition muss an jedem Zeilenende ein Backslash geschrieben werden.
Ein interessantes Makro wäre die Nachbildung der Funktion fflush(), mit der es auf Linux aber Probleme gibt. Hier der Quellcode:
#include <stdio.h>
#define Fflush(int_keyboard_buffer)\
while(( (int_keyboard_buffer) = getchar()) != '\n')
int main()
{
char a,b;
printf("Buchstabe eingeben: ");
scanf("%c",&a);
Fflush(a);
printf("Buchstabe eingeben: ");
scanf("%c",&b);
Fflush(b);
printf("Die Eingabe lautet %c %c\n",a,b);
return 0;
}
Lange Makros, auf die häufig zugegriffen wird, können allerdings den Code unnötig aufblähen. In solch einem Fall sind Funktionen besser geeignet. Hierzu ein Negativ-Beispiel:
#include <stdio.h>
#define VIEL_TEXT "TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText\n"
int main()
{
printf(VIEL_TEXT);
printf(VIEL_TEXT);
printf(VIEL_TEXT);
return 0;
}
Dieses Programm würde nach dem Präprozessorlauf und vor dem Compilerlauf folgendermaßen aussehen:
#include <stdio.h>
int main()
{
printf("TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText\n");
printf("TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText\n");
printf("TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText\n");
return 0;
}
Jetzt dasselbe Beispiel mit einer Funktion, welche in diesem Fall die effizientere Methode darstellt:
#include <stdio.h>
void viel_text()
{
printf("TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText"\
"TextTextTextTextTextTextTextTextTextText\n");
}
int main()
{
viel_text();
viel_text();
viel_text();
return 0;
}
Die define-Direktive ist im Übrigen eine rein für die Programmiersprache C gedachte Direktive. Ein reiner C++-Compiler wird define deshalb nicht erkennen und kompilieren. Die meisten Compiler kennen aber sowohl C als auch C++.
In der Regel sollten hier also keine Probleme beim Kompilieren auftreten. Dies nur ergänzend zum Thema, falls Sie die Grundlagen in C kennen lernen wollen, um anschließend mit C++ fortzufahren. Unter C++ und dem neuen ANSI C99-Standard können kleinere Funktionsmakros außerdem durch inline-Funktionen ersetzt werden.
|
Hinweis |
|
Der Geltungsbereich von symbolischen Konstanten bzw. Makros reicht vom Punkt der Deklaration mit #define bis zur Aufhebung mit #undef. Die Aufhebung mittels #undef ist aber optional. Wird #undef nicht verwendet, reicht der Geltungsbereich bis zum Dateiende. |
|
Zu diesem Unterkapitel muss erwähnt werden, dass viele der beschriebenen Vorgehensweisen nicht dem ANSI C-Standard entsprechen. Da aber Programmierer oft ihre Programme auch gern auf andere Systeme portieren wollen, soll hier dennoch näher auf die Thematik eingegangen werden. Hierzu die Syntax zur bedingten Übersetzung:
#ifdef symbol #ifdef ( symbol ) #elif symbol #elif ( symbol ) #else #endif
Diese Direktiven werden eingesetzt, um zu überprüfen, ob ein Symbol zuvor schon mit #define definiert wurde. Ist symbol definiert, liefern diese Direktiven 1 zurück, ansonsten 0. Abgeschlossen wird eine bedingte Übersetzung mit der Direktive #endif.
Sie haben im vorangegangenen Kapitel schon einen kurzen Einblick (bei der selbst geschriebenen Headerdatei) in die bedingte Kompilierung erhalten. Hierzu ein einfaches Beispiel:
#include <stdio.h>
#ifdef __unix__
#define clrscr() printf("\x1B[2J")
#elif __BORLANDC__ && __MSDOS__
#include <conio.h>
#elif __WIN32__ || _MSC_VER
#include <stdlib.h>
#define clrscr() system("cls")
#else
#define clrscr() printf("clrscr() - Fehler!!\n")
#endif
int main()
{
/*Universale Routine zum Löschen des Bildschirms*/
clrscr();
return 0;
}
Hier wird vor der Übersetzung festgelegt, welche Routine zum Löschen des Bildschirms benutzt werden soll. Mit
#ifdef __unix__
überprüft der Präprozessor, ob das Programm auf einem UNIX-artigen System läuft. Wenn das der Fall ist, dann wird mit
#define clrscr() printf("\x1B[2J")
die Routine zum Löschen des Bildschirms definiert, da diese eben nur unter UNIX/Linux funktioniert. Falls es sich nicht um ein UNIX-System handelt, wird mit
#elif __BORLANDC__ && __MSDOS__
überprüft, ob das Programm mit einem Borland-Compiler und unter MS-DOS übersetzt wird. Ist das der Fall, dann wird das Löschen des Bildschirms durch eine in der Headerdatei #include <conio.h> definierte Funktion mit demselben Namen vorgenommen. Anschließend wird überprüft, ob das Programm in einem Win32-Fenster läuft oder mit dem Visual C++-Compiler übersetzt wird.
#elif __WIN32__ || _MSC_VER
#define clrscr() system("cls")
Trifft keiner der geprüften Fälle zu, wird eine entsprechende Ausgabe erzeugt:
#else
#define clrscr() printf("clrscr()-Fehler!!\n")
Abgeschlossen wird diese bedingte Kompilierung mit
#endif
Durch die bedingte Kompilierung besteht die Möglichkeit, Programme einfacher auf andere Systeme zu portieren. Die bedingte Kompilierung lässt sich auch anders verwenden:
#include <stdio.h>
#ifdef __MSDOS__
int main()
{
printf("Programm läuft unter MSDOS \n");
return 0;
}
#elif __WIN32__ || _MSC_VER
int main()
{
printf("Programm läuft unter Win32\n");
return 0;
}
#elif __unix__
int main()
{
printf("Programm läuft unter UNIX/LINUX\n");
return 0;
}
#else
int main()
{
printf("Unbekanntes Betriebssystem!!\n");
return 0;
}
#endif
Hier wurden mehrere main()-Funktionen verwendet. Auf dem System, für das die bedingte Kompilierung gilt, wird die entsprechende main-Funktion auch ausgeführt.

Abbildung 13.2: Bedingte Kompilierung - Programm läuft unter Win32

Abbildung 13.3: Bedingte Kompilierung - Programm läuft unter Linux/UNIX
Sie können die bedingte Kompilierung mit if- else if -else-Abfragen vergleichen. Um Compiler-spezifische Abfragen zu tätigen, gibt es folgende Compiler-Konstanten:
| Konstante | Compiler |
| _MSC_VER | Microsoft C ab Version 6.0 |
| _QC | Microsoft Quick C ab Version 2.51 |
| __TURBOC__ | Borland Turbo C, Turbo C++ und BC++ |
| __BORLANDC__ | Borland C++ |
| __ZTC__ | Zortech C und C++ |
| __SC__ | Symantec C++ |
| __WATCOMC__ | WATCOM C |
| __GNUC__ | Gnu C |
| __EMX__ | Emx Gnu C |
Es wird nochmals darauf hingewiesen, dass diese Konstanten nicht vom ANSI C-Gremium vorgeschrieben sind.
Für die bedingte Kompilierung mit Betriebssystemen finden sich folgende Konstanten:
| Konstante | Betriebssystem |
| __unix__ oder __unix | UNIX-System |
| __MS_DOS__ | MS-DOS |
| __WIN32__ | Windows ab 95 |
| __OS2__ | OS2 |
| _Windows | Zielsystem Windows |
| __NT__ | Windows NT |
| __linux__ | Linux |
| _SGI_SOURCE | SGI-IRIX mit Extension *.sgi |
| _MIPS_ISA | SGI-IRIX |
| __hpux | HP-UX |
Es gibt sicherlich noch weitere Konstanten. Die hier Genannten zählen zu den gängigsten. Sehen Sie sich ein anderes Programmbeispiel dazu an:
#include <stdio.h>
#ifdef __unix__
#define SEKTORSIZE 1024
#elif __MSDOS__ || __WIN32__ || _MSC_VER
#define SEKTORSIZE 512
#else
#define SEKTORSIZE 0
#endif
void sect(long size)
{
long kb,s=SEKTORSIZE;
if(s == 0)
printf("Unbekanntes System\n");
else if(s==1024)
printf("Unixsystem : ");
else
printf("DOS/Win32 : ");
kb=size*s;
printf(" %ld Sektoren = %ldKB\n",size,kb);
}
int main()
{
long sector;
printf("Wie viele Sektoren: ");
scanf("%ld",§or);
sect(sector);
return 0;
}
Dies ist ein Beispiel zum Thema Portabilität. Auf MS-DOS/Win32 beträgt die Größe eines Sektors auf der Festplatte 512 KB. Auf UNIX-Systemen hingegen meist 1024 KB pro Sektor. Sie müssen nur am Anfang des Programms dem Präprozessor die Anweisung geben, für welches System er die Größe einer bestimmten Anzahl von Sektoren ausgeben soll.
Folgende Schreibweisen sind im Übrigen identisch:
#ifdef MAKRO /* ist identisch mit */ #if defined MAKRO
Des Weiteren gibt es eine Direktive, die es ermöglicht, zu überprüfen, ob etwas nicht definiert wurde:
#ifndef __STDIO_H #define __STDIO_H #endif
Hier überprüft der Präprozessor, ob er die Headerdatei <stdio.h> noch nicht eingebunden hat.
Das ist zum Beispiel erforderlich, wenn mehrere Headerdateien und Module benutzt werden, die <stdio.h> benötigen. Somit würden alle Makros in der Headerdatei <stdio.h> mehrmals definiert werden, was im schlimmsten Fall sogar einen Fehler auslösen kann. Mit der eben geschriebenen Struktur wird dieses vermieden.
Auch zu dieser Direktive gibt es eine alternative Schreibweise:
#ifndef MAKRO /* ist dasselbe wie */ #if !defined MAKRO
Folgende vordefinierten Makros werden von ANSI C vorgeschrieben:
| Makroname | Bedeutung |
| __LINE__ | Zeilennummer der aktuellen Zeile in der Programmdatei |
| __FILE__ | Name der Programmdatei |
| __DATE__ | Übersetzungsdatum der Programmdatei |
| __TIME__ | Übersetzungszeit der Programmdatei |
| __STDC__ | Erkennungsmerkmal eines ANSI C-Compilers. Ist die ganzzahlige Konstante auf den Wert 1 gesetzt, handelt es sich um einen ANSI C-konformen Compiler. |
| __cplusplus | C++-Code |
Hier folgt ein Beispiel zur Verwendung dieser vordefinierten Makros:
#include <stdio.h>
#if defined __STDC__
#define isstd_c() printf("ANSI C Compiler\n")
#else
#define isstd_c() printf("Kein ANSI C Compiler\n")
#endif
int main()
{
printf("Zeile %d in Datei %s\n",__LINE__,__FILE__);
printf("Übersetzt am %s um %s\n",__DATE__,__TIME__);
#line 999 "asdf.c"
printf("Zeile %d in Datei %s\n",__LINE__,__FILE__);
isstd_c(); /* Ist es ein ANSI C Compiler ? */
return 0;
}
Im Programm wurde gleich eine weitere neue Präprozessor-Direktive eingesetzt:
#line Zeilennummer dateinamen.c
Damit wird bewirkt, dass die nächste Zeile der Datei dateiname.c mit der Zeile Zeilennummer (also 999) beginnt. Diese Präprozessor-Direktive beeinflusst das Programm selbst nicht, sondern nur die Nummerierung der einzelnen Zeilen. Sie wird verwendet, um einem Programm Zeilennummern für Querverweise oder Fehlermeldungen zu übergeben. Diese Direktive wird vorwiegend von Programmen wie lex oder yacc verwendet, welche C-Quelltexte erzeugen.
Ist in einem Ersetzungstext vor dem Parameter das Zeichen # gesetzt, werden beim Aufruf des Makros das # und der Parameter durch den entsprechenden String ersetzt:
#define ausgabe(variable) printf(#variable"=%d\n",variable)
Hier wird der Variable zahl der Wert 10 (int Zahl=10;) übergeben. Steht im Programm beispielsweise
ausgabe(Zahl);
dann wandelt dies der Präprozessor zunächst um in
printf("Zahl"" = %d\n",Zahl);
Tatsächlich verhält sich der Ausdruck so, als würde Folgendes in der Zeile stehen:
printf("Zahl = %d\n");
Hierzu ein kleines Programmbeispiel, in dem diese Technik verwendet wird:
#include <stdio.h>
/* Bei Linux muss für math.h der Compilerflag -lm
mit angegeben werden:
gcc -o strkont strkont.c -lm
*/
#include <math.h>
#define wurzel(zahl)\
printf(#zahl" von %f = %f\n",zahl,sqrt(zahl))
#define summe(zahl1,zahl2)\
printf(#zahl1 "+" #zahl2 " = %d\n",zahl1+zahl2)
#define gibaus(string)\
printf(#string"\n")
#define wertvon(zahl,format)\
printf(#zahl" = "format"\n",zahl)
int main()
{
float Wurzel;
int Wert1=100;
int Wert2=150;
char character='s';
int integer=20;
float floating=5.550f;
printf("Zahl eingeben : ");
scanf("%f",&Wurzel);
wurzel(Wurzel);
summe(Wert1,Wert2);
gibaus(Hallo Welt);
wertvon(character,"%c");
wertvon(integer,"%d");
wertvon(floating,"%f");
return 0;
}

Abbildung 13.4: Ersetzung eines Makroparameters durch einen String
Um ein Makro bzw. eine symbolische Konstante, welche mit #define definiert wurde, wieder aufzuheben, muss nur im Programm
#undef makroname
geschrieben werden. Eine Makrodefinition gilt von deren Festlegung (#define) bis zum Programmende oder bis zur nächsten #undef-Direktive. Ein Beispiel:
#include <stdio.h>
#define NUM 10
int main()
{
printf("Wert für symb. Konstante NUM: %d\n",NUM); /* 10 */
#ifdef NUM
#undef NUM /* symb. Konstante aufheben */
#endif
#define NUM 100 /* symb. Konstante wieder neu definieren */
printf("Wert für symb. Konstante NUM: %d\n",NUM); /* 100 */
return 0;
}
Hätten Sie die symbolische Konstante NUM vor der Neudefinition nicht mit undef aufgehoben, hätte dies eine Warnung des Compilers zur Folge.
Mit
#error "Fehlermeldung"
wird eine Stringkonstante Fehlermeldung in der entsprechenden Zeile ausgegeben. Zum Beispiel:
#include <stdio.h>
#define TEST
int main()
{
printf("Programmstart!\n");
#ifdef TEST
#error "Das Programm ist noch nicht fertig gestellt!!\n"
#endif
printf("Programmende");
return 0;
}
Wenn Sie versuchen, das Programm zu kompilieren, wird folgende Fehlermeldung des Compilers ausgegeben:
Das Programm ist noch nicht fertig gestellt!!
Das Programm lässt sich somit auch nicht kompilieren. Damit kann z.B. vermieden werden, dass ein noch nicht ganz fertig gestellter bzw. nicht fehlerfreier Codeabschnitt verwendet wird. Das kann bei einem Projekt, an dem mehrere Leute arbeiten, praktisch sein. Aber es lassen sich damit auch Fehlermeldungen ausgeben, die im Zusammenhang mit der Expansion von Makros festgestellt wurden. Zum Beispiel:
#include <stdio.h>
#define MAX 101
int main(void)
{
printf("Programmstart\n");
#if (MAX % 2) == 1
#error "symb. Konstante MAX muss eine gerade Zahl sein!\n"
#endif
printf("Programmende\n");
return 0;
}
Dieses Programm lässt sich erst übersetzen, wenn Sie der symbolischen Konstante MAX einen geraden Wert übergeben.
#pragma sind Compiler-spezifische Direktiven und von Compiler zu Compiler verschieden. Wenn ein Compiler eine bestimmte #pragma-Direktive nicht kennt, wird diese ignoriert. Mithilfe dieser Pragmas können Compiler-Optionen definiert werden, ohne mit anderen Compilern in Konflikt zu geraten. Da das Verhalten von #pragma-Anweisungen stark systemabhängig ist, soll darauf hier nicht näher eingegangen werden.