为什么根据标准a++ = b;
是不允许的,而c[i++] = d;
是允许的?
(显然,a++ = b;
是不好的样式,但这是一个关于仔细阅读C语言标准的问题。
以下是强制性的最小示例:
#include <stdio.h>
int main(void)
{
int a = 10;
int b = 20;
int i = 1;
int c[] = {30, 40};
int d = 50;
a++ = b; // error
c[i++] = d;
printf("%d\n", a); // [21]
printf("%d\n", b); // 20
printf("%d\n", i); // 2
printf("%d\n", c[0]); // 30
printf("%d\n", c[1]); // 50
printf("%d\n", d); // 50
return 0;
}
GCC在使用-std=c90
或-std-c17
编译时会发出以下错误:
error: lvalue required as left operand of assignment
根据K&R(2 e)、A7.3.4和A7.4.1
结果[后缀/前缀++
或--
]不是左值。a++
被认为不是一个左值,但是从什么措辞中可以明确地得出c[i++]
* 是一个左值?转向C11标准,我找不到任何关于这两个的规定。
不管怎样:如果不是不允许,我会将a++ = b;
解释为a = b; a = a+1;
。
编辑:
有些人(理直气壮地)问为什么人们会在语义上假设像a++ = b;
这样的语句是有意义的。
我经常尝试把复杂的语法结构转换成等价但更简单的东西。它们是一个语法灾难:它们可以深深地嵌入在语句中,但具有必须在语句之前或之后执行的效果。)我通常会假设任何形式的非病理性语句
- 语句(
++w
,--x
,y++
,z--
)*
相当于w += 1;
x -= 1;
- 语句(
w
,x
,y
,z
)*
y += 1;
z -= 1;
- 其中语句前和语句后的赋值以实现定义的方式排序 *。当然问题是什么算作“非病态”(或者我们是否应该将其定义为“前增量和后增量之间的顺序无关紧要的情况”),但是,暂时将这种关注放在一边,对于程序员来说,假设递增/递减前和后表达式在语法上等同于它们在移除这些运算符后的对应形式并非不合理。
说“这样的运算符剥夺了它们的参数表达式的 * 左值 * 质量”是完全有效的,并且确实回答了我的问题,* 但是 *:
- 如果一个人的思维中没有这个假设,其他的解释(比如我上面写的)是可以想象的(也就是说,从语言设计的Angular 来看(在我看来),前/后递增/递减表达式失去它们的 * 左值 * 质量不是语法上的必要性。
- 对我来说,引用K&R的措辞(“后缀/前缀
++
或--
的结果不是左值。”)似乎只是为了禁止像a++ = b;
这样的赋值。
5条答案
按热度按时间s6fujrry1#
c[i++]
是左值?*c[i]
定义为*(c+i)
6.5.2.1.2后缀表达式后接方括号中的表达式
[]
是数组对象元素的下标表示。下标运算符[]
的定义是E1[E2]
等同于(*((E1)+(E2)))
。[...]这给予了我们一个左值。
6.5.3.2.4一元
*
运算符表示间接,如果操作数指向一个函数,则结果是一个函数指示符;如果它指向一个对象,则结果是一个指定该对象的左值。你能修改
5+1
的结果吗?当然不能。(5+1) = 3;
完全没有意义。5+1
的结果不是可以保存值的东西。你不能在以后获取你分配给它的值。分配给5+1
的结果完全是胡说八道。a++
的结果是a
的旧值。它不可能是a
本身,因为a
不再有正确的值。它只是一个短暂的值,就像5+1
的结果一样。它不是一个可以保存值的东西。给它赋值绝对没有意义。用技术术语来说,
a++
不是一个可修改的左值,因为它不是一个左值,因为它不可能引用一个对象。6.3.2.1.1左值是一个表达式(具有除void之外的对象类型),它可能指定一个对象。
参考文献来自C17标准。
bqjvbblv2#
int [i++] = d;
这很好,因为它可以翻译为:
a++ = B;
它不能扩展到任何有意义或有用的东西。这两个操作应该像这样扩展:
而这是无法实现的,因为++的结果是一个值,而不是一个“位置”。你不能把一个值赋给另一个值。
cxfofazt3#
a++
将得到一个值。正如标准所说:6.5.2.4/2
后缀
++
运算符的结果是操作数的值。赋值运算符
=
需要一个l值作为它的左操作数,但a++
不是l值。而在c[i++]
的情况下,[]
内部的任何子表达式都是数组的索引,并且应该被评估为整数值。该子表达式可以是也可以不是变量或l值或整数文字。子表达式但是表达式c[i++]
本身是L值。也就是说,
a++
不能是=
运算符的左操作数。因此,a++ = b;
在语法上是无效的语句。o4tp2gmn4#
不要太正式,理由如下。
注意
a
和b
在表达式a=b
中的不同状态。像v
这样的表达式本身没有意义,这取决于表达式出现的位置。它有时表示某些存储或存储本身的值。在赋值
a=b
中:a
是接收器,意味着它表示将存储值的存储器。b
是存储器中包含的值。想想
a=666
和3=b
,后者没有意义。a
是一个左值(赋值的左操作数),该值表示将进行存储的位置。b
是一个r值(赋值的右操作数),或者简单地说是一个值,即b
的值将被存储到a
中。a++
不能是l值,它是一个纯值(如666),即a
在其内容递增之前的值。c[i++]
是允许的,因为在表达式i++
中是一个值,用于索引数组c
,就像在c[some value]
中一样,它反过来可以用作l值或r值,它表示存储或内部的值。qkf9rpyu5#
让我们来看一个稍微不同的情况。我们来看一下假设的表达
现在,
++a
完全等价于a += 1
,a += 1
(几乎)完全等价于a = a + 1
。但是假设我们做了以下任何一个替换:你会认为这两个表达式中的任何一个都等价于
a = b
加上a
的增量吗?我不这么认为。尽管a++
在相同的方面不等价(也就是说,它产生a
的旧值而不是新值),但把它放在赋值运算符的左手同样是错误的。这就是问题所在“你可以放在赋值运算符左手的东西”,或 lvalues。像
a
这样的变量名总是左值。像a + 1
或a++
这样的值计算永远不是左值。但是像c[i]
这样的数组下标表达式总是左值,c[i++] = d
就可以了,c[i++ + f() + j++ + k++] = d
也可以,只有当你做了像c[i++] = e[i++]
这样的事情,它才会变成undefined(或者,上帝帮助你,c[i++ + i++] = d
)。增编:我说:
你会认为这两个表达式中的任何一个都等价于
a = b
后面跟着一个增量a
吗?我不这么认为。我在这里的措辞似乎表明,直觉上很明显,
(a += 1) = b
(以及扩展后的++a = b
)是无意义的。这是直觉上很明显的-对我来说,无论如何。:-)但我有偏见,因为我用C编程的时间太长了,实际上,称这些无意义肯定不是唯一可能的解释。事实上,正如Eric Postpischil在评论中提醒我的那样,在C++中,*++a
实际上是一个左值 *,其他两个也是如此。所以用++a = b
来类比为什么a++ = b
不能工作并不是一个非常有力的论点。