Ausdruck, lvalue, rvalue

Ein Ausdruck bezeichnet die Verknüpfung, also eine Verkettung oder Verschachtelung von Operationen. Bei der Übersetzung eines Ausdruckes muss der Compiler alle Operanden des Ausdruckes in der richtigen Reihenfolge auswerten, alle Operatoren in der richtigen Reihenfolge abarbeiten und die Operationen wie gewünscht miteinander verknüpfen.

Durch die Abarbeitung von einzelnen Operatoren eines Ausdruckes entstehen Werte, welche nur als Zwischenergebnisse dienen und nach Gebrauch sofort wieder verworfen werden können. Solche Werte werden als rvalues bezeichnet. Andere Werte jedoch haben dauerhaften Charakter: Sie sind entweder im Speicher oder in einem eigens dafür reservierten Register des Prozessors gespeichert. Solche Werte werden als lvalues bezeichnet und werden von Operatoren benötigt, welche einen Wert speichern oder auf die Speicheradresse eines Wertes zugreifen.

Details

Im Englischen werden Ausdrücke als Expression bezeichnet. Jede Verknüpfung von Operationen, welche zu einem Wert führt, wird als Ausdruck bezeichnet. Einige Elemente der Sprachen C und C++ erwarten als Eingabe einen Ausdruck. Damit ist gemeint, dass als Eingabe beliebig komplexe Operationen erlaubt sind, solange sie einen Wert ergeben.

Damit der Compiler Operationen erfolgreich bearbeiten kann, spezifiziert jeder Operator, welche Art von Operanden er erwartet und was für ein Art von Wert er zurückgibt. Hierbei bezeichnet ein rvalue einen Wert, der aus der Auswertung eines beliebigen Ausdruckes entstehen kann. Ein lvalue bezeichnet einen Wert, der an einem lokalisierbaren Ort gespeichert ist, sprich, dieser Wert hat entweder im Speicher oder in einem Prozessorregister einen vorgesehenen Platz. Nur an lokalisierbare Werte können Operatoren Werte zuweisen.

Die Unterscheidung zwischen lvalues und rvalues ist für Neulinge in der Programmierung häufig schwierig und nur begrenzt nachvollziehbar, da sie schlussendlich nur für den Compiler von Bedeutung ist. Manchmal stolpern Einsteiger über Compilerfehler wie invalid lvalue ohne genau zu wissen, was diese Meldung bedeutet. Im Deutschen werden lvalues und rvalues auch als L-Wert und R-Wert bezeichnet, diese Begriffe werden jedoch auf dieser Seite nicht verwendet.

Die Bedeutung der beiden Buchstaben L und R ist niemals standartisiert worden, weswegen bis heute einige Unklarheit darüber herrscht. Auf dieser Seite wird von Localizable (L) und Readable (R) gesprochen: Lokalisierbar und Lesbar. Dies aus folgendem Grund: Um einen Operanden auswerten zu können, muss er insbesondere eines sein: Lesbar. Um jedoch einen Wert zuweisen zu können, wird ein Ort benötigt, wohin er zugewiesen werden kann. Ein Makro oder ein konstanter Wert beispielsweise braucht keinen Platz, er ist fest im Programm eingebunden, wird für eine Operation kurzerhand ausgelesen und gleich wieder vergessen. Eine Variable jedoch hat einen Platz reserviert, sei es im Speicher oder als Register des Prozessors. An eine solche Stelle kann ein Operator einen Wert schreiben, er kann einem Ort zugewiesen werden, er hat eine Adresse, er ist lokalisierbar.

Nicht immer bezeichnet ein lvalue eine Variable, ein lvalue kann genauso wie ein rvalue aus einem Ausdruck entstehen, der jedoch explizit einen lvalue zurückgeben muss. Ein typischer Ausdruck, der einen lvalue zurückgibt ist beispielsweise das Ansprechen eines Elementes eines Arrays oder eines Feldes einer struct-Variablen. Im Allgemeinen befindet sich der durch einen Ausdruck lokalisierbare Ort irgendwo im Speicher, mit Ausnahme jedoch von Variablen, welche mittels der register-Speicherklasse deklariert wurden.

Die Sprachen C und C++ verhalten sich betreffend lvalues und rvalues nicht immer gleich. Der Grund hierfür liegt in der Ausprogrammierung der eingebauten Operatoren, welche in C++ an einigen Stellen erweitert wurde. In der detailierten Beschreibung der Operatoren ist stets aufgeführt, welche Art von Operanden ein Operator erwartet und welche Art von Wert er zurückgibt.

Ursprüngliche Erklärung: Links und Rechts

Die beiden Buchstaben L und R leiten sich ursprünglich aus der Betrachtung des Zuweisungsoperators ab, welcher rechts stets einen rvalue erwartet, den er einem lvalue auf der linken Seite zuweist. Somit steht das L für Links und R für Rechts:

L = R
a = 5;

Diese rechts-links-Betrachtung gilt jedoch nur beim Zuweisungsoperator, bei welchem der Ausdruck auf der rechten Seite einen lesbaren Wert ergeben muss und links des Operators ein Ausdruck stehen muss, dessen Auswertung einen lokalisierbaren Wert ergibt, andernfalls kann der Zuweisungsoperator den Wert nirgendwo hin zuweisen, da er keine Adresse hat.

Bei allen anderen Operatoren stimmt diese rechts-links-Betrachtung jedoch nicht. Beispielsweise gibt es die beiden Varianten des Inkrements: Post-Inkrement-Operator und Pre-Inkrement-Operator. Die Post-Variante schreibt den Operator nach dem Operanden, die Pre-Variante davor, jedoch erwarten beide Operatoren einen lvalue. Dies deswegen, da die Operatoren diesen Operanden selbst verändern, ihn somit also auch lokalisieren können müssen. Dies kann mit folgenden zwei fehlerhaften Zeilen leicht überprüft werden. Beide Varianten erwarten einen lvalue, obschon der Wert bei der zweiten Zeile rechts steht:

invalid lvalue
invalid lvalue
5++;
++5;

Anders herum gibt es Operatoren, welche überhaupt keinen lvalue erwarten, sondern zwei rvalues. Beispielsweise der Additions-Operator:

no error
5 + 5;

Der Autor empfielt, sich einzig und alleine folgendes zu merken: L steht für Lokalisierbar. Alles andere ist Detail und wird im Programmieralltag kaum je zu Problemen führen. Selbst der C11-Standard hat in der Version C11 den Begriff rvalue komplett fallengelassen und spricht stattdessen nur noch von dem Wert eines Ausdrucks. Mit der Zeit wird sich ein natürliches Verständnis aufbauen, was ein rvalue und was ein lvalue ist.

Weiteres

Ein lvalue ist stets auch ein rvalue: Ein Ort, der lokalisierbar ist, kann auch gelesen werden. Es ist jedoch zu beachten, dass ein lvalue nicht gleichbedeutend ist mit schreibbar. Die Auswertung eines lvalues kann beispielsweise eine Variable ergeben, welche als const deklariert wurde.

Einige Operatoren erwarten weder lvalues noch rvalues, sondern spezielle Operanden. Im Folgenden steht eine Tabelle aller Operatoren mit Angabe der erwarteten Operanden und des Rückgabewertes. Hierbei steht R für rvalue, L für lvalue, S für Symbol, A für eine Argumenten-Liste und T für einen Typ. Die Angabe L/R bedeutet, dass sowohl lvalues als auch rvalues erwartet werden können, je nach Operation, die ausgeführt werden soll. Die Angabe RCL bedeutet, dass in C ein rvalue zurückgegeben wird, in C++ jedoch ein lvalue.

RValue Operatoren

LValue Operatoren

Speicher Operatoren

Typ Operatoren

Special Operatoren

Beim Funktionsaufruf wird unter C grundsätzlich ein lvalue als erster Operator erwartet. Da jedoch die Sprache C erlaubt, Funktionspointer direkt, also ohne Dereferenzierung, hinzuschreiben (Syntactic Sugar), wird hier auch die Angabe eines rvalues (ein Funktionspointer) aufgeführt.