C-Tutorial (C oder C++, Vorwort, Installation, Kapitel 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)

Symbolische Konstanten und Makros

5.1. Symbolische Konstanten und Makros

Hin und wieder ist es notwendig, Konstanten zu definieren. Etwa dann, wenn es nachträglich vorkommt, dass sich ansich fixe Werte ändern. Ein typischer Fall dafür sind Konfigurationseinstellungen. An zentraler Stelle werden Konstanten mit den nötigen Einstellungen deklariert (und definiert). Wird der Wert der Konstante später im Quellcode geändert, hat das Auswirkungen auf das Programm. Überall dort, wo die Konstante eingesetzt wurde, wird - korrekt - der geänderte Wert verwendet.

Es macht also Sinn, Werte, die sich ändern können, zentral festzulegen statt "hart" zu kodieren. Bei letzterem würden die Werte (Zahlen, Zeichenketten) fix eingesetzt werden. Bei Änderungen hieße es dann Suchen und Ersetzen. Und zwar im ganzen Programm. Das kann bei größeren Projekten mit vielen Dateien sehr umständlich werden. Konstanten machen also Änderungen leichter: Änderungsfreundlichkeit.

Daneben gibt es noch einen zweiten Grund: Leichtere Lesbarkeit. In dem Fall beschreibt die Konstante näher, wozu der Wert gut ist. Ein einfaches Beispiel dazu werden Sie weiter unten sehen.

So weit, so gut. Wie Sie Konstanten mit dem Schlüsselwort const deklarieren (und definieren), wissen Sie bereits. "Normale" Konstanten sind allerdings auf die Datentypen beschränkt, von denen Sie auch Variablen deklarieren können. Danaben belegen Sie Speicher. Wie Variablen.

Hier kommen Symbolische Konstanten ins Spiel. Diese werden nicht mit const, sondern mit einer sog. Präprozessor-Anweisung (Präprozessor-Direktive) festgelegt. Den Präprozessor kennen Sie bereits von #include, wo er anstelle von #include eine Datei einfügt. Um eine symbolische Konstante festzulegen, brauchen Sie #define:

#define Konstante Ersatztext

Überall, wo nun der Name der Konstante im Quellcode vorkommt (außer in Strings!), wird der Ersatztext eingesetzt. Im Grunde ein simples Suchen und Ersetzen, das Sie aber nicht selbst erledigen müssen. Das macht der Präprozessor für Sie!

Beachten Sie, dass nach der Anweisung kein Semikolon folgt! Es ist ja eine Präprozessor-Direktive, und die geht den Compiler nichts an (der Präprozessor läuft vorher!). Ein Beispiel zum besseren Verständnis:

#define PI 3.141592654

Nun durchsucht der Präprozessor die Quellcode-Datei und ersetzt überall, wo PI vorkommt, PI durch den Ersatztext (3.141592654). Davon bekommen Sie nichts mit. Für den Compiler sieht der Quellcode danach so aus, als hätten Sie überall selbst 3.141592654 eingetragen. Strings (Zeichenketten) sind, wie gesagt, eine Ausnahme. In folgender Anweisung wird nichts ersetzt:

printf ("PI steht für Programmierer-Intelligenz .. oder doch nicht?"); 
   /* hier wird nichts ersetzt! */

Da #define aber wahllos ist, was es gegen was ersetzen soll, bieten sich noch weitere Anwendungsmöglichkeiten. Mit der #define-Direktive lassen sich sog. Makros definieren. Ein Makro ist einer symbolischen Konstante sehr ähnlich. Die Definition sieht auch genauso aus. Ein Beispiel:

#define quadrat(a) (a)*(a)

Wo immer quadrat(a) im Quellcode vorkommt, wird es durch (a)*(a) ersetzt. Achten Sie auf die Verwendung des Buchstabens a im Beispiel! In Makros sind - ähnlich wie bei Funktionen - Parameter möglich. Die Klammern () müssen unmittelbar nach dem Makronamen folgen. Dann wird a als Parameter behandelt, was zur Folge hat, dass Sie im Quellcode eine beliebige Variable einsetzen können:

#include <stdio.h>
 
#define quadrat(a) (a)*(a)
 
int main()
{
  int zahl;   /* die Variable heisst zahl, nicht a! */
 
  printf ("Das Quadrat welcher Zahl ermitteln: ");
  scanf ("%d",&zahl);
 
  printf ("Ergebnis: %d\n", quadrat(zahl) );   /* wird durch (zahl)*(zahl) ersetzt! */
 
  return 0;
}

Der Präprozessor macht dann aus der printf()-Zeile:

printf ("Ergebnis: %d\n", (zahl)*(zahl) );

Beachten Sie bitte, dass Bezeichner und Ersatztext nur durch EIN Leerzeichen getrennt werden! Machen Sie zwischen quadrat und (a) schon ein Leerzeichen, dann würde Folgendes angenommen werden:

quadrat = Bezeichner (Text, der ersetzt werden soll)
(a) (a)*(a) = Ersatztext

Testen Sie das am besten einmal! Sie bekommen dann eine Fehlermeldung: »a« nicht deklariert (deutschsprachige Meldung bei GCC). Sie können sich anschauen, wie der "Zwischenoutput" aussieht, den der Präprozessor generiert. Dazu muss GCC mit dem Parameter -E aufgerufen werden. Am einfachsten haben es hier Linux-User: (Gleiches funktioniert aber auch unter Windows!)

gcc -E beispiel.c     # oder wie Ihre Datei heisst ..

Dann kommt in der relevanten Zeile folgendes heraus:

printf ("Ergebnis: %d", (a) (a)*(a)(zahl) );

Das überrascht auf den ersten Blick! Sehen wir uns zum besseren Verständnis noch einmal die "falsche" Präprozessor-Direktive und die printf()-Zeile im Beispiel an. Ich habe die einzelnen Bestandteile farblich hervorgehoben.

#define quadrat (a) (a)*(a)

printf ("Ergebnis: %d\n", quadrat(zahl) );

Das ergibt:

printf ("Ergebnis: %d", (a) (a)*(a)(zahl) );

Der rote Text wird durch den blauen ersetzt. Der grüne Text betrifft den Präprozessor gar nicht! Darum bleibt er einfach stehen. Durch das falsche Leerzeichen wurde aus dem Makro eine symbolische Konstante (und das Beispiel funktioniert nicht mehr).

Ein weiterer Punkt ist wichtig in diesem Beispiel: Die Klammerung! Probieren Sie einmal folgende Direktive

#define quadrat(a) a*a

und dazu folgende printf()-Zeile:

printf ("Quadrat von 1+1 = %d\n", quadrat(1+1) );

Als ganzes Beispiel:

#include <stdio.h>
 
#define quadrat(a) a*a
 
int main()
{
  printf ("Quadrat von 1+1 = %d\n", quadrat(1+1) );
 
  return 0;
}

Der Präprozessor ersetzt die printf()-Zeile dann durch:

printf ("Quadrat von 1+1 = %d\n", 1+1*1+1 );

Und wie Sie wissen, wird in

1+1*1+1

zuerst 1*1 berechnet, und erst dann zweimal 1 addiert. Das liegt an der Priorität der Operatoren, wie in Kapitel 4.2 erklärt. Ergibt 3. Mit Klammern käme korrekt 4 als Ergebnis heraus:

printf ("Quadrat von 1+1 = %d\n", (1+1)*(1+1) );

Je nachdem, in welchem Kontext Sie das Makro verwenden, kann es auch Sinn machen, alles zu klammern. Sicher ist sicher:

#define quadrat(a) ((a)*(a))

Vorheriges Kapitel Nächstes Kapitel