Chapter 1: Introduction to Lvalues and Rvalues – Limitations of Surface Understanding
In C++ programming, lvalues and rvalues are fundamental and important concepts. Many developers’ understanding of them remains at the level of “the left side of the equals sign is an lvalue, and the right side is an rvalue,” but this perception often leads to confusion.
1.1 Surface Understanding of the Equals Sign
Beginners often understand it this way:
int x = 1;int y = 3;int z = x + y;
-
x is an lvalue, 1 is an rvalue
-
y is an lvalue, 3 is an rvalue
-
z is an lvalue, the result of x + y is an rvalue
1.2 Contradictions in Surface Understanding
However, this understanding leads to contradictions in the following situation:
int a = 1; // a is an lvalueint b = a; // a becomes an rvalue here?
The same variable a is an lvalue in the first line, but in the second line, it plays the role of an rvalue, which clearly does not conform to logic.
Chapter 2: Essential Differences – Lifespan and Memory Address
2.1 Core Characteristics of Lvalues
Lvalues refer to named values that point to specific memory (named objects), and they have:
-
Stable Memory Address: The address remains unchanged during its lifespan
-
Longer Lifespan: Typically lasts until the end of the scope
-
Addressable: Can use the
<span>&</span>operator to obtain the address -
Identifier: Has a variable name or can be referenced in other ways
int x = 10; // x is an lvalueint* p = &x; // Can obtain the address of x
2.2 Core Characteristics of Rvalues
Rvalues are anonymous values that do not point to stable memory addresses (unnamed objects), and they have:
-
Short Lifespan: Typically temporary
-
Not Addressable: Cannot use the
<span>&</span>operator to obtain the address -
Anonymous: No persistent identifier
int x = 10; // 10 is an rvalueint y = x + 5; // x + 5's result is an rvalue
Chapter 3: Analysis of Special Cases
3.1 String Literals as an Exception
int x = 1; // 1 is an rvalueset_val(6); // 6 is an rvalueauto p = &"hello"; // Compiles successfully!
Although string literals appear to be rvalues, the compiler actually stores them in the program’s data segment, allowing their addresses to be obtained.This is because string literals have static storage duration, existing throughout the program’s entire lifecycle, which is fundamentally different from ordinary integer literals.
3.2 Changes in Function Parameters
void set_val(int val) // val is a parameter{ int *p = &val; // Can obtain the address of val x = val;}
Even if an rvalue is passed in, the parameter <span>val</span> becomes an lvalue after entering the function because it has a name and a definite memory address.This characteristic is important as it explains why it is impossible to distinguish whether the passed parameter was originally an lvalue or an rvalue within the function. This is also the root of the problem that C++ needs to address with techniques like reference collapsing and perfect forwarding.
Chapter 4: Conclusion
Distinguishing between lvalues and rvalues should not remain at the surface level, but rather understand their essence:
-
Lvalues: Have identity, have address, long lifespan
-
Rvalues: No identity, no address, short lifespan