C verbrachte seine ersten 27 Jahre ohne einen Wahrheitstyp. Das ist kein Versehen - es ist eine Designentscheidung mit Konsequenzen, denen du an deinem ersten Tag begegnest, und mit einem berühmten Bug, der direkt daraus folgt.
Null ist falsch, alles andere ist wahr
In C gibt es in den Regeln für Bedingungen keinen besonderen Wahr/Falsch-Wert. if, while, for, &&, || und ! stellen alle genau eine Frage: Ist dieser Wert null?
Negative Zahlen sind wahr. Brüche sind wahr. Ein Zeiger ungleich null ist wahr. Nur die Null - in welchem Typ auch immer - ist falsch.
Deshalb ist C-Code voller Idiome, die knapp aussehen, bis man die Regel kennt:
if (count) /* wenn count nicht null ist */
if (!count) /* wenn count null ist */
if (p) /* wenn p nicht NULL ist */
if (!strcmp(a, b)) /* wenn die Strings gleich sind - strcmp liefert 0 bei Übereinstimmung */
Das letzte erwischt viele. strcmp liefert 0, wenn die Zeichenketten übereinstimmen, „gleich" liest sich in einem if also als „nicht". if (strcmp(a, b) == 0) zu schreiben ist klarer und tut genau dasselbe.
Vergleiche liefern int
Ein Vergleich ist in C keine besondere Art von Ausdruck. Er wertet zu einem gewöhnlichen int mit dem Wert 1 oder 0 aus, und du kannst ihn ausgeben, speichern oder damit rechnen:
Der !-Operator funktioniert genauso: Er liefert 1, wenn sein Operand null ist, und sonst 0. !!x ist daher ein klassisches Idiom, das jeden Wert auf genau 0 oder 1 normiert.
bool, true und false (C99)
C99 fügte einen echten Wahrheitstyp namens _Bool hinzu sowie einen Header, der ihm einen lesbaren Namen gibt:
<stdbool.h> ist winzig: Er definiert bool als _Bool, true als 1 und false als 0. Das ist der ganze Header. Doch ihn zu benutzen macht die Absicht sichtbar, was int flag = 1; nie tut.
_Bool hat ein wirklich eigenes Verhalten: Er speichert nur 0 oder 1. Die Zuweisung eines Werts ungleich null wird zu genau 1 konvertiert:
Es gibt in printf weiterhin kein %b für Wahrheitswerte - gib sie mit %d aus, oder wandle sie selbst in Text um:
In C23 wurde das noch einfacher: bool, true und false wurden echte Schlüsselwörter, das Einbinden ist also nicht mehr nötig. Der meiste Code zielt heute noch auf C17, behalte also das #include <stdbool.h>.
Einen Wahrheitswert aus einer Funktion zurückgeben
Hier verdient sich bool seinen Platz am deutlichsten:
Ein bool-Rückgabetyp sagt dem Aufrufer, was der Wert bedeutet. Ein int-Rückgabetyp einer Funktion namens check_file könnte ein Wahrheitswert, eine Anzahl oder ein Fehlercode sein - und nach den Konventionen der C-Bibliothek ist es oft das Dritte, wobei 0 Erfolg bedeutet. Den Typ zu benennen nimmt das Raten heraus.
Der = statt ==-Bug
Weil die Zuweisung ein Ausdruck ist, der den zugewiesenen Wert liefert, und weil jeder Wert ungleich null wahr ist, kompiliert das hier:
Zwei Dinge gingen gleichzeitig schief. Die Bedingung wies x den Wert 5 zu und zerstörte damit seinen Wert, und testete dann 5, was ungleich null ist, also lief der Zweig. Das Programm hat standardmäßig weder einen Fehler noch eine Warnung.
Schlimmer noch: Die Variante mit 0 führt ihren Zweig nie aus:
if (found = 0) { /* wird nie genommen - und found ist jetzt 0 */ }
Drei Verteidigungslinien, nach Nützlichkeit geordnet:
Mit Warnungen kompilieren. -Wall fängt es ab:
warning: suggest parentheses around assignment used as truth value
Auf null Warnungen zu kommen ist hier die eigentliche Lösung, und sie kostet ein Flag.
Die Konstante zuerst schreiben. Die „Yoda-Bedingung" macht aus dem Tippfehler einen Übersetzungsfehler:
if (5 == x) /* korrekt */
if (5 = x) /* Fehler: kein L-Wert - der Compiler hält dich auf */
Manche Teams lieben das, andere finden es unleserlich; beide Positionen sind vertretbar, aber es funktioniert.
Sei bewusst, wenn du es so meinst. Eine Zuweisung in einer Bedingung ist beim Lesen von Eingaben wirklich nützlich:
int c;
while ((c = getchar()) != EOF) { ... }
Die zusätzlichen Klammern um c = getchar() sind wegen der Vorrangregeln nötig, und sie signalisieren zugleich, dass die Zuweisung beabsichtigt ist. Das ist das Idiom - wenn deine Zuweisung-in-einer-Bedingung nicht so aussieht, ist sie wahrscheinlich ein Tippfehler.
Häufige Fehler mit Wahrheitswerten
Mit true vergleichen. if (flag == true) funktioniert mit stdbool.h, geht aber in dem Moment kaputt, in dem flag ein int mit dem Wert 42 ist, denn 42 == 1 ist falsch. Schreibe if (flag).
Verkettete Vergleiche. if (0 < x < 10) ist immer wahr: 0 < x liefert 0 oder 1, und beide sind kleiner als 10. Schreibe if (x > 0 && x < 10).
Bitweise statt logisch. & und | sind nicht && und ||. Sie werten nicht abkürzend aus und sie verknüpfen Bits:
Beide Operanden sind „wahr", und dennoch ist a & b gleich 0. & zu benutzen, wo du && meintest, erzeugt eine Bedingung, die nur für bestimmte Werte falsch ist - die schwerste Art von Bug.
Gleitkommazahlen auf Gleichheit prüfen. if (0.1 + 0.2 == 0.3) ist falsch. Vergleiche stattdessen gegen eine Toleranz - siehe Datentypen für den Grund.
Häufig gestellte Fragen
Hat C einen Wahrheitstyp?
Seit C99 ja: _Bool ist ein eingebauter Typ, und das Einbinden von <stdbool.h> gibt dir die freundlicheren Schreibweisen bool, true und false. Vor C99 gab es überhaupt keinen Wahrheitstyp - Programme benutzten int mit 0 für falsch und 1 für wahr, was weiterhin funktioniert und weiterhin verbreitet ist.
Was gilt in C als wahr?
Jeder Wert ungleich null. 1, -5, 0.01 und ein Zeiger ungleich null sind in einer Bedingung alle wahr; nur 0, 0.0 und ein Nullzeiger sind falsch. Es gibt keine separate Wahrheitsprüfung - if (x) testet schlicht, ob x ungleich null ist.
Wie benutze ich bool in C?
Füge #include <stdbool.h> hinzu, dann funktioniert bool ready = true; wie erwartet. Unter der Haube ist bool gleich _Bool, das nur 0 oder 1 speichert - die Zuweisung eines beliebigen Werts ungleich null speichert genau 1.
Was ist der Unterschied zwischen = und == in C?
= weist zu, == vergleicht. if (x = 5) weist x den Wert 5 zu und testet dann 5, was ungleich null ist, also läuft der Zweig immer. if (x == 5) prüft auf Gleichheit. Das Erste kompiliert ohne Fehler - deshalb gibt es -Wall und die Angewohnheit, if (5 == x) zu schreiben.