Wenn in der Sprache C ein Programm übersetzt (compiliert und gelinkt) werden soll, dann wird, bevor der Compiler den Quelltext verabeitet, von einem besonderen Teil des Compilers, dem Präprozessor, ein zusätzlicher Übersetzungslauf durchgeführt. C C++ C/C++ Präprozessor Preprocessor Preprozessor define include Direktive Direktiven Makros Konstanten bedingte Compilierung Präprozessor-Direktiven - #define #include #ifdef #ifndef #elif #else #error Kapitel 13: Präprozessor-Direktiven

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            zurück  Ein Kapitel tiefer  zum Inhaltsverzeichnis

Die Direktive #include kopiert andere, benannte (Include-) Dateien in das Programm ein. Meistens handelt es sich dabei um Headerdateien mit der Extension *.h oder *.hpp. Hier die Syntax der Präprozessor-Direktive include:

#include <header >
#include "header"

Der Präprozessor entfernt die include-Zeile und ersetzt diese durch den Quelltext der include-Datei. Der Compiler erhält anschließend einen modifizierten Text zur Übersetzung.

Natürlich können Sie damit eigene Headerdateien schreiben und diese einkopie-ren lassen. Sie haben beispielsweise eine Headerdatei geschrieben und diese im Verzeichnis /HOME/MYOWNHEADERS unter dem Namen meinheader.h gespeichert. Dann müssen Sie diese Headerdatei am Anfang des Quelltextes mit

#include "/home/myownheaders/meinheader.h"

einkopieren. Dabei muss dasjenige Verzeichnis angegeben werden, in dem die Headerdatei gespeichert wurde. Steht die Headerdatei hingegen zwischen ecki-gen Klammern (wie dies bei Standardbibliotheken meistens der Fall ist), also:

#include <datei.h> 

so wird die Headerdatei datei.h im implementierungsdefinierten Pfad gesucht. Dieser Pfad befindet sich in dem Pfad, in dem sich die Headerdateien Ihres Compilers befinden.

Steht die Headerdatei zwischen zwei Hochkommata, also:

#include "datei.h" 

so wird diese im aktuellen Arbeitsverzeichnis oder in dem Verzeichnis gesucht, das mit dem Compiler-Aufruf -I angegeben wurde - vorausgesetzt, Sie übersetzen das Programm in der Kommandozeile. Sollte diese Suche erfolglos sein, so wird in denselben Pfaden gesucht als wäre #include <datei.h> angegeben.

Hier die einzelnen Schritte, durch die aus dem Quellcode eine ausführbare Datei wird (von oben nach unten):

Abbildung 13.1: Von der Quelldatei zur ausführbaren Datei

Abbildung 13.1: Von der Quelldatei zur ausführbaren Datei

Dazu eine Übersicht zu den Standard-Headerdateien, die von ANSI C vorgeschrieben sind:

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

Tabelle 13.1: Übersicht zu den Standard-Headerdateien

13.2. Makros und Konstanten - #define            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

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:
const double PI = atan(1)*4;

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.

13.3. Bedingte Compilierung            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

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.2: Bedingte Kompilierung - Programm läuft unter Win32

Abbildung 13.3: Bedingte Kompilierung - Programm läuft unter Linux/UNIX
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

Tabelle 13.2: Konstanten für bestimmte Compiler

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

Tabelle 13.3: Konstanten für bestimmte Betriebssysteme

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",&sector);
   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

13.4. Vordefinierte Präprozessor-Direktiven (ANSI C)            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

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

Tabelle 13.4: Vordefinierte Standard-Makros

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.

13.5. Ersetzung von Makroparametern durch einen String            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

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
Abbildung 13.4: Ersetzung eines Makroparameters durch einen String

13.6. #undef - Makronamen wieder aufheben            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

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.

13.7. Ausgeben von Fehlermeldungen #error            zurück  Ein Kapitel tiefer  Ein Kapitel höher  zum Inhaltsverzeichnis

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.

13.8. #pragma            zurück  Ein Kapitel höher  zum Inhaltsverzeichnis

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

Weiter mit Kapitel 14: Arrays            zum Inhaltsverzeichnis