Zum Inhalt springen
Topos Uranos

Zusammenfassung

Ein C++-Ausdruck kann zwei verschiedene Dinge tun: einen Wert liefern und den Zustand des Programms ändern. x + 1 liefert nur einen Wert; x = 7 liefert einen Wert und ändert außerdem x; x++ liefert den Wert, den x hatte, und hinterlässt in x einen anderen. Diese Lektion untersucht diesen Unterschied zwischen dem Wert und der Wirkung eines Ausdrucks: die Zuweisung als Ausdruck, die zusammengesetzte Zuweisung, Inkrement und Dekrement in ihrer Präfix- und Postfixform und die Reihenfolge, in der die Teile eines Ausdrucks ausgewertet werden, die der Standard nur teilweise festlegt. Daraus folgt die Frage, die den Rest der Lektion ordnet: was der Standard über ein Programm verspricht. Die Antwort ist ein Katalog mit vier Graden, vom definierten bis zum undefinierten Verhalten, der alle bisher gesehenen Fälle zusammenfasst und für jeden angibt, was ihn erkennt (der Compiler, der Sanitizer, die Bibliothek) und was nichts erkennt.

Lernziele

  1. Den Wert eines Ausdrucks von seinem Nebeneffekt unterscheiden, bei Zuweisungen, Inkrementen und Aufrufen.
  2. Das Ergebnis von x++ und ++x vorhersagen, wenn sie als Wert verwendet werden, und ein Programm mit zusammengesetzten Zuweisungen von Hand nachvollziehen.
  3. Ausdrücke erkennen, die eine Variable zweimal ändern oder sie lesen und ändern, ohne dass der Standard eine Reihenfolge festlegt, und sie in getrennte Anweisungen umschreiben.
  4. Ein Konstrukt als definiert, implementierungsdefiniert, unspezifiziert oder undefiniert einordnen, mit einem Beispiel für jede Kategorie.
  5. Für jedes undefinierte Verhalten des Katalogs angeben, ob der Compiler davor warnt, ob der Sanitizer oder die Bibliothek es anhält oder ob nichts es erkennt.

Der Wert und die Wirkung

Die Lektion Arithmetik hat den Ausdruck als einen Baum untersucht, der sich auf einen Wert reduziert: 2 + 3 * 4 ergibt 14, und seine Berechnung ändert nichts außerhalb des Ausdrucks selbst. Die Lektion Relationen und Logik hat dasselbe mit den Bedingungen getan, deren Wert true oder false ist. Nun gibt es Ausdrücke, die neben einem Wert eine Spur im Zustand des Programms hinterlassen: Sie ändern den Wert einer Variablen, schreiben auf den Bildschirm oder lesen aus der Eingabe. Diese Spur heißt Nebeneffekt oder einfach Wirkung, und das Wort „Neben“ bedeutet nicht, dass sie weniger wichtig wäre: In x = 7 ist die Wirkung, dass x den Wert 7 annimmt, genau das, was man erreichen will.

Wenn dem so ist, muss man jeden Ausdruck mit zwei Fragen lesen: was er liefert und was er tut. Drei Beispiele legen die Unterscheidung fest. count + 1 liefert den Nachfolger von count und tut nichts: count behält seinen Wert. count = 9 liefert 9 und bewirkt, dass count den Wert 9 hat. std::println("hallo") ist ebenfalls ein Ausdruck, ein Funktionsaufruf, und was an ihm zählt, ist seine Wirkung: eine Zeile zu schreiben. Eigene Funktionen werden in der Lektion Funktionen untersucht, und dort taucht die Unterscheidung wieder auf: Eine Funktion, die nur ihr Ergebnis berechnet, ist leichter zu verstehen und zu testen als eine, die außerdem etwas außerhalb ihrer selbst ändert.

Eine Anweisung wie count + 1; ist gültiges C++: Der Wert wird berechnet und verworfen, sodass sie zu nichts dient, und GCC warnt davor mit -Wunused-value. Eine Ausdrucksanweisung hat ihren Sinn in ihrer Wirkung. Daraus folgt, dass die Ausdrücke mit Wirkung diejenigen sind, die das Programm voranbringen, und auch diejenigen, die Sorgfalt verlangen, denn die Wirkung tritt zu irgendeinem Zeitpunkt ein, und dieser Zeitpunkt ist nicht immer der, den der Leser annimmt.