我试图理解C语言中一元运算符背后的语法。根据这个版本的标准link,在第6.5.3节中,一元运算符具有以下语法:
unary-expression:
...
++ unary-expression
...
这意味着从语法的Angular 来看,这样的东西是法律的:a = ++++b
。但是,gcc编译器提供了以下错误:lvalue required as increment operand
。
我不明白为什么?
根据标准,++b
相当于(b += 1)
。这意味着a=++++b
应该扩展为a=((b+=1)+=1)
。为什么编译器给予上述错误?
4条答案
按热度按时间lg40wkob1#
为了充分解释这一点,我们需要深入到语言律师的领域。我会一步一步地解释。
当预处理器解析表达式
++++b
时,它会寻找最长的字符序列,以形成一个有效的运算符,即所谓的“最大咀嚼规则”。在这种情况下,这意味着代码将被视为我们已经编写了++ ++b
(而不是++ + +b
或类似的代码)。正如你从形式语法中注意到的,前缀++和--可以与其他一元表达式组合,所以我们得到“从右到左的结合性”,意思是
(++(++b)
。然而,C语法只指定了 * syntag-wise *,像这样合并组合多个一元运算符是可以的,但它是否有效取决于各个运算符。从那里我们将结束思考正式C称为“左值”的东西,本质上是一个可修改的内存位置,而不是表达式的临时结果。例如,在两个变量的情况
a + b
中,a
和b
分别是对象和左值,但加法表达式的结果不是。让我们先看一个有效的表达式。如果你写了
++*ptr
,那么这将是有效的C,因为在这种情况下,两个一元运算符指定了以下内容:C17 6.5.3.1,重点矿山
约束条件
前缀递增或递减运算符的操作数必须是原子、限定或非限定的真实的或指针类型,必须是可修改的左值。
C17 6.5.3.2,重点矿山
一元
*
运算符表示间接。如果操作数指向函数,则结果是函数指示符;如果它指向一个对象,则结果是一个左值,指定该对象。所以前缀
++
需要一个可修改的左值,而前缀*
提供了这个值,所以++*ptr
是可以的。然而,在
++++b
的情况下,第一个++b
的结果是 * 不是 * 左值。因此,在第一个++
上添加另一个++
,我们违反了上面引用的约束,这意味着代码是无效的C。那么为什么
++b
的结果不是左值呢?6.5.3.1说:前缀
++
运算符的操作数的值递增。结果是递增后操作数的新值。表达式++E
等价于(E+=1)
。好了,我们必须去挖掘
+=
(复合)赋值运算符的规则。我们在C17 6.5.16中找到了相关的解释,重点是:
赋值运算符将值存储在左操作数指定的对象中。赋值表达式在赋值后具有左操作数的值,但不是左值。
因此,由于约束冲突,编译器必须提供诊断消息。我们没有违反语法规则,但我们违反了约束。如果您希望将代码称为严格遵循的C程序,那么语法和约束都是“不允许例外”的。
ylamdve62#
在C文法中,
++b
不是左值,因此不能作为++
运算符的操作数。6.3.2.1左值、数组和函数指示符
lvalue 是一个表达式(对象类型不是
void
),它可能指定一个对象;如果一个 lvalue 在计算时没有指定一个对象,则行为是未定义的。当一个对象被称为具有特定类型时,该类型由用于指定该对象的 lvalue 指定。虽然没有C表达式是 * 左值 * 的列表,但是C标准在很多地方提到了一些表达式是左值,而另一些不是。没有提到前缀运算符的应用,但是赋值
b += 1
显式地不是左值per:6.5.16赋值运算符
[...]赋值运算符将值存储在左操作数指定的对象中。赋值表达式在赋值后具有左操作数的值,但不是 * 左值 *。
请注意,C++语法在这方面可能有所不同。
o7jaxewo3#
++b
子表达式产生一个没有可寻址存储位置的临时值。尝试在该表达式上应用++
失败,因为该运算符需要可修改的位置。像++5
这样的表达式将失败,并显示相同的错误消息。你必须递归地思考它,然后它变得明显:
++b
的值只能用作不需要修改存储位置的操作符的操作数,比如+
:57hvy0tb4#
这
也给出了错误:
(措辞几乎相同)