【C语言深入】细聊C语言中的“左值”和“右值”
一、C语言中的“左值”和“右值”
1、左值
左值就是那些可以出现在赋值符号左边的东西,它标识了一个可以存储结果值的地点。
程序在编译时,编译器会为每个变量分配一个地址(左值),这个地址在编译是即可知。
也就是说,左值在编译时即可知,左值标志存储结果的一个地方,也可以理解为左值就是一块空间。
2、右值
右值就是那些可以出现在赋值符号右边的东西,它必须具有一个特定的值。
与左值相反,变量中存储的那个值(右值),只有在运行时才可知,且只有要用到变量中存储的值时,编译器才会发出指令从指定的地址读入变量的值,并将它存于寄存器中。
也就是说,右值就是一个数字或一个字面值或一个常量,它并不标识任何位置。
左值和右值的区别
对于大多数表达式(几乎全部),他们都有右值的属性,因为每个变量所标识的地址空间中都会存储一个值,只要表达式计算不出错,都能计算出一个特定的值。
但并不是所有的表达式都具有左值的属性,例如现在有以下变量和表达式:
int a = 3;
int b = 5;
a = b + 25;
b + 25 = a;
其中表达式a是一个左值,因为它标识一个特定的位置(a变量的地址),但b + 25就不是一个左值,因为它并未标识一个特定的位置。
当然,计算机在计算b + 25的时候,一定是把它的结果存储与内存的某个位置,但这对于程序员来说是无法预测的,程序员也不能保证这个表达式的值下一次还会存储在同一个地方。
故b + 25不是一个左值。
二、与“位置”相关的操作符
1、取地址操作符&
之前已经就取地址操作符的功能论述过此操作符了,但今天是就此操作符产生的是左值还是右值再讨论一番。
我们都知道取地址操作符&的作用是取出一个变量的地址,但是它产生的结果是个左值还是右值呢?
可能我在上面就已经说清楚了,它产生的结果是一个右值,因为如果对一个变量取地址,这个地址应该存储在内存的什么位置呢,这是为清晰定义的,用图来解释就是如下图:
&ch就相当于取出了ch的地址,存储在了内存中的某个位置,但这个位置是未知的。
而且如今的编译器都是动态的分配内存空间的,也就是说程序这次执行和下一次执行时,同一个变量的地址是不同的。
所以,取地址操作符产生的结果是一个右值,也可以说是一个地址常量或指针常量。
2、间接访问操作符*
积案解引用操作符*是作用域指针的,它的功能通过指针访问指针中存储的地址,它产生的结果是一个左值,但它所产生的这个“左值”既可以作为左值使用也可以作为右值使用。
这可能有点儿难以理解,其实也并没这么难,只要记住下面这个结论,那么对于一些复杂的指针运算就能清楚的理解了:
在类型相同的情况下,对指针解引用,代表指针所指向的目标。
我们现在看一个实例:
int a = 0;
int *p = &a;
上面的结论的意思就是,对于*p我们可以直接认为它等价于a,置于用的是a的左值还是右值,那要视情况而定。
也可以总结为:
对p解引用,是取出p中的地址,访问改地址指向的内存单元的空间或者内容(其实根据指针变量访问,本质是一种间接寻址的方式)。
三、指针表达式作为左值和右值
说到地址和位置,就一定避不开指针,有些人对于一些复杂的指针表达式,很难分清它到底表达的意义是什么?(其结果是左值还是右值?)。
今天我就给大家好好的来分析一下那些令人眼花缭乱的指针表达式到底是个什么东西:
1、当指针单独出现在表达式中时
当指针单独出现在表达式中时候,它既可以作为左值也可以作为右值使用,作为左值时它的结果表示指针所在的存储位置,右值就是指针的值(也就是一个地址常量)。
假设现在有一个字符型变量和字符型指针:
char c = 'a';
char *p = &c;
用画图的方式来理解,就如下图:
上图中黑色的方框表示的是位置(空间),黑的的椭圆表示的是内容。
所以上图所表达的意思是:p作为左值时所表示的就是变量p所在的位置,作为右值的时候表示的是变量p中的内容(c的地址)
2、当对指针解引用时
对指针解引用时,它产生的结果也是既可以作为左值也可以作为右值,作为左值是它表示的是指针中存储的地址的那块空间,作为右值时它表示的是指针中存储的地址的那块空间中的内容。
用图解的方式表示,就如下图:
其实这里就已经可以用到我们上面所说的结论了,就是*p等价于c,只不过用的是c的左值还是右值要看具体情况。
3、当对指针取地址时
当对指针取地址时,它是一个合法的右值,但却不是一个合法的左值,原因我想上面已经说过了。
当它作为一个右值时,它表示的是变量cp的地址(是个地址常量),但这个地址的存储位置是未清晰定义的,所以它不是一个合法的左值。
用图来解释的话,就如下图:
4、当对指针加减整数时
当对指针加减整数时它的右值表示的是它存储的地址跳过一个类型长度后的地址(是个是个地址常量),但它不是一个合法的左值,原因我想应该很清晰了,因为保存这个地址常量的空间并未被清晰定义。
用图来解释,就如下图:
这里需要注意的是任何变量在进行计算的时候,用的都是它的右值,所以表达时所产生的结果都是一个常量(也可以说是一个副本),所以对于指针的自加或自减(前置和后置都一样),其实分析方式都和上图一样,所以都是合法的右值,而不是一个合法的左值。
5、当对“指针加减整数”解引用时
当对“指针加减整数”解引用时,它的结果既是一个合法的右值也是一个合法的左值,没错!你没有听错,它是一个合法的左值,虽然“指针加减整数”本身不是一个合法的左值,但加上了解引用操作符之后就能将它变成一个合法的左值。间接引用是少数几个其结果为做值的操作符之一。
当它作为右值时,表示的是指针中存放的地址跳过一个类型长度后的地址中的内容,当它作为左值时,表示的是指针中存放的地址跳过一个类型长度后的地址的空间。
用文字描述实在太费劲,还是看图吧:
这里在计算时其实是先拿出p中保存的地址(常量)与1相加,从而产生了一个“副本”(如图中的虚线椭圆所示),在通过这个“副本”的值找到对应地址的空间或值。
但千万要注意的是,虽然*(p + 1)是一个合法的左值,但并不代表它是一个合法的访问方式,因为p + 1的位置是并没又被分配的,所以对它的访问是非法的。
既然我们已经懂得了这个,那我们理解 ++p和p++也就水到渠成了,他们都是合法的左值和右值。只不过*++p这种访问方式是非法的。