C和C++中++运算符的区别

71

我一直在摆弄一些代码,看到了一些我不理解的东西,想问一下原因。

int i = 6;
int j;

int *ptr = &i;
int *ptr1 = &j

j = i++;

//now j == 6 and i == 7. Straightforward.

如果你把运算符放在等号左边会怎样?

++ptr = ptr1;

等价于

(ptr = ptr + 1) = ptr1; 

然而

ptr++ = ptr1;

等价于

ptr = ptr + 1 = ptr1;

后缀表达式编译时出现错误,我明白了。在赋值运算符的左侧有一个不变的“ptr + 1”常量。好的。

前缀表达式在C++中可以编译和运行正常。是的,我知道这很混乱,你正在处理未分配的内存,但它可以工作并编译。在C中,这种方式不会编译,并返回与后缀表达式相同的错误“需要左值作为赋值运算符的左操作数”。无论如何编写代码,使用两个“=”运算符或者“++ptr”语法,都会发生这种错误。

C和C++在处理这种赋值方式时的区别是什么?


12
据我所知,在C语言中++i不返回一个左值(l-value)。无论如何,这是未定义行为,因为你在两个连续的序列点之间修改了变量2次。换句话说,增量值和赋值操作的顺序是未指定的。 - bolov
3
@juanchopanza 这段代码有未定义行为(UB),导致程序回溯并停止编译过程。所以...是的... - bolov
4
也许该程序会倒退时间并中断编译。编辑:我看到bolov有同样的想法。 - Benjamin Lindley
2
在C语言中,赋值的结果是一个rvalue,在C++中则是一个lvalue(而++x仅仅是x += 1的简写)。 - T.C.
3
我认为在C++(>= 11)中,++ptr = ptr1不是未定义行为。前缀++的副作用与=的副作用之间存在一个先序关系。 - dyp
显示剩余23条评论
2个回答

74
在C和C++中,x++的结果是一个右值,因此您不能对其进行赋值。在C中,如果x不是bool,则++x等同于x += 1(C标准§6.5.3.1/p2)。在C++中,++x等同于x += 1,如果x不是bool(C++标准§5.3.2 [expr.pre.incr]/p1)。
在C中,赋值表达式的结果是一个右值(C标准§6.5.16/p3)。因为它不是左值,所以您无法对其进行赋值(C标准§6.5.16/p2 - 请注意,这是一种约束条件)。
在C++中,赋值表达式的结果是一个左值(C++标准§5.17 [expr.ass]/p1)。因此,在C中++ptr = ptr1;是一种可诊断的约束条件违规,但不违反C++中任何可诊断的规则。
然而,在C++11之前,++ptr = ptr1;具有未定义行为,因为它在两个相邻的序列点之间修改了ptr两次。
在C++11中,++ptr = ptr1的行为变得明确定义。如果我们将其重写为:
(ptr += 1) = ptr1;
自 C++11 开始,C++ 标准提供了以下规定(§5.17 [expr.ass]/p1):
在所有情况下,赋值操作被排序为在左右操作数的值计算之后、赋值表达式的值计算之前进行。对于一个未确定排序的函数调用,在复合赋值运算中的操作是单个求值。
因此,由 = 执行的赋值操作在 ptr += 1ptr1 的值计算之后进行。由 += 执行的赋值操作在 ptr += 1 的值计算之前进行,+= 所需的所有值计算都必须在该赋值操作之前被排序。因此,这里的排序是明确定义的,不存在未定义行为。

1
在你最后的引用中,“the assignment”是指“赋值的副作用”吗? - M.M
1
还有一个要点:在C++中,每个对象都等同于一个包含一个对象(它本身)的数组,而任何数组的末尾值都是有效的指针值。因此,没有“混乱的...未分配内存”。 - Potatoswatter
@Potatoswatter 说得好,我在我的答案中添加了一条注释来涵盖这个问题。 - Shafik Yaghmour
1
++ptr = ptr1; 在两种语言中都是语法上正确的(在 C 语言中,左侧表达式可以是一个一元表达式);或者你指的是语法正确性是什么? - Columbo
2
@MarcvanLeeuwen:这里有一个例子,在这个例子中有很大的差别,很明显赋值表达式的值并不等于它的右操作数的值:http://rextester.com/CIWA70704 - Ben Voigt
显示剩余5条评论

17
在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之前,它是未定义的行为。本报告中的解释是我所见过的最好的解释之一,多次阅读它可以让我以新的方式理解许多概念。

导致这个表达式的行为变得明确的逻辑如下:

  1. 赋值副作用必须在其左右值的值计算之后进行排序(5.17 [expr.ass]第1段)。

  2. LHS(i)是一个左值,因此它的值计算涉及计算i的地址。

  3. 为了计算RHS(++i + 1)的值,必须首先计算lvalue表达式++i的值,然后对结果进行lvalue-to-rvalue转换。这保证增量副作用在加法操作的计算之前进行排序,进而在赋值副作用之前进行排序。换句话说,它产生了一个明确定序和最终值的表达式。

对于另一个表达式,逻辑有些类似:

++ptr = ptr1;
  1. LHS和RHS的值计算在赋值副作用之前进行。

  2. RHS是一个lvalue,因此其值计算涉及计算ptr1的地址。

  3. 为了对LHS(++ptr)进行值计算,需要首先对lvalue表达式++ptr进行值计算,然后对结果进行lvalue-to-rvalue转换。这保证了增量副作用在赋值副作用之前进行顺序。换句话说,它为此表达式产生了一个明确定义的顺序和最终值。

注意

OP说:

是的,我知道它很混乱,而且你正在处理未分配的内存,但它可以工作并编译。

指向非数组对象的指针被认为是大小为1的数组,用于加法运算符,我将引用C++标准草案,但C11几乎具有完全相同的文本。来自第5.7加性运算符

对于这些操作符,指向非数组对象的指针与指向长度为1且元素类型为该对象类型的数组的第一个元素的指针行为相同。此外,它进一步告诉我们,只要不引用指针,就可以指向数组末尾之后的位置。如果指针操作数和结果都指向同一数组对象的元素或数组对象的最后一个元素之后的一个元素,则评估不会产生溢出;否则,行为是未定义的。
++ptr ;

仍然是一个有效的指针。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接