[zt]关于左值"lvalue"和右值"rvalue"的一点理解

关于左值"lvalue"和右值"rvalue"的一点理解
原文地址:http://bbs.chinaunix.net/viewthread.php?tid=749505&

看了帖子
"hello" 的类型是 const char *?
发现很多朋友对"lvalue"和"rvalue"理解有误,我先谈谈自己对此的一些理解,并期望能够引起更多朋友的广泛讨论。也算起到抛砖引玉的作用吧。
QUOTE:
注:
这里所说主要针对标准C语言。(感谢whyglinux兄指正)

首先说明一下何谓"l-value”和"r-value“。
"l-value”的定义:
QUOTE:
《TCPL》A.5:An object is a named region of storage, an l-value is an expression referring to an object.


QUOTE:
《ISO/IEC9899 WG14/N1124》P58:An l-value is an expression with an object type or an incomplete type other than void.

"r-value"的定义:
QUOTE:
《C: A reference manual》(Fifth Edition) P203: An expression that is not an l-value is sometimes called an r-value because it can be only on the right-hand side of an assignment.

可以看出《TCPL》和《C99 WG14》(准标准文档)对"lvalue"的定义有一定的变化,后者更宽泛一些。
"l-value"的称谓来自于赋值运算,
QUOTE:
《ISO/IEC9899 WG14/N1124》P58:The name "l-value" comes originally from the assignment E1 = E2, in which the left operand E1 is required to be a (modifiable) l-value.

但是其本身表示的含义不局限于此。"lvalue"还可以分为一般的"lvalue"和"modifiable lvalue"。
QUOTE:
注:
这里为什么不说:"lvalue"还可以分为"unmodifiable lvalue"和"modifiable lvalue"
是因为在标准中只出现了"modifiable lvalue" 而没有出现"unmodifiable lvalue"的字眼,可以这样理解,但我个人不主张引入非标准中出现的专用名词,以免造成误解。
感谢whyglinux兄指出。


QUOTE:
《ISO/IEC9899 WG14/N1124》P58: A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions)wit a const-qualified type.

"lvalue"的概念可以用来对operators进行分析,就是看哪些operators可以产生"lvalue",哪些operators的左操作数要求是"lvalue"。例如:
QUOTE:
对于地址
(void *)0x800000FF
*0x800000FF就不是"lvalue",因为*0x800000FF是void类型,但是cast operator可以产生"lvalue":
*(char *)0x800000FF 是"lvalue",并且是"modifiable lvalue"。
*(char *)0x800000FF = ‘a';
QUOTE:
注:非常对不起,这个例子是不正确的。whyglinux兄指出:
0x800000FF是整型,不是指针类型,因此 *0x800000FF 是非法的,还谈不上是左值还是右值的问题。
你可能是想说明 *(void*)0x800000FF,不过显然也是非法的。
另外,你说的“cast operator可以产生"lvalue"”是错误的。恰恰相反,在标准 C 和 C++ 中,cast operator 操作结果产生的是右值。

其实cast operator产生左值只是编译器(如GCC)的extentions,标准C中在脚注中明确指出:
"A cast does not yield an lvalue."
(谢谢whyglinux兄斧正)

对于赋值运算,要求其左操作数必须为"lvalue"。(这个就不用多说了)

我觉得简单一点的话,"lvalue"和"rvalue"可以这样理解:
"lvalue"必须对应于一块确定的内存空间,并且在编译时已经确定了;
QUOTE:
注:
这里理解有误,这句话对于《TCPL》中"lvalue"的定义可能有效,但是对于更宽泛的定义有问题。根据《ISO/IEC9899 WG14/N1124》的定义,对于非 void 的非完整类型(incomplete type)的引用是左值,因此,它与此存储空间编译时是否确定、是否真实存在以及是否能够实际访问无关。那么是否可以这样理解:
"lvalue"必须对应于一块存储空间,并且是对非void 类型的object的reference。
(感谢whyglinux兄斧正)

"rvalue"可以理解为一段存储空间表示的值或一个表达式的值,它强调的是value的本意。
标准中建议这样理解:
QUOTE:
《ISO/IEC9899 WG14/N1124》P58 footnote 53):It (lvalue) is perhaps better considered as representing an object "locator value". What is sometimes called "rvalue" is in this International Standard described as the "value of an expression".

所以,我认为按照"rvalue"的定义,任何有确定值的量都可以作为"rvalue",只是按照C语言的标准,有些量作为"rvalue"将引发"undefined Behavior"或其他意想不到的问题。
而"lvalue"可以作为"rvalue",但是"rvalue"不一定可以作为"lvalue"来使用。
QUOTE:
注:
这里的说法需要进一步说明。C标准中对"lvalue"进行了原始的定义,而"rvalue"是在"lvalue"的基础上定义的,就相当于“错”在“对”的基础上定义为“不对”。whyglinux兄指出的:左值和右值的概念是对立的,左值可以作为右值是因为 C 和 C++ 标准中规定的 lvalue-to-rvalue 转换所致。虽然这个我没有找到原文(有哪位知道请不吝告知),但我觉得是有道理的。所以说"lvalue"可以作为"rvalue"(但不是所有的适合,因为对"lvalue"求值可能得到垃圾数据或造成非法操作)。另一方面,一个表达式是"lvalue"可以说是“天生的”,但是一个表达式是"rvalue"却存在两种情况:一类是“天生的”(比如整型常量100);而还有一类是"lvalue"转换来的,这一类的"rvalue"其实既可以称为"rvalue"(当然要在具体上下文环境中,因为"rvalue"的定义就是和合法的赋值运算紧密联系在一起的)又可以称为"lvalue",所以这一类的"rvalue"是可以作为"lvalue"来使用的。因此这里说“"rvalue"不一定可以作为"lvalue"来使用”主要是考虑这种情况。
例如:
CODE:
int x;
int y = 10;
x = y; /*这里变量y按定义是"lvalue",但是转化为"rvalue"来使用,针对赋值运算来说也可以称为
"rvalue"*/
y = 100; /*这里y按其本来的类型作为"lvalue"来使用*/

(感谢whyglinux兄指出)

举几个例子:
1、"hello world"是string literal(如帖中whyglinux兄所说),并且是数组类型,C标准说string literal 将存储在静态存储区,并且其元素具有char类型(注意:标准《ISO/IEC9899 WG14/N1124》并没有说其元素具有const char类型,其实在老版本的C中没有const关键字,它是从C++中借鉴过来的)。但是"hello world"确实是表示了同const char *相同的意思。所以可以这么说吧。有点扯远了,那么"hello world"是不是"lvalue"?答案是:它是不可修改的"lvalue"。可以这样来分析:首先"hello world"具有确定的数据类型,然后它所在的内存地址在编译的时候已经确定了(在静态存储区的某段连续的空间)。
2、看以下一段代码:
CODE:
char *p;
*p = 'a';
printf("%c\n", *p);

大家都能看出这是一段错误的代码,其原因可以归结为"incorrect use of assignment",因为*p不是
"lvalue",因为p指向了一个在编译期间无法确定的object,因而*p所对应的内存空间是不确定的。
QUOTE:
注:
这里解释完全错误。根据《ISO/IEC9899 WG14/N1124》对"lvalue"的定义,*p应该是"lvalue"。
(感谢wolf0403兄斧正)

那么*p是"rvalue"吗?答案是:它按定义来说是"rvalue"。因为*p不管怎样在每次运行的时候都会有值存在(只不过是垃圾数据或会导致非法访问)。所以应该说:*p是"rvalue"但是不能在实际中当"rvalue"来用。

那么p是不是"lvalue"呢?这是当然了,它是"lvalue"而且还是"modifiable lvalue",要不然如何初始化呢?
QUOTE:
注:“要不然如何初始化呢”一句中的“初始化”应该是“赋值”,不是笔误,是针对下一句代码的,是思路错误。呵呵。
(感谢whyglinux兄斧正)

原因也可以像以上所示方法分析。
CODE:
p = malloc(BUFSIZ * sizeof (*p));

3、"lvalue"可以作为"rvalue",但不是所有的都可以。以下代码:
QUOTE:
注:这里有错误。任何函数调用不是"lvalue",还有标准也明确给出函数名(function designator)不是左值,因为function designator 是function type而不是object type,所以它也不是"lvalue"。
例子应该是:不能作为"rvalue"的例子。
(感谢whyglinux兄斧正)


CODE:
void foo(void)
{
/*do something...*/
}
int i;
i = foo(); /*function call是表达式,foo()是"lvalue"但在这里不能作为"rvalue"*/
这里的foo()函数调用不是"lvalue"。

4、两个“天生”就是"rvalue"的例子
CODE:
#define CONSTANT 10
int num;

num = 3; /*整数、浮点数、字符(不是字符串)常量“天生”是"rvalue"*/
num = CONSTANT; /*宏定义的常量也是"rvalue"*/

其实我觉得深入了解C标准中的一些原始定义,不仅可以更深入地了解C语言本身,而且在查找问题的时候可以直接深入其根源,这样就能更容易地找到治本的良方了。这方面我觉得国内的朋友相比国外的同行来说应该加油了,一个很深刻的例子是:有一次,我在comp.lang.c上看到一位网友发帖问问题的时候对一段代码给出了这么一句话:
CODE:
<snip>
int result;
int x = 3;
int y = 4;
result = max(x, y);
<snip>

The parameters of the function max in the function call statement...
马上就有人指出这里的"parameters"用错了,应该是"arguments",因为"parameters"是用来描述形参,而"arguments"是用来描述实参的。这可能有语言上的差异,但有一点是相通的,那就是"argument"和"parameter"在C标准中具有不同的意思。


原文地址:https://www.cnblogs.com/karlchen/p/573559.html