免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 7515 | 回复: 18
打印 上一主题 下一主题

关于左值"lvalue"和右值"rvalue"的一点理解 [复制链接]

论坛徽章:
0
1 [报告]
发表于 2006-05-03 19:29 |只看该作者
2、看以下一段代码:

  1. char *p;
  2. *p = 'a';
  3. printf("%c\n", *p);
复制代码

大家都能看出这是一段错误的代码,其原因可以归结为"incorrect use of assignment",因为*p不是
"lvalue",因为p指向了一个在编译期间无法确定的object,因而*p所对应的内存空间是不确定的。


我认为这一段说法不确切。 char *p 声明后,*p 不论何时,都是一个 lvalue。否则,*p = 'a'; 将引发的是一个编译错误,而不是运行错误。
C 语言标准规定的范畴在于源码层次而不在二进制层次。虽然 *p = 'a' 这样的代码在通常环境中可能会运行出错,但是 C 语言并没有规定这样是不允许的。在一个无虚拟内存的环境中,这个指令也许可以将 p 所指向的位置(如 p 在栈上声明,则应为随机值)赋值为 'a' 。

[ 本帖最后由 wolf0403 于 2006-5-3 19:32 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2006-05-03 20:07 |只看该作者
说这么多干嘛

简单问题复杂化,没必要

论坛徽章:
0
3 [报告]
发表于 2006-05-04 00:28 |只看该作者
要准确理解左值和右值的概念,首先需要明确我们指的是 C 还是 C++ 中的左值或右值。这是因为 C 和 C++ 对于左值及右值的定义是有区别的。另外,左值和右值的概念人为规定的成份很大,往往给出的定义不能囊括所有情况。

>> 《TCPL》A.5:An object is a named region of storage, an l-value is an expression referring to an object.

对于 C 语言来说,这个定义就不是太准确,特别在 C99 标准出台之后更是如此。这个定义中规定了“对象(object)”是“有名存储区(a named region of storage)”。且不说动态分配的内存(无名存储区)能否作为对象,只就 string literal 以及在 C99 中新增加的 Compound litertal 而言(它们都是对象,并且都是左值,但是又都是没有名字表示的),就不在上述定义的界定范围之内。

对于 C++ 来说,这个定义就更不适用了。因为 C++ 中对象也可能是一个非左值,即右值。同理,ISO/IEC9899 中为 C 语言提供的左值的定义也不适用于 C++。

>> "lvalue"还可以分为一般的"lvalue"和"modifiable lvalue"。

这句话说得比较别扭。是否可改为 "lvalue" 还可以分为"modifiable lvalue" 和 "unmodifiable lvalue"?

>> "lvalue"必须对应于一块确定的内存空间,并且在编译时已经确定了

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

根据上述定义,对于非 void 的非完整类型(incomplete type)的引用也是左值;而我们知道在程序中对于非完整类型是不能进行引用的,否则会在编译时产生错误。

因此,左值只是对应着某一存储空间,而与此存储空间是否真实存在、是否能够实际访问无关,更谈不上是在编译时确定的了——这显然否定了动态分配的对象也可以是左值。

>> "lvalue"可以作为"rvalue",但是"rvalue"不一定可以作为"lvalue"来使用。

左值和右值的概念是对立的,即非左即右(根据C++标准对左值的定义,C标准没有明确这么说)。左值可以作为右值是因为 C 和 C++ 标准中规定的 lvalue-to-rvalue 转换所致,但是右值不是“不一定”、是一定不能作为左值来使用。

>> 它是"lvalue"而且还是"modifiable lvalue",要不然如何初始化呢?

左值对象都可以被初始化,即使是对于不能改变的左值也是如此。否则,如果不能初始化你又如何使用它呢?因为未初始化就使用是非法的。显然上面一句的“初始化”应该是“赋值”的笔误。


了解了左值和右值的概念,说明对于语言又有了更深的理解。其好处就是:对于在编程中遇到的一些问题,原来可能“只知其然”,现在可以做到“知其所以然”。

论坛徽章:
0
4 [报告]
发表于 2006-05-04 15:42 |只看该作者
原帖由 wolf0403 于 2006-5-3 19:29 发表


我认为这一段说法不确切。 char *p 声明后,*p 不论何时,都是一个 lvalue。否则,*p = 'a'; 将引发的是一个编译错误,而不是运行错误。
C 语言标准规定的范畴在于源码层次而不在二进制层次。虽然 *p = 'a'  ...


你是对的,谢谢指正。我已将原文修改。

论坛徽章:
0
5 [报告]
发表于 2006-05-04 15:52 |只看该作者
要准确理解左值和右值的概念,首先需要明确我们指的是 C 还是 C++ 中的左值或右值。这是因为 C 和 C++ 对于左值及右值的定义是有区别的。另外,左值和右值的概念人为规定的成份很大,往往给出的定义不能囊括所有情况。

谢谢指出,目前对C++的标准还不甚了解,我的本意都是针对标准C的。

>> "lvalue"还可以分为一般的"lvalue"和"modifiable lvalue"。

这句话说得比较别扭。是否可改为 "lvalue" 还可以分为"modifiable lvalue" 和 "unmodifiable lvalue"?

这里主要是考虑尽量不误导大家,因为标准C中明确出现了"modifiable lvalue",但我自己在标准中还没找到"unmodifiable lvalue",不知是否是自己遗漏。

>> "lvalue"可以作为"rvalue",但是"rvalue"不一定可以作为"lvalue"来使用。

左值和右值的概念是对立的,即非左即右(根据C++标准对左值的定义,C标准没有明确这么说)。左值可以作为右值是因为 C 和 C++ 标准中规定的 lvalue-to-rvalue 转换所致,但是右值不是“不一定”、是一定不能作为左值来使用。

可能是表述不清吧,具体解释请参看原文修改的部分。若有不同意见请不吝赐教。

>> 它是"lvalue"而且还是"modifiable lvalue",要不然如何初始化呢?

左值对象都可以被初始化,即使是对于不能改变的左值也是如此。否则,如果不能初始化你又如何使用它呢?因为未初始化就使用是非法的。显然上面一句的“初始化”应该是“赋值”的笔误。

呵呵,这都看出来了,想必whyglinux兄确是一位一丝不苟之人啊,敬佩!
这里不是笔误,而是思路歪了呵呵,谢谢指正。

了解了左值和右值的概念,说明对于语言又有了更深的理解。其好处就是:对于在编程中遇到的一些问题,原来可能“只知其然”,现在可以做到“知其所以然”。

严重同意,知音啊。呵呵

[ 本帖最后由 kernelxu 于 2006-5-4 16:10 编辑 ]

论坛徽章:
0
6 [报告]
发表于 2006-05-05 04:19 |只看该作者
不错, 收藏了~~

论坛徽章:
0
7 [报告]
发表于 2006-05-05 13:14 |只看该作者
>> "lvalue"还可以分为一般的"lvalue"和"modifiable lvalue"。
>> 这里为什么不说:"lvalue"还可以分为"unmodifiable lvalue"和"modifiable lvalue"
>> 是因为在标准中只出现了"modifiable lvalue" 而没有出现"unmodifiable lvalue"的字眼,可以这样理解,但我个人不主张引入非标准中出现的专用名词,以免造成误解。

在标准中也没有“一般的lvalue”一说。不理解你说的“一般的lvalue”指的是什么:是"modifiable lvalue"还是非"modifiable lvalue"?抑或是同时指两者?

>> *0x800000FF就不是"lvalue",因为*0x800000FF是void类型,但是cast operator可以产生"lvalue"

0x800000FF是整型,不是指针类型,因此 *0x800000FF 是非法的,还谈不上是左值还是右值的问题。

你可能是想说明 *(void*)0x800000FF,不过显然也是非法的。

另外,你说的“cast operator可以产生"lvalue"”是错误的。恰恰相反,在标准 C 和 C++ 中,cast operator 操作结果产生的是右值。

>> 那么是否可以这样理解:
>> "lvalue"必须对应于一块存储空间,并且是对非void 类型的object的reference。

注意标准中说的是“An l-value is an expression with an object type or an incomplete type other than void”。因此,强调的是非 void 的 object typeincomplete type,而不是实际存在的 object。我在前面已经说过,左值概念与对象(存储空间)的实际存在与否无关。当然了,if an lvalue does not designate an object when it is evaluated, the behavior is undefined。

>> "rvalue"可以理解为一段存储空间表示的值或一个表达式的值,它强调的是value的本意。

“一段存储空间表示的值”其实就是“对象的值”。获得对象的值的过程我们通常称之为“求值”。对对象求值是一个由左值转化为右值的过程(lvalue-to-rvalue),所以对象的值是右值,但不能说对象是右值。关于 lvalue-to-rvalue 转化在 C99 标准的 6.3.2.1-2 和 -3 一节中叙述。

一个单独的对象也可以作为表达式使用,所以上述这句话可简写为“"rvalue"可以理解为表达式的值”。这也是标准中出现的描述。

>> "lvalue"可以作为"rvalue",但不是所有的都可以。

lvalue-to-rvalue 转化是标准规定的。lvalue 转换为 rvalue 实际上就是对左值求值的过程。存在不能对其求值的左值吗?

你在上面举的例子也说明不了你这句话的正确性。因为在 C 中,所有的函数调用产生的都是右值,而不是你说的左值(在 C++ 中,除了返回引用的函数调用是左值外,其它的函数调用结果也都是右值)。

论坛徽章:
0
8 [报告]
发表于 2006-05-05 15:56 |只看该作者
首先非常感谢whyglinux兄的大力支持和不遗余力的指导。

在标准中也没有“一般的lvalue”一说。不理解你说的“一般的lvalue”指的是什么:是"modifiable lvalue"还是非"modifiable lvalue"?抑或是同时指两者?

我查找到的原文这样说的:
regular "lvalue" and "modifiable lvalue"
(当然这里不是说"regular lvalue"和"modifiable lvalue",因为没有"regular lvalue"这一术语。是在下直译过来的)

可能这样说比较好一点吧:"lvalue"中包含一类特殊的"lvalue"——"modifiable lvalue"。我觉得理解起来也可以像您那样说:
"modifiable lvalue"和非"modifiable lvalue"
0x800000FF是整型,不是指针类型,因此 *0x800000FF 是非法的,还谈不上是左值还是右值的问题。
你可能是想说明 *(void*)0x800000FF,不过显然也是非法的。
另外,你说的“cast operator可以产生"lvalue"”是错误的。恰恰相反,在标准 C 和 C++ 中,cast operator 操作结果产生的是右值。

谢谢斧正,这个例子用来说明some operators can yield lvalue是错误的,这是其一;另一方面,说cast operator 产生"lvalue"是错误的,它产生左值是编译器(如GCC)的extentions,标准C中在脚注中明确指出:
"A cast does not yield an lvalue."  
(因为标准中的脚注并不是标准的正式文本,才导致有的编译器另行对待吧)

呵呵还是想举个例子,比如说indirection operator产生"lvalue"(这里的产生并非指从"lvalue"到"lvalue"),这个例子不知道对不对,请不吝赐教:
  1. #include <stdlib.h>
  2. #include <stdio.h>

  3. char *mystrcp(char *dest, const char *src)
  4. {
  5.         char *temp;
  6.         temp = dest;
  7.         while ((*dest++ = *src++) != '\0')
  8.             ;
  9.        
  10.         return dest;
  11. }

  12. int main(int argc, char *argv[])
  13. {
  14.     char *dest = malloc(100 * sizeof (*dest));
  15.     char *src  = "Hello World";
  16.         
  17.     *mystrcp(dest, src) = 'I';  /********************/
  18.     printf("dest: %s\n", dest);
  19.         
  20.     system("pause");
  21.     return 0;
  22. }
复制代码

这段代码在DEV-CPP4.9.9.2和VC++6.0下试验未出现任何问题。首先函数调用肯定不是"lvalue",这里的*operator将产生一个"lvalue"。
>> 那么是否可以这样理解:
>> "lvalue"必须对应于一块存储空间,并且是对非void 类型的object的reference。

注意标准中说的是“An l-value is an expression with an object type or an incomplete type other than void”。因此,强调的是非 void 的 object type 或 incomplete type,而不是实际存在的 object。我在前面已经说过,左值概念与对象(存储空间)的实际存在与否无关。当然了,if an lvalue does not designate an object when it is evaluated, the behavior is undefined。

您说得很对,但这里我并未说object必须是实际存在的object啊。不过你这一段,更能加深大家的理解。还是很感谢。
>> "rvalue"可以理解为一段存储空间表示的值或一个表达式的值,它强调的是value的本意。

“一段存储空间表示的值”其实就是“对象的值”。获得对象的值的过程我们通常称之为“求值”。对对象求值是一个由左值转化为右值的过程(lvalue-to-rvalue),所以对象的值是右值,但不能说对象是右值。关于 lvalue-to-rvalue 转化在 C99 标准的 6.3.2.1-2 和 -3 一节中叙述。

嗯,这里我觉得可能啰嗦了一点但是并没有歧义,“一段内存空间表示的值”最终还是“值”的意思。好谢谢指明转化的位置。

一个单独的对象也可以作为表达式使用,所以上述这句话可简写为“"rvalue"可以理解为表达式的值”。这也是标准中出现的描述。

对,标准在脚注中说的原文是:
What is sometimes called "rvalue" is in this International Standard described as the "value of an expression".

以上那样说只是我自己的理解:“一段存储空间表示的值”是我想强调一类由"lvalue" 转化来的"rvalue"吧。
>> "lvalue"可以作为"rvalue",但不是所有的都可以。

lvalue-to-rvalue 转化是标准规定的。lvalue 转换为 rvalue 实际上就是对左值求值的过程。存在不能对其求值的左值吗?
你在上面举的例子也说明不了你这句话的正确性。因为在 C 中,所有的函数调用产生的都是右值,而不是你说的左值(在 C++ 中,除了返回引用的函数调用是左值外,其它的函数调用结果也都是右值)。

嗯,首先承认错误,任何函数调用不是"lvalue",还有标准也明确给出函数名(function designator)不是左值,因为function designator 是function type而不是object type,所以它也不是"lvalue"。但有一点是:不是"lvalue"就是"rvalue"吗?
那么这个例子中:
  1. void foo(void)
  2. {
  3.    /*do something...*/
  4. }
  5. int i;
  6. i = foo();          /*function call是表达式,函数调用foo()不是"lvalue"但在这里也不能作为"rvalue"*/
复制代码

论坛徽章:
0
9 [报告]
发表于 2006-05-05 16:27 |只看该作者
>> 比如说indirection operator产生"lvalue"(这里的产生并非指从"lvalue"到"lvalue")
>> *mystrcp(dest, src)

解引用的 * 运算符要求其操作数仅是一个右值,因此 *p(p是左值对象,可转化为右值)以及 *f()(f()是右值)的结果都是左值。

>> i = foo();          /*function call是表达式,函数调用foo()不是"lvalue"但在这里也不能作为"rvalue"*/

之所以不能这样赋值是因为类型的问题:函数 foo() 的返回值为 void,而 i 的类型为 int。如果单独调用函数的话 foo();,那函数返回的就是一个右值。

论坛徽章:
0
10 [报告]
发表于 2006-05-05 19:15 |只看该作者
非常高兴和您继续探讨!

>> i = foo();          /*function call是表达式,函数调用foo()不是"lvalue"但在这里也不能作为"rvalue"*/

之所以不能这样赋值是因为类型的问题:函数 foo() 的返回值为 void,而 i 的类型为 int。如果单独调用函数的话 foo();,那函数返回的就是一个右值。


这里我觉得其实质原因不是类型的问题,而是本身void类型的值不能作为"rvalue"来使用。

标准中没有提到可以用void来定义变量,所以
  1. void num;
  2. 这样的定义是非法的。
复制代码

the void type的用法主要有:
《C:A Reference Manual 》fifth edition, P168 5.9 The void Type:
(1) as the return type of a function, signifying that the function returns no value;
(2) in a cast expression when it is desired to explicitly discard a value;
(3) to form the type void *, a "universal" data pointer; and
(4) in place of the parameter list in afunction declarator to indicate that the function takes no arguments.

如果仅仅是类型的问题话,那么我引入cast operator是否就应改产生"rvalue"呢?但是
void foo(void)
{
   /*do something...*/
}

int i;
i = (int)foo();

这样也是不行的,而有另外一个例子:
  1. int num;
  2. num = (void)100;
复制代码

(void)100是合法的,但是这样将他作为"rvalue"就是非法的。

然后,"rvalue"的定义为"the value of an expression",但是
void as the return type of a function, signifying that the function returns no value;

所以我觉得单独的返回值为void的函数调用也不是"rvalue",而是"not a "lvalue" "。

[ 本帖最后由 kernelxu 于 2006-5-5 19:17 编辑 ]
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP