在C语言中,前缀和后缀递增的结果是rvalues,我们不能将其赋值给一个rvalue,我们需要一个lvalue(
也可以参考:在C和C++中理解lvalues和rvalues)。我们可以通过查看
draft C11 standard的第6.5.2.4节
后缀递增和递减运算符来了解这一点,其中说道(
从此向前强调我的意见):“后缀++运算符的
结果是操作数的值。[...]有关约束、类型和转换以及操作对指针的影响的信息,请参见加法运算符和复合赋值的讨论。[...]”
所以后自增的结果是一个值,它与rvalue同义,我们可以通过查看第6.5.16
节赋值运算符中上面段落指出的进一步了解约束和结果的部分来确认这一点,该部分说:
[...] 赋值表达式具有左操作数的值,但不是lvalue。[...]
这进一步证实了后自增的结果不是lvalue。
对于前自增,我们可以从第6.5.3.1
节前缀增量和减量运算符中看到:
[...] 有关约束、类型、副作用和转换以及操作对指针的影响的信息,请参见加性运算符和复合赋值的讨论。
也像后自增一样回指到6.5.16
,因此C中前自增的结果也不是lvalue。
在C++中,后置递增也是一个
rvalue,更具体地说,是一个
prvalue。我们可以通过查看第
5.2.6
节的
增量和减量来确认这一点,其中写道:
[...] 结果是一个prvalue。结果的类型是操作数的cv-unqualified版本[...]
关于前置递增,在C和C++中有所不同。在C中,结果是一个rvalue,而在C++中,结果是一个lvalue,这解释了为什么++ptr = ptr1;
在C++中可行但在C中不行。
对于C ++,这在第5.3.2
节的增量和减量中有所涉及,其中写道:
[...]结果是更新的操作数;它是一个lvalue,如果操作数是位域,则它是一个位域[...]
要理解是否:
++ptr = ptr1
在C++中,如果要确定某个表达式是否被定义良好,我们需要使用两种不同的方法,一种是针对C++11之前的版本,另一种是针对C++11。在C++11之前的版本中,这个表达式会调用
未定义行为,因为它在同一序列点内修改了对象超过一次。我们可以通过查看C++11之前的草案标准第5节
表达式
来看到这一点。
Except where noted, the order of evaluation of operands of individual
operators and subexpressions of individual expressions, and the order
in which side effects take place, is unspecified.57) Between the
previous and next sequence point a scalar object shall have its stored
value modified at most once by the evaluation of an expression.
Furthermore, the prior value shall be accessed only to determine the
value to be stored. The requirements of this paragraph shall be met
for each allowable ordering of the subexpressions of a full
expression; otherwise the behavior is undefined. [ Example:
i = v[i ++]; / / the behavior is undefined
i = 7 , i++ , i ++; / / i becomes 9
i = ++ i + 1; / / the behavior is undefined
i = i + 1; / / the value of i is incremented
—end example ]
我们先将指针
ptr
递增,然后再对其进行赋值,这是两次修改操作,在这种情况下,序列点发生在表达式末尾的
;
之后。
对于C+11,我们应该参考
缺陷报告637:排序规则与示例不一致,这是导致以下结果的缺陷报告:
i = ++i + 1;
在C++11中,这个表达式的行为变得更加明确,而在C++11之前,它是未定义的行为。本报告中的解释是我所见过的最好的解释之一,多次阅读它可以让我以新的方式理解许多概念。
导致这个表达式的行为变得明确的逻辑如下:
赋值副作用必须在其左右值的值计算之后进行排序(5.17 [expr.ass]第1段)。
LHS(i)是一个左值,因此它的值计算涉及计算i的地址。
为了计算RHS(++i + 1)的值,必须首先计算lvalue表达式++i的值,然后对结果进行lvalue-to-rvalue转换。这保证增量副作用在加法操作的计算之前进行排序,进而在赋值副作用之前进行排序。换句话说,它产生了一个明确定序和最终值的表达式。
对于另一个表达式,逻辑有些类似:
++ptr = ptr1
LHS和RHS的值计算在赋值副作用之前进行。
RHS是一个lvalue,因此其值计算涉及计算ptr1的地址。
为了对LHS(++ptr)进行值计算,需要首先对lvalue表达式++ptr进行值计算,然后对结果进行lvalue-to-rvalue转换。这保证了增量副作用在赋值副作用之前进行顺序。换句话说,它为此表达式产生了一个明确定义的顺序和最终值。
注意
OP说:
是的,我知道它很混乱,而且你正在处理未分配的内存,但它可以工作并编译。
指向非数组对象的指针被认为是大小为1的数组,用于加法运算符,我将引用C++标准草案,但C11几乎具有完全相同的文本。来自第5.7
节加性运算符:
对于这些操作符,指向非数组对象的指针与指向长度为1且元素类型为该对象类型的数组的第一个元素的指针行为相同。此外,它进一步告诉我们,只要不引用指针,就可以指向数组末尾之后的位置。如果指针操作数和结果都指向同一数组对象的元素或数组对象的最后一个元素之后的一个元素,则评估不会产生溢出;否则,行为是未定义的。
++ptr ;
仍然是一个有效的指针。
++i
不返回一个左值(l-value)。无论如何,这是未定义行为,因为你在两个连续的序列点之间修改了变量2次。换句话说,增量值和赋值操作的顺序是未指定的。 - bolov++x
仅仅是x += 1
的简写)。 - T.C.++ptr = ptr1
不是未定义行为。前缀++
的副作用与=
的副作用之间存在一个先序关系。 - dyp