Lvalue vs Rvalue in C: Definitions and Grammar

In the C programming language, expressions are categorized based on whether they refer to a memory location or simply compute a transient value. An lvalue designates an object residing in a specific, identifiable memory address, while an rvalue refers to the data value itself produced by an expression. This distinction governs how expressions are evaluated, assigned, and manipulated within the syntactic grammar defined by the ISO C standard.

The Formal Grammar and Definitions

Historically, the terms originated from assignment syntax: an lvalue appeared on the left side of an assignment operator (=), and an rvalue appeared on the right side.

In the formal C grammar (ISO/IEC 9899), the term "rvalue" is rarely used directly; instead, the standard refers to the "value of an expression." An lvalue is formally defined as an expression that potentially designates an object—where an "object" is a distinct region of data storage in the execution environment.

Syntactic Categories of Lvalues

An expression is an lvalue if it refers to a persistent memory location that can be read or modified. In C syntax, lvalues include:

  • Identifiers: Variable names that designate objects in storage (e.g., int x; where x is an lvalue).
  • Pointer Indirection: The unary * operator applied to a pointer expression (e.g., *ptr).
  • Array Subscripts: The [] operator evaluated as indirection (e.g., arr[i], which is syntactically equivalent to *(arr + i)).
  • Member Access: The . operator applied to an lvalue struct or union, or the -> operator applied to a pointer expression (e.g., point.x or ptr->x).
  • String Literals: In C, string literals (such as "hello") are statically allocated arrays of char, making them lvalues, although attempting to modify them results in undefined behavior.

Modifiable vs. Non-Modifiable Lvalues

The C grammar enforces that the left-hand operand of an assignment operator must be a modifiable lvalue. An lvalue is non-modifiable if:

  1. It possesses an array type.
  2. It possesses an incomplete type.
  3. It is qualified with the const type qualifier.
  4. It is a struct or union containing a member with a const qualifier.

Syntactic Categories of Rvalues

An rvalue represents the evaluation of an expression that yields a value without binding that value to an accessible, named memory address. In C syntax, rvalues include:

  • Numeric and Character Literals: Constants such as 42, 3.14, and 'a'.
  • Arithmetic and Bitwise Expressions: Operators that compute intermediate results, such as a + b, x * y, or i << 2.
  • Logical and Relational Expressions: Results of comparisons, such as a > b or x && y.
  • Function Calls: Any function call returning a non-void value (e.g., get_count()).
  • The Address-of Operator: The expression &var yields an rvalue representing the address, even though var itself is an lvalue.
  • Cast Expressions: Explicitly casting a type (e.g., (int)floating_val) yields an rvalue.

Grammar Constraints in Assignments

The C grammar defines an assignment expression as:

assignment-expression:
    conditional-expression
    unary-expression assignment-operator assignment-expression

Syntactically, the grammar requires a unary-expression on the left side of the assignment operator. Semantically, the compiler enforces that this unary-expression must resolve to a modifiable lvalue.

An lvalue automatically undergoes an lvalue-to-rvalue conversion whenever it is used in a context that expects a value rather than an object designation (such as the right-hand operand of an assignment or an operand in an arithmetic expression). Conversely, an rvalue cannot be converted into an lvalue; it has no designated storage location that the programmer can reference via syntax.