左值和右值

C语言中的左值和右值

CC++ 中,我们经常会碰到两个概念, lvaluervalue 。甚至在编译程序的时候,也会碰到一些关于 lvaluervalue 的错误。

ISO IEC 的标准文档中,对这两个概念并没有详细说明,特别是 rvalue ,只在一个地方提了一下。

在 IBM 的 XL C/C++ V8.0 for AIX 的标准文档中是这样来规定 lvalue

An object is a region of storage that can be examined and stored into. An lvalue is an expression that refers to such an object.

左值就是始终可以被解析为相应对象的地址,除非该对象是位字段或者是使用了register关键字声明的变量。

产生左值的运算符包括:

  • []
  • *

示例:

其中最后两个不是左值,因为

也就是说 lvalue 是一个表达式,且指向一个可读写的存储区域。而 rvalue ,可以为任何表达式。 lvaluervalue 的取名,是来自于一个赋值表达式中的。对于一个合法的赋值表达式:

1
L = R;

我们就说 LlvalueRrvaluelvalue 必须是可写的,且 lvalue 可以转换为 rvalue ,但是 rvalue 却不一定能转换成 lvalue 。比如:

1
2
int x;
x=1;

这个赋值是合法的,因为 x 是一个 lvalue ,但是:

1
1=x;

这种写法明显是错误的,因为 1 只是一个 rvalue

左值和右值不仅仅存在于变量中,在表达式中,也是存在的。因为在 lvaluervalue 的定义中,它们的本质就是一个表达式。不是所有的表达式都会产生 lvalue 的。比如:

1
(m+1)=n;

这是一个错误的赋值语句,因为加法产生的结果是 rvalue 。对于一些一元运算符,有的会产生 lvalue ,比如:

1
2
3
int m,*p;
p=&m;
&m=p; //error

明显,最后一个是错误的,因为 & 产生的是 rvalue ,但是这种写法:

却是正确的,因为 * 产生的是 lvalue 。再举一个复杂的例子:

1
2
((condition) ? a : b) = complicated_expression; //error
*((condition) ? &a : &b) = complicated_expression; //correct

对于很多类型转换,你可能会这么用:

1
2
char *p;
((int *)p)++;

你的目的是想让指针 p 跳过几个 int 大小的位置,但是这个代码却不能正常执行。 原因有两个,一个是类型转换操作最后产生的是一个 rvalue ,也就无法用于 ++ 。另外一个,类型转换只是做一个类型转换, 并不会让编译器也随着类型改变相应的大小,也就是说最后, ++ 操作所加的大小并不是一个 int 的大小,而是一个 char 的大小。 所以你应该这样做:

1
p = (char *)((int *)p + 1);

参考资料


左值和右值
https://ysc2.github.io/ysc2.github.io/2024/04/19/左值和右值/
作者
Ysc
发布于
2024年4月19日
许可协议