CG@CPPBLOG

/*=========================================*/
随笔 - 76, 文章 - 39, 评论 - 137, 引用 - 0
数据加载中……

数组类型、函数类型到左值和右值的转换

1、左值和右值

表达式的左值是它的地址,右值是该地址所存储的内容。比如下面代码:

= x + 1;

这两个 x 有什么不同呢?第一个表达式 x 表示的是它的左值,而第二个表达式 x 表示的是它的右值。一个表达式能不能放到 赋值操作符 的左边,取决于这个表达式有没有左值,同样的,一个表达式能不能放到 赋值操作符 的右边,取决于它有没有右值。

一个表达式究竟是取左值还是右值,需要结合上下文,大多数表达式同时具有左值和右值。

2、指针类型、数组类型和函数类型

C++是一种强类型语言,类型在程序中的非常重要,每个变量和表达式都有一个确定的类型,类型匹配和类型转换也是C++语言中的重要部分。除了内建的一些类型和用户自定义类型(包括类类型),下面重点说一下指针类型、数组类型和函数类型。

T* a; 

声明一个变量a,它的类型是T*(指向类型T的指针类型)。

T arr[100];

声明一个变量arr,它的类型是 T[100](一维,维长100,元素类型为T的数组)。

T f(void){/*  . . . */}

声明一个变量f,它的类型是 T(void)(返回值为T,参数为void的函数)

特别要强调的是,arr 的类型和 f 的类型都不是指针。这两个类型的表达式没有右值。

3、&、* 操作符

* 操作符应用与左值表达式,以表达式左值为地址,取出表达式类型的值,作为右值返回。
& 操作符应用于左值表达式,返回表达式的左值。

注意*、& 操作符的操作数必须拥有左值。

4、左值到右值的转换

C++ 标准转换包含 左值到右值的转换。因为数组类型和函数类型的表达式没有右值,所以特别这里要说明数组类型和函数类型到右值的转换。比如上文所说 arr ,当它作为赋值操作符的操作数时,它需要转换为 T* 类型的指针(注意类型是指针!!),其值等于第一个元素的地址。而上文中所说的 f,当它作为赋值操作符的操作数时,它需要转换为 T(*)(void) 的指针(注意类型是指针!!),它指向f的地址。

5、对数组类型或者函数类型施加&、*操作符

&arr、 *arr、 &f、 *f 这些表达式都是什么呢?打开RTTI,在VC里运行下面代码:

 1 #include "stdafx.h"
 2 #include <iostream>
 3 using namespace std;
 4 
 5 int func()
 6 {
 7         int i = 2;
 8         return i;
 9 }
10 
11 int _tmain(int argc, _TCHAR* argv[])
12 {
13         int arr[100= {0,1,2,3};
14 
15         cout<<typeid(func).name()<<endl;
16         cout<<typeid(*func).name()<<endl;
17         cout<<typeid(&func).name()<<endl;
18         cout<<endl;
19         cout<<typeid(arr).name()<<endl;
20         cout<<typeid(*arr).name()<<endl;
21         cout<<typeid(&arr).name()<<endl;
22         cout<<typeid(int*).name()<<endl;        
23 
24         getchar();
25         return 0;
26 }

运行结果如下:

int __cdecl(void)
int __cdecl(void)
int (__cdecl*)(void)

int 
[100]
int
int (*)
[100]
int *

可以看出 *func 和 func 类型相同,是函数类型,而&func 是指向函数的指针。arr 是数组类型,*arr 是 T 类型, &arr 是 指向数组的指针(这个比较费解)

因为*func 和 func 是等价的,所以可以这样调用 func:

(***********************func)();

如果要用 &func,必须这样(注意第一个一定是 *):

(*&*&*&*&*&*&*&*&*&*&*&*&*&*&*&func)();

至于 arr、*arr、&arr 因为类型不同,不可混用,当然用来 memset 的话,表达式 arr 和 &arr 的值都为第一个元素的地址,最终都被转换为 void* 。




posted on 2008-04-07 22:50 cuigang 阅读(4415) 评论(28)  编辑 收藏 引用 所属分类: C/C++

评论

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

几个错误:
左值:返回一个可修改对象的表达式称为左值
右值:返回一个不可修改对象的表达式称为右值
*操作符返回的对象可能是左值也可能是右值
&操作符可用于左值和右值
*操作符的操作数可以是右值(请参考“映射设备寄存器到内存”http://blog.chinaunix.net/u/12783/showart_385250.html
arr是T [100],那么&arr就是T (*)[100],没什么费解的
2008-04-08 10:27 | raof01

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

给楼上的纠两个错:

“左值:返回一个可修改对象的表达式称为左值”

左值未必都是可修改的,例如数组名就是不可修改的左值,可参见Expert C Programming 4.3.1

“右值:返回一个不可修改对象的表达式称为右值”

右值未必都是不可修改的;在C++中,built-in的右值总是不可修改的,但是对于user defined type 的右值,在某些情况下是允许被修改;例如对于函数返回的临时对象,调用non-const member function是合法的

2008-04-08 18:09 | 啸天猪

# re: 数组类型、函数类型到左值和右值的转换[未登录]  回复  更多评论   

@raof01

1、我并非说我的左值右值描述是定义,但是你的依靠是否可修改的判断却反而是错的,一个const变量x,表达式 x 仍然有右值,但是不代表 x 可修改。关于左值和右值,ISO C++ 98版标准这样说:

An lvalue for an object is necessary in order to modify the object except that an rvalue of class type can also be used to modify its referent under certain circumstances.

而《C++ primer》4th 这样说:

存储数据值的那块内存的地址,它有时被称为变量的左值lvalue,读作ell-value,我们也可认为左值的意思是位置值(location value)。

左值代表了一个可被程序寻址的对象,可以从该对象读取一个值,除非该对象被声明为const,否则它的值也可以被修改。相对来说,右值只是一个表达式,它表示了一个值,或一个引用了临时对象的表达式,用户不能寻址该对象,也不能改变它的值。

2、我承认对于 * 操作符用于左值的描述有问题,应该说 解引用操作符 用于指针类型,但 & 操作符的确只能应用于有左值的表达式,看C++ 标准这样说:

The result of the unary & operator is a pointer to its operand. The operand shall be and lvalue or a qualified-id.

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

至于你链接的文章,请恕我未能洞悉,不知里面哪里提到 解引用操作符可以用于右值,或者隐含此意味。

虽然标准中没有说 解引用操作符 到底是用在左值还是右值表达式上,但是间接寻址(indirection)反而说明了你的观点,它是将指针变量的右值作为地址来访问指向物,类似的有
‘.’,‘->’运算符。

3、我说指向数组的指针费解,不是说我不理解,只是说大多数人实际中难以见到,不好理解,如果你可以理解,你可以说说它和多维数组 arr[][] 的 指针 arr[2] 有什么区别。

2008-04-08 21:44 | cuigang

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

哪个是对的,不要误解人
2008-04-09 16:14 | 我爱你

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@cuigang
想太多反而会把问题搞复杂。

C++ Primer 4th: ch2.3.1
左值可以出现在赋值语句(等号)的左边或右边。
右值只能出现在赋值(等号)的右边,不能出现在赋值语句的左边。

无论在哪本C/C++书籍中,对左值、右值的描述最基本的就是以上两句话。其他关于左值、右值话题的讨论、延伸都脱离不了以上两句话。

至于左值是否是地址,右值是否为数据本身——
对于 x = x + 1;
假设 x 为 int 类型,则编译后汇编语句应该为:
mov eax, DWORD PTR x_$[ebp]
inc eax
mov DWORD PTR x_$[ebp], eax

很明显 ebp 是地址而不是具体数据,而右边的 x 是作为右值出现的。也就是说除非字面值表达式(1, 'a'等),其他表达式不管左值右值,编译器都有可能以地址处理。

至于右值是否能被寻址——
const int iX = 100;
cout << &iX << endl;
是合法的。从加上const关键字那一刻起,iX就只能作为右值出现。
试图用强悍的指针来修改iX的值:
int* pX = &iX; //无法从“const int *”转换为“int *”
*pX = 101;

不是纠错,只是讨论。
2008-04-09 23:20 | ww

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@啸天猪
看起来比较抽象,能详细举例说明吗?
2008-04-09 23:22 | iwong

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

我觉得不严谨的理解,左值就是程序员有权知道其地址的object;右值则是虽然知道这个object的存在,但是它的地址对程序员是无意义的。

至于左值/右值的准确含义,还是参考标准吧;书毕竟是面向大众的,通常不会像标准那样给出完整、严谨、天书般的定义

@iwong

建议参考水木社区Cplusplus版的FAQ,解释的很详细

http://www.newsmth.net/bbscon.php?bid=335&id=179727

2008-04-10 00:38 | 啸天猪

# re: 数组类型、函数类型到左值和右值的转换[未登录]  回复  更多评论   

@啸天猪

你提供的链接解释很充分,希望你自己也能自己看一下
2008-04-10 22:08 | cuigang

# re: 数组类型、函数类型到左值和右值的转换[未登录]  回复  更多评论   

@ww

啸天猪的链接你看一下,&只能对左值表达式取址。我就不用再废话了。
2008-04-10 22:20 | cuigang

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

向前辈致敬~
2008-04-11 22:58 | elent

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@啸天猪
@cuigang

http://www.newsmth.net/bbscon.php?bid=335&id=179727
这篇文章把能够用&x取得地址的x都视为左值,于是"Hello world!"这样的字面值字符串和const常量都成为了左值。

可是:
const int i = 1;
i++;
"Hello world!"++;

在VC中编译时对后两行都会提示“++需要左值”。

如果相信这篇文章的话,那么这个结果是很令人费解的。
2008-04-12 14:30 | iwong

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@iwong

你需要区分概念:左值分为两类──可修改的左值和不可修改的左值。通常提到的左值都指的是前者──例如上面VC的提示



3.10 Lvalues and rvalues [basic.lval]

Every expression is either an lvalue or an rvalue.

If an expression can be used to modify the object to which it refers, the expression is called modifiable. A
program that attempts to modify an object through a nonmodifiable lvalue or rvalue expression is ill-formed.


我自己的理解:

rvalue意味着object的地址对程序员无意义──它的地址对编译器有用,对程序员是没用的

modifibale lvalue 意味着object的地址对程序员有意义,且可以用这个lvalue对object进行修改

unmodifiable lvalue意味着object的地址对程序员有意义,但若通过这个lvalue对object进行修改,也是无意义的(编译报错或者运行出错)

i++出错的原因在于”const int i=1“中的i并不是可修改的左值

而标准规定,前缀/后缀增量的操作对象必须是modifiable lvalue

”5.2.6 Increment and decrement [expr.post.incr]
1 The value obtained by applying a postfix ++ is the value that the operand had before applying the operator.The operand shall be a modifiable lvalue.

5.3.2 Increment and decrement [expr.pre.incr]
1 The operand of prefix ++ is modified by adding 1, or set to true if it is bool (this use is deprecated). The operand shall be a modifiable lvalue. “


”Hello world!“++出错的原因在于这里发生了string literal 到 pointer的自动转换,”hello wolrd“转换为类型为char *的rvalue。对于一个built-in type的rvalue执行增量操作,肯定是不合法的


4.2 Array-to-pointer conversion

2 A string literal (2.13.4) that is not a wide string literal can be converted to an rvalue of type “pointer to char”;
2008-04-12 17:00 | 啸天猪

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@啸天猪
"左值未必都是可修改的,例如数组名就是不可修改的左值":数组名不是左值。注意我说的话:*返回*可修改对象……数组名是一个* const,不是左值。简言之:int a[10];可以有int * p = a; a[10] = x;但不能有a = p;
“但是对于user defined type 的右值,在某些情况下是允许被修改;例如对于函数返回的临时对象,调用non-const member function是合法的”:临时对象不一定是右值。*返回*不可修改对象……
讨论就是讨论,没有骂人的,看来大家都是好人。哈哈,这里不错。
2008-04-17 13:07 | raof01

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

sorry,上面有个错误:a[10] = x; 改为 a[0] = x;
2008-04-17 13:08 | raof01

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@cuigang
请注意我的用词(有咬文嚼字之嫌,见谅):返回、对象、表达式。对象包括很多:指针、字面常量、临时对象……我认为我们之间的分歧在于你把我说的对象细分了。

“但 & 操作符的确只能应用于有左值的表达式”——你是对的,我的错误。

“至于你链接的文章,请恕我未能洞悉,不知里面哪里提到 解引用操作符可以用于右值,或者隐含此意味。”——实际我想说明的就是“但是间接寻址(indirection)反而说明了你的观点,它是将指针变量的右值作为地址来访问指向物,类似的有‘.’,‘->’运算符。”,因为我把指针当作对象的一种。

“我说指向数组的指针费解,不是说我不理解,只是说大多数人实际中难以见到,不好理解,如果你可以理解,你可以说说它和多维数组 arr[][] 的 指针 arr[2] 有什么区别。”我相信你理解了,否则也不会写出这么有深度的文章。T arr[]中arr的类型是T[],&arr的类型就是T(*)[];但是,T arr[][]中arr的类型是T * arr[],&arr的类型是T**——最后这点是因为C中二位数组是线性存储的,不是我们所理解的二维矩阵。

——向你的认真致敬!呵呵
2008-04-17 13:42 | raof01

# re: 数组类型、函数类型到左值和右值的转换[未登录]  回复  更多评论   

@raof01

跟你先前的评论比较,想必你最近也深入研究了这个问题,虽然言语之中仍然为自己辩护,不过大家观点毕竟更加接近了,其实我们对于这个问题的理解可能都有少许偏差,但讨论之后一定更加接近正确了。

谢谢你捧场。
2008-04-17 13:54 | cuigang

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@cuigang
你举的例子x=x+1;其中x无论是在左边还是右边都是左值,表达式"x+1"才是右值。
2008-04-17 14:17 | raof01

# re: 数组类型、函数类型到左值和右值的转换[未登录]  回复  更多评论   

@raof01

你要说 x + 1 中的 x 是左值, 我真的很无奈。
2008-04-17 23:18 | cuigang

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@cuigang
你别无奈啊,给个理由先。
我的依据是:左/右值指的是表达式。单纯就x这个表达式来说,它是个左值,无论在=左边还是右边。
2008-04-18 10:05 | raof01

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@cuigang
我始终不同意这句话:“表达式的左值是它的地址,右值是该地址所存储的内容。”因为无论地址还是内容,都是对象。而且按照你这句话,x = x + 1;第一个x是个地址,第二个x是其内容,假设x是T类型,那么上述表达式就应该理解为=左边是个地址,也就是T*,对吧?矛盾就来了,你把T类型的值赋给了T*。
“虽然言语之中仍然为自己辩护,不过大家观点毕竟更加接近了”——刚才又仔细看了一遍你的文章,我坚持我第一个评论里的观点,除了“&操作符能作用于左值和右值”这个错误。
我认为:因为你的第一个观点是全文的核心,所以我们之间还存在巨大的分歧。
2008-04-18 10:19 | raof01

# re: 数组类型、函数类型到左值和右值的转换[未登录]  回复  更多评论   

@raof01

本来我以为我们的观点接近了,但是看到你说 x + 1 的 x 是左值, 我就知道我们根本没有持相同观点.

现在,我解释一下为什么 x + 1 中的 x 是右值, 首先要明确对于一个表达式,究竟是左值还是右值, 取决于它在表达式中的位置, 它并不是一定为左值或者右值,这一点你可以去看c++标准,如果你对这点都不认同,我们其实已经无法再继续讨论了。

其次,对于一个表达式,它都会有一个对象和它对应,无论它是一个变量,还是能产生一个临时对象,或者是一个字面常量(自演算表达式). 这一点你应该是同意的. 那么一个对象, 其实是一个映射关系, 它实际存在在内存中(或者寄存器中), 它一定有位置信息和值信息(当然得到值还需要类型信息), 符号表是在编译时转换用的,运行时虽然没有这个表存在,但这个映射关系是存在的. 变量就表示了这层关系.

表达式 x 的运算结果就是 x , 但是这个运算结果是 x 本身吗? 答案是否定的, 众所周知, 内存单元是无法做算术运算的, x 的值 需要被取到 寄存器中, 然后才能加 1 . 如果你看汇编, 你会看到 一个 类似 lea 的指令, 所以 x 代表了它的值(先不管是左值还是右值).

对于一个非引用类型的a, a=b , 是会让 a 和 b 变成同一个对象吗? 不是, 只是让 a 和 b 的值相等.

象basic那样,给赋值加上一个let, let a = b, 这个a 是符号表中的那个a吗? 一定. b是符号表中那个b吗? 不必. 其实编译器也是这么认为的, 如果 b 的值 已经被取到寄存器,它不会再取一次. 就象读起来那样, 让a等于b, 只要跟b相等就好, b的位置在哪里无所谓, 但a在哪里就很重要了,否则就会给错人.

说的很乱,可能看不懂,其实回到本来的字面,左值就是放到等号左边的值,右值就是放到右边的值. 从 a = b 可以看出, 左值表示位置, 右值表示内容(也就是值).

再回到 x + 1, x + 1 会改变 x 吗? 不会, 那么不一定需要x ,更不需要 x 的位置, 一个和 x 相等的对象也行, 所以, 这里的x 是右值.

对于非命令式语言, 就没有左右值的烦恼, 命令式语言着实的麻烦. 怎么做的,解释起来都费劲.

呵呵.

2008-04-18 11:01 | cuigang

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@cuigang
仔细查阅了一些资料,发现对于lvalue和rvlaue的说明各有不同。找了两个比较经典的说法,共同参考一下。下面是在comp.lang.c++.moderated上找到的(为避免断章取义,贴全文):
The simplest explanation I know is that an rvalue, in current C++, is an *expression* that produces a value, e.g. 42, and an lvalue is an *expression* that just refers to some existing object or function[1]. Hence the acronym "locator value"[2], which however is still misleading because an lvalue is simply not a value. Better would be *rexpression* versus *lexpression*, and even better, just value versus reference (unfortunately the term "reference" is already hijacked).

"object" versus "value": in C++ the basic definition of "object" is a region of storage, i.e. anything that actually occupies storage, while a pure value such as 42 doesn't necessarily occupy any storage. A value (an rvalue) can be an object, though. If it is, then it's a temporary object.

C++ allows you to call member functions on a class type rvalue, i.e. on a temporary class type object.

The ability to call member functions on (class type) rvalues, together with C++'s treatment of assignment operator as a member function (automatically generated if needed and none defined), means that you can assign to class type rvalues. However, C++ does not regard that rvalue to be *modifiable*: to be
well-defined, the assignment must do something else than modifiying the object assigned to (and typically we indicate that by declaring the assignment operator const). §3.10/14 "A program that attempts to modify an object through a nonmodifyable lvalue or rvalue expression is ill-formed".

Fine point: §3.10/14 means that rvalue-ness transfers to parts of an rvalue, which are thereby also considered rvalues. However, at least the two compilers I use don't seem to know that rule. Comeau Online does.

I'm not sure if the explanation above holds up with respect to C++0x rvalue references. rvalue versus lvalue got a lot more complicated in C++ than in original C. Perhaps it's even more complicated in C++0x, I don't know. :-)

Cheers, & hth.,
- Alf

Notes:
[1] Amazingly that's also the general definition the standard starts out with, before going into details: §3.10/2 "An lvalue refers to an object or function", however, it would have helped much if the word "expression" was explicitly included there, not just by being mentioned in the preceding paragraph.
[2] Unfortunately the C++ standard doesn't use or even mention the acronym "locator value". This acronym comes from the current C standard. And ironically the C++ standard mentions, by contextual placement, the original old C acronym of "left hand value", in §3.10/4: "built-in assignment operators all expect their left hand operands to be lvalues".

Programming Cpp的解释:
http://etutorials.org/Programming/Programming+Cpp/Chapter+3.+Expressions/3.1+Lvalues+and+Rvalues/

你的解释更接近真相,呵呵,不过我还是不同意地址一说,用引用会比较好一些。要不你整理一下,就lvalue和rvalue专门写一篇?

看来俺还得多看看标准啊。
我的C++标准丢了,给发一个?raof01@gmail.com。
2008-04-18 14:13 | raof01

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@raof01

C++03 标准
http://www.newsmth.net/bbsanc.php?p=335-15-1-4-2

我觉得有必要再罗嗦几句

关于数组名是不是左值,C++标准中我没找到具体条目,不过在C中,《Expert C Programming》4.3.1 确实提到”Hence,arrayname is a lvalue but not modifiable lvalue“

我把左值理解为”object with a name“,数组名在此范围之内



右值这个概念,本质上是与可修改没有关联的

关于rvalue,常见的误解是”不可修改“。并不是这样的,右值并不是根据“不可修改”这个性质来定义的

首先,函数的返回值一定是右值,除非返回的是引用

C++03 3.10.5
“The result of calling a function that does not return a reference is an rvalue.”

其次,右值是否能修改要看其是否是const-qualified

C++03 3.10.9
“Class rvalues can have cv-qualified types; non-class rvalues always have cv-unqualified types.”

也就是说,对于UDT,右值是否可修改要看这个右值是否被限定为const

举个例子

class A
{
public:
void foo() {}
void fooc() const {}
};

A func() { return A();}
const A func2() {return A();}

func().foo(); //Ok
func().fooc(); //Ok
func2().foo(); //Error,对const rvalue调用non-const member function
func2().fooc(); //OK

func和func2的区别在于返回值(rvalue)是否限定为const──对于UDT,有没有这个const限定的结果显然不同

至于“临时对象不一定是右值”,我想不出什么情况下临时对象不是右值
2008-04-19 06:23 | 啸天猪

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@啸天猪
数组名是lvalue。但lvalue不是object with name,而是内存区域的引用。这个问题我更倾向于cuigang的说法,虽然他用的是地址。
临时对象是右值,呵呵。
我对于lvalue和rvalue的理解基本上是错误的,呵呵,多谢cuigang和猪了。
2008-04-19 12:35 | raof01

# re: 数组类型、函数类型到左值和右值的转换[未登录]  回复  更多评论   

@raof01

善哉善哉
2008-04-19 21:26 | cuigang

# re: 数组类型、函数类型到左值和右值的转换[未登录]  回复  更多评论   

如果:
“* 操作符应用与左值表达式,以表达式左值为地址,取出表达式类型的值,作为右值返回。”

那么:
int foo(int *p)
{
return (*p)++; // Or *p += 1;
}
如何解释?
2008-07-28 14:46 | raof01

# re: 数组类型、函数类型到左值和右值的转换[未登录]  回复  更多评论   

表达式的lvalue是不可修改的,可修改的只有其rvalue。因为当一个表达式具有lvalue时,它在内存中的位置是固定的,也就是lvalue是固定的。所谓修改表达式的值只是通过其lvalue来修改其rvalue。
比如
int i = 10;
假定i的地址为0x1000,那么i的lvalue是0x1000,rvalue是10,
i = 100;
则表示0x1000存放的内容也就是rvalue被修改成100,而不是i的lvalue——0x1000被修改成别的值,其内容为100。
2008-07-28 15:21 | raof01

# re: 数组类型、函数类型到左值和右值的转换  回复  更多评论   

@raof01

这句话说得确实有问题,应该是把表达式的值(右值)作为地址(但不是表达式的左值),取出内容。

谢谢指正。
2008-08-03 13:45 | cuigang

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理