Eine Konstante ist ein Wert mit einem Namen und dem Versprechen, dass er sich nicht ändert. C gibt dir drei Mechanismen dafür, und unter der Haube sind sie wirklich verschiedene Dinge - einer ist eine Variable, einer ist Textersetzung und einer ist ein Ganzzahltyp.
Warum einem Wert überhaupt einen Namen geben
Vergleiche diese beiden Zeilen:
double area = 3.14159 * r * r;
double area = PI * r * r;
Die zweite ist aus drei Gründen besser, die sich alle später zeigen. Sie sagt, was die Zahl bedeutet. Sie steht an genau einer Stelle, das Ändern der Genauigkeit ist also eine Bearbeitung statt einer Suche. Und ein Tippfehler in PI ist ein Übersetzungsfehler, während ein Tippfehler in 3.14195 eine falsche Antwort ist.
Unbenannte Zahlen, die über den Code verstreut sind, heißen magische Zahlen, und sie zu entfernen ist eine der billigsten Verbesserungen, die du an einem C-Programm vornehmen kannst.
const
Das Schlüsselwort const markiert eine Variable als nur lesbar:
Kommentiere die Zuweisung ein, und der Compiler verweigert sich: assignment of read-only variable 'PI'. Dieser Fehler zur Bauzeit ist der ganze Sinn.
Eine const-Variable ist eine echte Variable. Sie hat einen Typ, der Compiler prüft also, wie du sie benutzt. Sie gehorcht dem Gültigkeitsbereich, ein const innerhalb einer Funktion dringt also nicht nach außen. Sie hat eine Adresse, du kannst also einen Zeiger darauf nehmen. Und ein Debugger kann dir ihren Namen und Wert zeigen.
const muss an der Stelle der Deklaration initialisiert werden - eine zweite Gelegenheit gibt es nicht:
const int limit; /* Fehler (oder für immer Müll): später kann nichts zuweisen */
const int limit = 50; /* korrekt */
#define
Die Alternative des Präprozessors ist ein Makro:
Das ist keine Variable. Bevor der Compiler läuft, ersetzt der Präprozessor jedes Vorkommen von PI im Quelltext durch die Zeichen 3.14159265358979. Der Compiler sieht den Namen überhaupt nie - genau deshalb hat ein #define keinen Typ und kann in keinem Debugger auftauchen.
Beachte: kein Semikolon und kein =. Ein Makro ist roher Text, dieser häufige Fehler:
#define MAX_USERS 100; /* beachte das Semikolon */
int limit = MAX_USERS + 1; /* expandiert zu: int limit = 100; + 1; -> Fehler */
erzeugt also einen Fehler in einer Zeile, die der Definition überhaupt nicht ähnelt. Die Konvention, Makros groß zu schreiben, existiert genau deshalb, damit Leser wissen, dass hier eine Ersetzung passiert.
Makros werden konventionell in GROSSBUCHSTABEN geschrieben; const-Variablen folgen dem meist ebenfalls, wenn sie für dieselbe Idee stehen.
Enum-Konstanten
Die dritte Form gibt dir einen Satz benannter Ganzzahlen:
Die Werte beginnen bei 0 und steigen um eins, sofern du sie nicht zuweist, MON ist also 0 und FRI ist 4. Ein anonymes enum { ... } ist ein verbreitetes Idiom für schlichte Ganzzahlkonstanten.
Enum-Konstanten haben gegenüber beiden Alternativen einen echten Vorteil: Sie sind Ganzzahlkonstanten zur Übersetzungszeit, die der Compiler versteht, und sie sind für den Debugger sichtbar. Die ganze Geschichte steht unter Enums in C.
Welche man nimmt
| Bedarf | Nimm |
|---|---|
| Eine typisierte, gültigkeitsbereichstreue, debugbare Konstante | const |
Eine Arraygröße, ein case-Label, eine Bitfeldbreite | enum oder #define |
| Eine Zeichenkettenkonstante | #define oder const char * |
| Eine Gleitkommakonstante | const double |
| Ein Satz zusammengehöriger Ganzzahlwerte | enum |
Alles, was der Präprozessor sehen muss (#if, Include-Schutz) | #define |
Die Voreinstellung sollte const sein. Greife zu #define, wenn der Wert vor der Übersetzung existieren muss, und zu enum, wenn du eine Ganzzahl zur Übersetzungszeit oder eine benannte Gruppe brauchst.
Die eine Stelle, an der das wirklich beißt, sind Arraygrößen:
Ein const int ist in C eine nur lesbare Variable, keine konstante Ausdrucksform zur Übersetzungszeit. Innerhalb einer Funktion akzeptiert C99 int local[const_size], indem es daraus ein Array variabler Länge macht; auf Dateiebene, in einer struct oder als case-Label wird es rundheraus abgelehnt. enum und #define funktionieren überall.
(Das ist einer der echten Unterschiede zu C++, wo ein const int sehr wohl eine konstante Ausdrucksform ist. Code, der zwischen den Sprachen wandert, stolpert darüber.)
const mit Zeigern
const und Zeiger lassen sich auf drei Arten kombinieren, und die Bedeutungen sind nicht austauschbar. Lies die Deklaration vom Namen nach außen:
const char *p; /* p zeigt auf chars, die nicht verändert werden dürfen */
char * const q; /* q selbst kann nicht umgehängt werden; die chars dürfen sich ändern */
const char * const r; /* weder der Zeiger noch die chars dürfen sich ändern */
Die Form const char * ist die, die du am häufigsten schreiben wirst, denn sie ist der richtige Typ für einen Funktionsparameter, der eine Zeichenkette liest, ohne sie zu ändern:
Einem nur lesenden Parameter ein const hinzuzufügen kostet nichts und dokumentiert den Vertrag auf eine Art, die der Compiler durchsetzt. Es lohnt sich überall.
Zeichenkettenliterale sind faktisch const
Eine subtile Falle, die const abfangen soll:
char *s = "hallo"; /* gültiges C, aber s zeigt in nur lesbaren Speicher */
s[0] = 'H'; /* undefiniertes Verhalten - meist ein Absturz */
Zeichenkettenliterale liegen in einem nur lesbaren Abschnitt der ausführbaren Datei. Über diesen Zeiger zu schreiben führt typischerweise zu einem Segfault. Den Zeiger als const char *s = "hallo"; zu deklarieren lässt den Compiler die Zuweisung zurückweisen, statt sie zur Laufzeit abstürzen zu lassen.
Wenn du eine veränderbare Zeichenkette brauchst, kopiere sie in ein Array:
Häufig gestellte Fragen
Wie deklariert man eine Konstante in C?
Füge einer Deklaration const hinzu und gib ihr einen Wert: const double PI = 3.14159;. Der Compiler weist dann jeden Zuweisungsversuch zurück. Die ältere Alternative ist ein Präprozessor-Makro, #define PI 3.14159, das stattdessen Text ersetzt.
Was ist der Unterschied zwischen const und #define in C?
const erzeugt eine echte typisierte Variable, die der Compiler kennt, sie wird also typgeprüft, taucht im Debugger auf und gehorcht dem Gültigkeitsbereich. #define ist reines Textersetzen vor der Übersetzung - ohne Typ, ohne Gültigkeitsbereich und im Debugger nicht einsehbar. Bevorzuge const, sofern du den Wert nicht in einem Kontext brauchst, der eine Konstante zur Übersetzungszeit verlangt.
Kann eine const-Variable in C als Arraygröße dienen?
Nicht für ein Array fester Größe auf Dateiebene. const int N = 10; int arr[N]; ist in C99 ein Array variabler Länge (erlaubt innerhalb einer Funktion, nicht auf Dateiebene und nicht in einer Struktur). Für eine echte Größe zur Übersetzungszeit nimm #define N 10 oder ein enum { N = 10 };.
Was bedeutet const char *p?
Es ist ein Zeiger auf ein konstantes char: Du darfst ändern, wohin p zeigt, aber nicht das Zeichen, auf das es zeigt. char * const p ist das Gegenteil - ein konstanter Zeiger auf ein veränderbares char. const char * const p sperrt beides. Lies die Deklaration vom Namen nach außen.