Q:那么如何判断一个表达式是左值?
A:依据标准的定义来判断。[C99]An lvalue is an expression with an object type or an incomplete type other than void; 也就是说,如果一个表达式具有对象类型或非空不完整类型,那就是左值。其实这里关键的是对象类型,虽然不完整类型不是对象类型,但由于它可以通过某种方式补充完整,因此可以认为它也是一种对象类型;但void除外,因为void不能被补充完整。
Q:那么如何判断一个表达式具有对象类型?
A:那我们要先搞清楚什么是对象类型。Types are partitioned into object types (types that fully describe objects), function types (types that describe functions), and incomplete types (types that describe objects but lack information needed to determine their sizes). 所以“通过非函数类型声明的非类型标识符”都是左值。
其实,hpsmouse“把 *p 这样实实在在的表达式称为 lvalue,多少触及到了lvalue的重要实质”(pmerOFc语)。实际上,lvalue中的“l”可以理解为“location”(来自这篇文章,谢谢Tiger_Zhao提供链接)。早期的左值定义(比如C89)指的就是一个其结果有adrressable location(可以寻址的存储)的表达式,你可以往这个location放数据或信息。(The "l" in lvalue can be though of as location, meaning that an expression has an addressable location that data or information can be put into.)
其实左值无非是可以引用到对象的表达式,因此左值的概念和C里的对象是密不可分的,只要理解好了对象,就比较好把握左值了。C里的对象 (注意和“面向对象”里的“对象”完全两回事)是指一块命名的内存区域(An Object is a named region of storage—From “The C Programming Language”)。所以,左值的关键是拥有你可访问的存储!
double i;
int *p = ( int* )&i; /*p并非真正指向一个int对象,但*p依然是左值*/
int *p;
*p = .........; /*p并没有指向啥,但*p还是左值*/
Q:说了这么多左值,那右值的定义是什么呢?
A:[C99]右值(rvalue)是指表达式的值。(46页脚注)What is sometimes called “rvalue” is in this International Standard described as the “value of an expression”. 实际上,右值里的“r”其实也有“read”的意思(The "r" in rvalue can be thought of as "read" value—来自这篇文章),也就是要把存在左值的存储空间里的东西读出来。当然,这只是个用于帮助理解记忆的经不起推敲的说法,实际中很多右值并没有对应的左值,更谈不上从什么地方读出来了。
Q:有点晕。那左值表达式的值也是右值?
A:恩,对。实际上,除了上面必须使用左值的场合(以及作为sizeof的操作数)以外,所有左值表达式(数组类型的左值除外)在使用的时候其实是被转化为它所引用的对象所存储的值使用的。 Except when it is the operand of the sizeof .operator. the unary h operator. the ++ operator. the -- operator. or the left operand of the . operator or an assignment -operator. an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue). (上述道理和pmerOFc争论了大半夜才弄明白,感谢pmerOFc和supermegaboy。以上内容译摘自C89 6.2.2.1)。
也就是说,在C中,一个左值表达式,除了应用于一些特定的运算符时以外,在使用时会自动进行左值到右值的转换,并一直进行到无法再转换为止。因此,在C里表达式的值一定是右值(supermegaboy语),而且一定是不拥有可访问的存储的。在C++标准里也有类似的说法(飞雪提供)Whenever an lvalue appears in a context where an rvalue is expected, the lvalue is converted to an rvalue。
课后练习
来看下面这段代码,来自supermegaboy的《指针与数组的艺术》。
struct Test
{
int a[10];
};
struct Test fun(struct Test*);
int main( void )
{
struct Test T;
int *p = fun(&T).a; /* 语句1 */
int (*q)[10] = &fun(&T).a; /* 语句2 */
printf( "%d", sizeof(fun(&T).a)); /* 语句3 */
return 0;
}
struct Test fun(struct Test *T)
{
return *T;
}
小结:
(1)定义和含义
a) 左值是指具有对象类型或非空不完整类型的表达式。(关键是要可以引用到对象,也就是要可以拥有可访问的存储,l-location)
b) 右值(rvalue)是指表达式的值。(在C里表达式的值一定是右值;在期待右值时,左值会自动转化为右值。r-read)
(2)依据下述规则来判断左值:
a) “通过非函数类型声明的非类型标识符”都是左值
b) 每种运算符都规定了它的运算结果是否左值。
(3)常见规则
a) 下列运算符的操作数要求左值:Sizeof运算符, 取地址运算符 & , ++ 运算符, -- 运算符,赋值 = 运算符的左侧,成员 . 运算符的左侧。
b) 间接运算符*的运算结果是左值;取地址运算符&的运算结果是右值。
c) 下列表达式不能产生lvalue: 数组名,函数,枚举常量,赋值表达式,强制类型转换(目标类型是引用时除外),函数调用。