值类别
C 中每个表达式(带有实参的运算符、函数调用、常量、变量名等)以两个独立属性刻画:类型和值类别。
每个表达式属于三个值类别之一:左值、非左值对象(右值)以及函数指代器。
左值表达式
左值表达式是除类型 void
之外的任何对象类型,且隐含地指代一个对象的表达式(当左值在求值时不实际指代一个对象时,行为未定义)。换言之,左值表达式求值得到对象标识。此值类别的名称(“左值”)是历史性的,并反映了 CPL 中,左值表达式作为赋值运算符的左运算数。
左值表达式可用于下列左值语境:
若将左值表达式用于除 sizeof
、_Alignof
或上述的运算符之外的语境,则任何完整类型的非数组左值会经历左值转换,这模仿的是从对象位置到其值的内存加载。同样地,在用于除 sizeof
、_Alignof
、取址运算符或从字符串字面量初始化数组之外的语境时,数组左值会经历数组到指针转换。
const
/volatile
/restrict
限定符和原子类型的语义仅应用于左值(左值转换将剥除限定符并移除原子属性)。
下列表达式是左值:
- 标识符,含具名函数形参,只要声明它们为指代对象(而非函数或枚举常量)
- 字符串字面量
- (C99) 复合字面量
- 括号表达式,若其无括号版本是左值
- 成员访问(点)运算符的结果,若其左参数是左值
- 通过指针访问成员(
->
)运算符的结果 - 对指向对象指针运用间接使用(一元
*
)运算符的结果 - 下标运算符的结果(
[]
)
可修改左值表达式
一个可修改左值是任何完整的非数组类型的、非 const 限定的左值表达式,而且若它是结构体/联合体,则递归地没有任何成员为 const 限定。
只有可修改左值表达式可用作自增减运算符的实参,赋值和复合赋值运算符的左实参。
非左值对象表达式
称为右值,非左值表达式是对象类型的表达式,它不指代对象,而是指代没有对象身份或存储位置的值。不能对非左值表达式取址。
下列表达式是非左值对象表达式:
- 整数、字符、浮点数常量
- 所有不返回左值的运算符,包括
- 任何函数调用表达式
- 任何转型表达式(注意看起来相似的复合字面量是左值)
- 作用于非左值结构体/联合体的成员访问(点)运算符,f().x、(x,s1).a、(s1=s2).m
- 所有算术、关系、逻辑及位运算符的结果
- 自增和自减运算符(注意:前置形式在 C++ 中是左值)的结果
- 赋值及复合赋值运算符(注意:它们在 C++ 中也是左值)的结果
- 条件运算符(注意:在 C++ 中,如果第二和第三个操作数为相同类型的左值则为左值)
- 逗号运算符(注意:在 C++ 中,如果第二个操作数是左值则为左值)
- 取址运算符,即使它被用一元
*
运算符的结果中和
作为特殊情况,void
类型表达式被假设成非左值对象表达式,得出一个不存在表示且不要求存储的值。
注意,拥有一个数组类型成员(可以是嵌套的)的结构体/联合体右值实际上指代一个拥有临时生存期的对象。此对象可通过由索引数组元素组成的左值表达式,或解引用该数组的数组到指针转换所得的指针来访问。
函数指代符表达式
函数指代符(由函数声明引入的标识符)是函数类型的表达式。当在异于取址运算符、 sizeof
及 _Alignof
(后两者在作用于函数时生成编译错误)的语境中时,函数指代符始终转换成指向函数的非左值指针。注意函数调用运算符是对指向函数指针,而非对函数指代符自身定义的。
引用
- C17 标准(ISO/IEC 9899:2018):
- 6.3.2.1 Lvalues, arrays, and function designators (第 40 页)
- C11 标准(ISO/IEC 9899:2011):
- 6.3.2.1 Lvalues, arrays, and function designators (第 54-55 页)
- C99 标准(ISO/IEC 9899:1999):
- 6.3.2.1 Lvalues, arrays, and function designators (第 46 页)
- C89/C90 标准(ISO/IEC 9899:1990):
- 3.2.2.1 Lvalues and function designators