📙 Kapitel 7 · Unit 32 von 36

Präprozessor
#define · Makros · #include · #ifndef

#define · Makros · #include · #ifndef · Bedingte Kompilierung

32 / 36 Units
Ömer
Kapitel 7: Praxis & Fortgeschritten ~100 Min. Theorie + Simulator + Quiz + Spickzettel
Ömer
Ömer sagt:

Der Präprozessor ist ein unsichtbarer Helfer – er läuft bevor der Compiler deinen Code überhaupt sieht! Er ersetzt Text, bindet Dateien ein und kann Code-Teile ein- oder ausblenden. Das ist mächtiger als es klingt!

Was ist der Präprozessor?

Bevor der eigentliche Compiler dein C-Programm übersetzt, läuft der Präprozessor darüber. Er verarbeitet alle Zeilen die mit # beginnen – sogenannte Direktiven. Der Präprozessor macht nur reine Textersetzung – er kennt keine Typen, keine Variablen, keine Logik.

Die 3 Phasen der Kompilierung

Präprozessor
#define / #include expandiert
Compiler
C → Maschinencode (.o)
Linker
Einzelne .o zu .exe zusammen
PhaseWas passiert
Präprozessor#define und #include werden expandiert, Kommentare entfernt
CompilerC-Quellcode wird in Maschinencode übersetzt (.o-Datei)
LinkerEinzelne Objektdateien werden zur ausführbaren .exe zusammengefügt

#define – Konstanten

Mit #define kannst du benannte Konstanten definieren. Jedes Vorkommen des Namens im Code wird vor dem Compilieren durch den Wert ersetzt. Konvention: Namen immer in GROSSBUCHSTABEN.

konstanten.cC
#define PI          3.14159
#define MAX_GROESSE 100
#define BEGRUESSUNG "Hallo HTL!"

#include <stdio.h>

int main() {
    float radius = 5.0;
    float flaeche = PI * radius * radius;
    // Präprozessor macht daraus:
    // float flaeche = 3.14159 * radius * radius;

    printf("Flaeche: %.2f\n", flaeche);
    printf("Max: %d\n", MAX_GROESSE);
    return 0;
}
▶ Ausgabe
Flaeche: 78.54 Max: 100

✅ #define – Vorteile

  • Kein Speicher verbraucht
  • Kein Typ (universell einsetzbar)
  • Reine Textersetzung – sehr schnell
  • Kann nicht versehentlich verändert werden

const – sichere Alternative

  • const float PI = 3.14159f;
  • Hat einen Typ – der Compiler prüft!
  • Nimmt Speicher (wie eine Variable)
  • Besser für komplexe Typen

#define – Makros mit Parametern

Makros können auch Parameter haben – sie sehen aus wie Funktionen, sind aber reine Textersetzung. Immer extra Klammern um jeden Parameter und um den gesamten Ausdruck!

makros.cC
#define QUADRAT(x)    ((x)*(x))
#define MAX(a,b)      ((a)>(b) ? (a) : (b))
#define MIN(a,b)      ((a)<(b) ? (a) : (b))

int main() {
    printf("%d\n", QUADRAT(5));      // → 25
    printf("%d\n", QUADRAT(3+2));    // → 25 (korrekt!)
    printf("%d\n", MAX(7, 3));        // → 7
    printf("%d\n", MIN(7, 3));        // → 3
    return 0;
}

⚠️ Die Klammer-Falle

Ohne Klammern um den Parameter passiert etwas Unerwartetes:

#define QUADRAT_FALSCH(x) x*x

QUADRAT_FALSCH(3+2)
// wird zu: 3+2*3+2 = 11 ← FALSCH!

#define QUADRAT_RICHTIG(x) ((x)*(x))

QUADRAT_RICHTIG(3+2)
// wird zu: ((3+2)*(3+2)) = 25 ← Richtig!

#include – Bibliotheken und eigene Header

Mit #include fügt der Präprozessor den Inhalt einer anderen Datei wörtlich ein – als ob du den Code direkt dort geschrieben hättest.

includes.cC
#include <stdio.h>    // Systemheader: <spitze Klammern>
#include <stdlib.h>   // Sucht in Systempfaden (z.B. /usr/include)
#include <math.h>     // Mathematische Funktionen

#include "mein_header.h"  // Eigene Datei: "Anführungszeichen"
#include "rechner.h"      // Sucht zuerst im aktuellen Verzeichnis

<spitze Klammern>

  • Für Systembibliotheken (stdio, stdlib, math...)
  • Sucht in System-Include-Pfaden
  • Beispiel: #include <stdio.h>

"Anführungszeichen"

  • Für eigene Header-Dateien
  • Sucht zuerst im aktuellen Verzeichnis
  • Beispiel: #include "rechner.h"

#ifndef – Include Guards

Problem: Wenn zwei Dateien denselben Header einbinden, wird er doppelt eingefügt – das führt zu Fehlern! Die Lösung sind Include Guards:

mein_header.hC
#ifndef MEIN_HEADER_H     // Falls MEIN_HEADER_H NICHT definiert ist...
#define MEIN_HEADER_H     // ...dann definiere es jetzt

/* Alle Deklarationen hier */
int addiere(int a, int b);
float kreis_flaeche(float r);

#endif                    // Ende des Guards

Moderne Alternative: #pragma once

#pragma once am Anfang der Header-Datei macht dasselbe wie Include Guards – kürzer und moderner. Wird von allen gängigen Compilern unterstützt, ist aber technisch kein C-Standard.

#pragma once

/* Alle Deklarationen hier – kein #endif nötig! */

Bedingte Kompilierung

Mit #ifdef / #ifndef / #endif kann man Code-Teile je nach Konfiguration ein- oder ausblenden – z.B. für Debug-Ausgaben oder plattformspezifischen Code.

debug.cC
#include <stdio.h>
#define DEBUG   // Kommentiere das aus um Debug zu deaktivieren!

int main() {
    int x = 42;

#ifdef DEBUG
    printf("[DEBUG] x = %d\n", x);
#endif

    printf("Ergebnis: %d\n", x * 2);
    return 0;
}
▶ Ausgabe (mit DEBUG)
[DEBUG] x = 42 Ergebnis: 84
plattform.cC
#ifdef _WIN32
    printf("Laueft auf Windows!\n");
#else
    printf("Laueft auf Linux/Mac!\n");
#endif

Mit dem Compiler-Flag gcc -DDEBUG programm.c kann man DEBUG auch von der Kommandozeile aus definieren – ohne den Quellcode zu ändern!

Häufige Fehler

Fehler 1: Semikolon in #define

#define PI 3.14;
// x = PI; wird zu: x = 3.14;; ← doppeltes Semikolon!

Fehler 2: Fehlende Klammern in Makros

#define DOPPELT(x) x*2
DOPPELT(3+4) // wird zu: 3+4*2 = 11 statt 14!

Fehler 3: Makros mit Seiteneffekten

MAX(i++, j++)
// wird zu: ((i++)>(j++) ? (i++) : (j++))
// i++ oder j++ wird ZWEIMAL ausgewertet!

Merksatz: #define ist blindes Text-Copy-Paste

Der Präprozessor sieht keine Typen, prüft keine Syntax und versteht keine Logik. Er kopiert und fügt Text ein. Alles Weitere ist Compiler-Aufgabe.

⚡ Code-Simulator

Probiere den Präprozessor aus! Ändere die #define-Werte und beobachte wie sich die Ausgabe ändert:

C Simulator – Unit 32 – Präprozessor
▶ Ausgabe
– Klicke auf AUSFÜHREN –
Ömer
Ömer sagt:

Ändere den Wert von PI auf 3 und schau was passiert. Dann probier QUADRAT(3+2) mit und ohne Klammern – der Unterschied ist dramatisch!

🎯 Wissens-Quiz

Frage 1
Mit welchem Zeichen beginnen Präprozessor-Direktiven?
A@
B#
C$
D%
Frage 2
Was macht #define MAX 100?
ADeklariert eine int-Variable MAX mit Wert 100
BErsetzt alle Vorkommen von MAX im Code durch 100
CDefiniert eine Konstante mit Typ int
DReserviert Speicher für den Wert 100
Frage 3
Warum brauchen Makro-Parameter extra Klammern? (z.B. #define QUADRAT(x) ((x)*(x)))
AWegen Speicherverwaltung
BNur für den Stil, kein technischer Grund
CWegen Operatorpriorität – QUADRAT(3+2) ohne Klammern ergibt 11 statt 25
DDamit der Compiler den Typ erkennt
Frage 4
Was ist der Unterschied zwischen #include <stdio.h> und #include "mein.h"?
AKein Unterschied, beide Schreibweisen sind identisch
BSpitze Klammern für C++, Anführungszeichen für C
CSpitze Klammern = Systemheader, Anführungszeichen = eigene Datei im Projektverzeichnis
DAnführungszeichen sind veraltet
Frage 5
Was sind Include Guards und wozu dienen sie?
ASchützen den Code vor unbefugtem Zugriff
BVerhindern das mehrfache Einbinden derselben Header-Datei
CÜberprüfen zur Laufzeit ob eine Datei existiert
DSind ein Sicherheitsmechanismus gegen Pufferüberlauf
Frage 6
Was ist #pragma once?
AEine Compiler-Warnung
BEin Fehler wenn eine Datei mehr als einmal vorhanden ist
CModerne Alternative zu Include Guards – verhindert Mehrfach-Include
DBedeutet das Programm soll nur einmal ausgeführt werden
Frage 7
Welche Konvention gilt für #define-Namen?
AcamelCase wie bei Variablen
BMit Unterstrich beginnen wie _define
CGROSSBUCHSTABEN – oft mit Unterstrichen (z.B. MAX_GROESSE)
DKleinbuchstaben wie bei Funktionen
Frage 8
Was gibt #define DOPPELT(x) x*2 bei DOPPELT(3+4) aus?
A14 (korrekt: (3+4)*2)
B11 (falsch: 3+4*2 wegen fehlender Klammern)
CCompilerfehler
D7 (nur x wird eingesetzt)

📋 Spickzettel – Präprozessor

#define – Konstante
#define NAME wertGrundform
#define PI 3.14159Beispiel
GROSSBUCHSTABENKonvention
kein Semikolon!Häufiger Fehler
#define – Makro
#define M(x) ((x)*(x))mit Parameter
MAX(a,b) ((a)>(b)?(a):(b))Maximum
MIN(a,b) ((a)<(b)?(a):(b))Minimum
Klammern!Immer um Parameter
#include
#include <stdio.h>Systemheader
#include "eigen.h"Eigene Header
Fügt Datei einWörtlich
Include Guard
#ifndef HEADER_HBeginn Guard
#define HEADER_HMarker setzen
/* Inhalt */Deklarationen
#endifEnde Guard
Bedingte Kompilierung
#ifdef DEBUGWenn DEBUG def.
#ifndef RELEASEWenn NICHT def.
#elseSonst
#endifAbschluss
Nützliches
#pragma onceModerner Guard
gcc -DDEBUGVon außen definieren
_WIN32Windows-Flag
gcc -E datei.cNur Präprozessor

✅ Checkliste Unit 32

  • Ich weiß was der Präprozessor macht und wann er läuft
  • Ich kann #define-Konstanten mit GROSSBUCHSTABEN definieren
  • Ich schreibe Makros mit korrekten Klammern um jeden Parameter
  • Ich kenne den Unterschied zwischen <> und "" bei #include
  • Ich kann einen Include Guard korrekt schreiben
  • Ich nutze #ifdef für bedingte Kompilierung (Debug-Modus)