Lvalueness is an important attribute of expressions in C/C ++. Only one left-value expression can be used to reference and change the value of an object. (In some cases, the right value expression can also be referenced (refer) to an object, and the value of this object may be indirectly modified, as described later ).
What is an object? If not explicitly stated, the objects mentioned here are more extensive than the narrow class/object. In C/C ++, the so-called object refers to a region of storage in the execution environment, and the content in the region represents (represent) value of the object ). Note that we refer to "representation" here. For an object, if we need to extract the value of (FETCH), we need to reference it through a certain type. Using different types, interpreting the content of the same object may lead to different values or some undefined behaviors.
Before introducing the left value, we also need to introduce a concept: variable ). Some people often confuse variables with objects. What is a variable? The so-called variable is a declaration. Through Declaration, we map a name to an object. When we use this name, it indicates that we perform some operation on this object. But not every object has a name, and it does not mean a corresponding variable. For example, a temporary object (temporary object) does not have a name associated with it (do not call it a temporary variable by mistake, which is incorrect ).
1. Left value in C
1.1 according to the definition of C, the left value is an expression referenced to the object. With the left value, we can retrieve the value of this object. You can also modify the value of this object through the modifiable left-value expression (modifiable lvalue. (It should be noted that, in C ++, the left value can also be referenced to a function, that is, if expression F references a function type, in C, it is neither the left nor the right value; in C ++, It is the left value ). Because the left value references an object, when we use & to perform an operation on the left value expression (or only to the left value expression and function, the address of the object can be obtained (there are two types of left-value expressions that cannot be accessed, one is bit-field type, because the minimum addressing unit in the implementation is byte; the other is the variable compiler with the register identifier, which may be optimized to the register using the register modifier ).
Ex1.1 Char A [10]; // A is an lvalue representing an array of 10 ints. Char (* P) [10] = & A; // & A is the address of the array. Const char * P = "Hello World"; // "Hello world" is an lvalue of type char [12] // In C, type const char [12] in C ++. Char (* P) [12] = & "Hello World "; Struct s {int A: 2; int B: 8 ;}; Struct s t; Int * P = & T. A; // error. t. A is an lvalue of bitfield. Register int I; Int * P = & I; // error. I is an lvalue of register type. Int A, B; Int * P = & (a + B); // error. A + B is not an lvalue. |
1.2 suppose that expr1 is a pointer to an object type or an incomplete type (incomplete type, that is, the layout and size of this type are unknown, we can assert that * expr1 must be a left-value expression, because * expr1 indicates referencing the object pointed to by expr1 according to the * operator definition. If expr1 is a simple name, it represents a variable.
Similarly, this expression is also a left value because it represents the object corresponding to the variable. For subscript operators, we can make the same conclusion, because expr1 [expr2] is always equal to * (expr1) + expr2), then p-> member, it is also a left-value expression. However, for expr1.expr2, we cannot determine that it is a left expression. Because expr1 may not be the left value.
It should be noted that the Left value is only a static attribute of the expression. When we say that an expression is the left value, it does not mean that it must reference a valid object. Int * P; * P is the expression of the Left value. However, the result of reading and writing the object referenced by * P may be undefined.
Ex1.2 Extern struct a; Struct a * P2 = &; A is an expression of the Left value, so it can perform the & operation. However, stru A is still incomplete. // In C ++ Extern Class; A & R = A; // OK. refers to A, though a with an incomplete type. |
1.3 modifiable left Value
In terms of semantics, in the expression of the object corresponding to the left value, the left value must be a modifiable left value. For example, the left operand in a value assignment (including a compound value assignment) expression must be a modifiable left-value expression and the Self-increment/decrement operator.
Ex1.3 Const int A [2], I; // Note: A unintialized. Legal in C, illegal in C ++. I ++; // error, I is an lvalue of Type const Int. A [0] --; // error, a [0] is an lvalue of const Int. |
1.4 another concept that corresponds to the left value is the right value (Rvalue ). In C, the right value is also expressed by the value of the expression. That is, the right value emphasizes not the expression itself, but the result after the expression operation. This result often does not reference a certain object and can be regarded as the intermediate result of calculation. Of course, it may also reference a certain object, but through this right expression we cannot directly modify this object.
1.4.1 storage location of the right value
Ex1.4
Int I;
I = 10;
10 is an expression of the right value. the semantics of the preceding sentence is to modify the object referenced by I with the value of integer constant 10.
From the Assembly Language Perspective, the above statements may be translated:
MoV addr_of_ I, 10;
10. The value is hardcoded into the machine command;
The right value can also be stored in the register:
Int I, J, K;
K = I + J;
The I + J expression is a right value, which may be stored in registers.
MoV eax, dword ptr [addr_of_ I]; MoV EBX, dword ptr [addr_of_j]; Add eax, EBX; MoV dword ptr [addr_of_k], eax; |
Here, the result of the I + J expression is in eax, and the result is the value of the I + J expression, which does not reference a certain object.
In some cases, a right-value expression may also reference an object.
Struct s {char C [2];}; Struct s f (void ); Void g () { F (). I; F (). c [1]; // (*) } |
The F () expression is a function call. The expression type is the return type struct s of F. The F () expression is the right value expression, but it usually corresponds to an object, here, the return value of the function is a structure. If an object (a storage area) is not used properly, the register is almost incompetent, and the [] operator must be referenced to the object in semantics.
Although the right value may be referenced to an object, it must be noted that in the right value expression, the lifetime of the referenced object and the referenced object is often not controlled by programmers.
1.4.2 why do I need the right value? The right value indicates the value after an expression operation. This value is not stored in the specified place. When we need a value after an expression operation, we need the right value. For example, in the Value assignment operation, A = B; we need to use the expression B value to modify the object represented by. If expression B is a left value expression, we must extract (FETCH) the value of this object from the object represented by B, and then use this value to modify the object represented by. This removal process is actually a process from the left value to the right value. This process is not clearly stated in C; but in C ++, it is clearly classified as one of the standard transformations, and the left-to-rvalue conversion is converted ). Looking back at the code above, in the I + J expression, the + operator requires that both the left and right operands are the right values. Rows 1 and 2 are the process of getting the value of the object corresponding to the left value expression I and J. In this process, lvalue-to-rvalue conversion. I + J itself is the right value. Here, you do not need to execute lvalue-to-rvalue conversion and directly assign the right value to K.
1.4.3 what is the type of the right value expression? In C, the right value is always the type of CV-unqualified. Because we cannot or cannot modify the right value even if it corresponds to an object. In C ++, the right value of the built-in type is CV-unqualified, but the right value of the class type, because c ++ allows indirect modification of its corresponding object, the right-value expression has the same nature as the left-value CV-qualified. (For details, refer to later)
Ex1.5 Void F (INT ); Void g () { Const int I; F (I); // OK. I is an lvalue. After an lvalue-to-rvalue conversion, the rvalue's // Type is int. } |
1.5 after understanding the concept of the left and right values, we can better understand why some operators need the right value, while some operators need the left value in some cases. Simply put, when an operator's operand is an expression and we only need the value of this expression, if this expression is not the right value, we need to retrieve the value of this expression; for example, in arithmetic operations, we only need the value of the left and right operands. In this case, the right value is required for the left and right operands. Any left value expression used as its operand must be converted to the right value. If we need not the value of the expression, but other information of the expression, for example, we only care about the type information of the expression, for example, as the operand of sizeof, we do not care about whether the expression is a left or right value. We only need to obtain its type information. (In c ++, there are static and dynamic types of expression information. In this case, the left value of the expression also affects the semantics of some operators. For example, typeid, which can be analyzed later ). Some operators must require that their operands be left values, because they need information about the object referenced by the expression, such as the address, or want to reference or modify the value of the object, for example, the value assignment operation.
According to the analysis, we can summarize which expressions are left values in C, and which operators require that their operands be left-value expressions:
Table 1: Left expression (from C reference manual)
--------------------------------------------------
Expression | condition |
__________________________________________________
Name | Name Is the variable name |
--------------------------------------------------
E [k] |/|
--------------------------------------------------
(E) // parentheses | E is the left value |
--------------------------------------------------
E. Name | E is the left value |
--------------------------------------------------
E-> name |/|
--------------------------------------------------
* E |/|
--------------------------------------------------
String Literal (String Literal Value) |/|
--------------------------------------------------
Here, the left value expression is described above. Only the remaining expressions are described. name, if e is the left value, E. name indicates the name of the referenced member in Object E, which is also the left value. The bracket expression does not change the meaning of E, so it does not affect its left value. In addition, the function call expression always has the right value. Special emphasis should be placed on the string literal. It has been previously stated that it is the left value and has the char [N] type in C. in C ++, the type is const char [N]. in C, char [N] is used to maintain forward compatibility. The left value expression in c ++ is more complex, but the left value expression in c ++ is still left.
1.5.1 The operators that require the operand to be the left value include: & (address fetch operation) (the operand can also be a function type); ++/--: The left operand of the value assignment expression. In addition, in array-to-pointer conversion, c89/90 requires that the array must be a left-value array to perform the conversion.
Ex1.6 Char C [2]; C [0]; // |
C [0] is equivalent *(? + 0); then expression C is converted from the left value of the char [2] type to the right value of a char *, which represents the address of the first element of the array;
Ex1.7 Struct s {char C [2];} f (void ); Void g () { F (). c [0]; F (). c [0] = 1 ;(*) } |
Expression F (). C [0] is equivalent to * (f (). C) + 0), but here f (). C is an array of right values in c89/90. Therefore, the above expression is invalid. In c99, array to pointer conversion is no longer an array of left values. The above expression is valid. In addition, although f () is the right value, F () references an object through F (). C [0] The left expression can reference a part of the object and can be modified through (*) (because the left expression is modifiable lvalue, however, the behavior of trying to modify it is undefined, but it works with the left and right values)
* About array types
In most cases, the array type degrades to a pointer (Right Value) pointing to its first element. Therefore, it is easy to misunderstand the left and right values and type judgment. There are several scenarios where arrays do not degrade. One is declaration, the other is the sizeof operand, and the other is the & addressing operand.
Ex1.8 Int A [3] = {1, 2, 3 }; Int B [3]; B = A; // error. B Converted to int * (Rvalue): array degration. Int * P = A; // array degration: a converted to an rvalue of int * Sizeof (a); // No degration. & A; // No degration. |
In C ++, arrays are not degraded in other scenarios, such as initializer as a reference; when it is used as the operands of typeid/typeinfo and template derivation.
2. Left value of C ++
2.1
Different from C, in C ++, a left-value expression can be referenced not only to an object, but also to a function. If F is a function name, expression F indicates a left-value expression that references the corresponding function; void (* p) (void ); * P is also a left value. However, the left value of a function type cannot be modified. * P = f; // error. (Note: the non-static member function of the class does not refer to the function type here. It cannot exist without an object, and the static member function is a function type)
Another difference is that for the register variable, C ++ allows the address to be retrieved, that is, the register int I; & I; operation, in fact, the C ++ compiler is required to ignore the suggestions specified by register specifier. In C/C ++, register and inline are just suggestions for Compiler Optimization. The choice of such suggestions is determined by the compiler.
In C ++, a class type expression with the right value references an object, which is often a temporary object (temporary object ). In C ++, We can indirectly modify the object through the member function of the right value expression of class type.
Ex2.1 Class { Int I, J; Public: Void F () {I = 0 ;} Void g () const {I ;} }; A foo1 (); Const A foo2 (); A (). F (); // OK, modify the temporary object. A (). G (); // OK, refer to the temporary object. Foo1 (). F (); // OK, modify the temporary object. Foo1 (). G (); // OK, refer to the temporary object. Typedef const a B; B (). F (); // error. B ()'s an rvalue with const, B (). G (); // OK, refer to the temporary object. Foo2 (). F (); // error. B ()'s an rvalue with const, Foo2 (). G (); // OK, refer to the temporary object |
Note that the right expression of class type in C ++ can have the const/volatile attribute, while the right value in C is always CV-unqualified.
Ex2.2 Struct a {char C [2];}; Const struct a f (); |
In C, the return value of F () is always an expression of the right value, which has the struct a type (const is ignored ).
2.2
Reference type is introduced in C ++, which is always referenced to an object or function. Therefore, when we use reference, it is equivalent to performing operations on the referenced objects/functions, so the expression of the reference type is always the left value.
(When analyzing expression types, if an expression expr initially has a T & type, the expression will be treated as a left value expression with a type T)
Ex2.3 Extern Int & Ri; Int & F (); Int g (); F () = 1; Ri = 1; G () = 1; // error. |
The return type of function call f () is Int &, so the type of expression F () is equivalent to an int type left value expression.
The return type of the function call G () is int, so the expression g () is the right value expression of the int type.
Compared with C ++, the return value of function calls in C is always the right value.
2.3
Compared with C, in C ++, the conversion expression may generate the left value. If the conversion target type is reference type T &, the conversion expression is a type T left value expression.
Ex2.4 Struct base {// polymorphic type Int I; Virtual F () {}// do nothing; }; Struct derived: Base { }; Derived D; Base B; Dynamic_cast <base &> (d). I = 1; // dynamic_cast yields an lvalue of Type Base. Dynamic_cast <base> (d); // yields an rvalue of Type Base. Static_cast <base &> (d). I = 1 ;// (Base &) d). I = 1; |
2.4
For the member selection expression e1.e2, C ++ is much more complex than C.
A) if the E2 expression type is T &, the expression e1.e2 is also a T left value expression.
B) If E2 is not a reference type, but a static member of type T (data/function member), e1.e2 will also be a T left expression, because e1.e2 actually references a static member, the existence of this Member is irrelevant to the left value of E1.
C) if E1 is the left value and E2 is the data member, e1.e2 is the left value and is referenced to the corresponding data member of the object represented by E1.
Ex2.5 Struct { Public: Static int Si; Static void SF (); Int I; Void F (); Int & Ri; }; Extern; A g (); Void test () { Void (* pf1) () = & A. SF; // A. SF is an lvalue of function type, refers to a: SF. Void (* pf2) () = & A. F; // error. A. F is not an lvalue and types mismatch. G (). Ri = 1; // OK. G (). Ri is a modifiable lvalue, though g () is an rvalue. G (). SI = 1; // OK. refers to a: Si; G (). I = 1; // error. G (). I is an rvalue. A. I = 1; // OK. A is an lvalue. } |
For E1-> E2, it can be viewed as (* E1 ). E2 is determined by the preceding method.
For the expression E1. * E2, if E1 is the left value and E2 is a pointer to a data member, this expression is the left value.
For E1-> * E2, it can be considered as (* E1 ). * E2. when E2 is a pointer to a data member, this expression is the left value.
2.5
Compared with C, both the prefix ++/-- Expression and the value assignment expression in C ++ return the left value.
If the second operand of a comma expression is a left-value expression, the comma expression is also a left-value expression.
Conditional expressions (? :), If the 2nd and 3rd operands have the same type, the result of this conditional expression is also left.
Ex2.6 Int I, J; Int & rI = I; Int * Pi = & (I = 1); // OK, I = 1 is an modifiable lvalue referring to I; +++ I; // OK. But maybe issues undefined behavior because of the constraints // About sequence points. (I = 2, j) = 1; // OK (I = 1 )? RI: J) = 0; // OK |
Note that, due to such changes, the well-formed code in C may produce undefined behaviors in C ++; on the other hand, even if all the code in C/C ++ is well-formed, different behaviors may occur.
Ex2.7 Int I, J; I = j = 1; // well formed in C, maybe undefined in C ++. Char array [100]; Sizeof (0, array ); |
In C, sizeof (0, array) = sizeof (char *). In C ++, sizeof (0, array) = 100;
2.6
The typeid expression is always left.
2.7
Where the left value is required in C ++:
1) left operand in the value assignment expression, which needs to be modifiable;
2) operands in auto-increment/subtraction;
3) the operand in the address fetch operation requires the left value (object type, or function type) or qualified ID.
4) for heavy-duty operators, because they execute the semantics of the function, the operator requires the left value of the operand, the type and left value of the operator expression, it is determined by the function declaration.
2.8
The effect of left-value on program behavior:
2.8.1
The left value of the expression has an important impact on the behavior of the program. If a (modifiable) left value is required, and no corresponding left value meets the requirements, the program will be ill-formed; otherwise, if a right value is required, then a left value expression needs to be converted to a right value through lvalue-to-rvalue conversion.
A scenario where the left value or only the static type information of the expression is required, regardless of the Left value of the expression, some standard conversions on the left value expression are often restrained; when the right value is required, a left value expression usually needs to undergo function-pointer conversion, array-pointer conversion, and left-right value conversion.
Ex2.8 Char C [100]; Char (& rarr) [100] = C; // suppresses array-to-pointer conversion on C. Char * P1 = C; // C will be adjusted to type "char *". Void F (); Void (& RF) () = F; // suppresses function-to-pointer conversion on expression F Void (* PF) () = F; // F will be adjusted to type "pointer to function // Type void ()". Sizeof (0, c); // The expression's value is sizeof (char *) in C; // 100 in C ++, since the expression (0, c) is an lvalue // In C ++. |
2.8.2
In addition, in some cases, the left and right values are acceptable, but different behaviors are executed based on the left and right values of the expressions:
Ex2.9 Typeid's behavior Class B {public: Virtual F () {}}; // polymorphic type. Class {}; B & Foo (); Typeid (FOO (); // Foo () is an lvalue of a polymorphic type. runtime check. Typeid (A (); // rvalue, statically Typeid (B (); // rvalue, statically A; Typeid (a); // lvalue of a non-polymorphic type, statically. //.......................... Reference's behavior Class C {PRIVATE: C (const C &) ;}; // notice Extern C; C g (); C & RC1 = C; // bind directly. (1) C & RC2 = g () // error. (2) Const C & r2 = g (); // error. (3) |
Here expression C is a left-value expression, and its type (c) is compatible with the RC1 type, so (1) is bound directly; while (2) and (3, the expression on the right is an expression of the right value, and the expression on the right cannot be converted to a compatible type with the variable on the left by using user conversion. Therefore, a temporary object needs to be constructed here, the C constructor and the right value G () are used to initialize the constructor. However, the copy constructor of C is not accessible. Therefore, the preceding statement is invalid.