关于++运算符的C和C ++的区别

我一直在玩一些代码,看到一些我不明白的“为什么”。

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 ++如何处理它有什么区别?

在C和C ++中, x++的结果是一个右值,所以你不能指定它。

在C中, ++x相当于x += 1 (C标准§6.5.3.1/ p2;所有的C标准都是WG14 N1570)。 在C ++中, ++x如果x不是bool (C ++标准§5.3.2[expr.pre.incr] / p1;所有C ++标准列表都是WG21 N3936),则++x等价于x += 1

在C中,赋值expression式的结果是一个右值(C标准§6.5.16/ p3):

赋值运算符将值存储在由左操作数指定的对象中。 赋值expression式在赋值后具有左操作数的值,但不是左值。

因为它不是左值,所以你不能指定它:(C标准§6.5.16/ p2 – 注意这是一个约束)

赋值运算符的左操作数应该有一个可修改的左值。

在C ++中,赋值expression式的结果是一个左值(C ++标准§5.17[expr.ass] / p1):

赋值运算符(=)和复合赋值运算符全部从右到左。 所有需要一个可修改的左值作为它们的左操作数,并返回一个左值指向左操作数。

所以++ptr = ptr1; 在C中是可诊断的约束违规,但不违反C ++中的任何可诊断规则。

但是,pre-C ++ 11, ++ptr = ptr1; 具有未定义的行为,因为它在两个相邻序列点之间修改ptr两次。

在C ++ 11中, ++ptr = ptr1的行为变得很好定义。 如果我们把它改写成更清楚

 (ptr += 1) = ptr1; 

由于C ++ 11,C ++标准规定(§5.17[expr.ass] / p1)

在任何情况下,赋值都是在左右操作数的值计算之后,赋值expression式的值计算之前进行sorting的。 对于一个不确定sorting的函数调用,复合赋值的操作是一个单独的评估。

所以在ptr += 1 ptr1ptr1的值计算之后,由=执行的赋值被sorting。 由+=执行的赋值在ptr += 1的值计算之前被sorting,并且+=所需的所有值计算必须在该赋值之前被sorting。 因此,这里的sorting是明确的,没有不确定的行为。

在C中,前后增量的结果是右值 ,我们不能赋值右值 ,我们需要左值另请参阅:在C和C ++中理解 左值右值 )。 我们可以看看C11标准草案 6.5.2.4 后缀增减操作符强调我的前进 ):

后缀++运算符的结果 操作数的值 。 […]查看添加剂操作符和复合赋值的讨论,了解有关约束,types和转换的信息以及操作对指针的影响。 […]

所以后增量的结果是一个与右值同义的 ,我们可以通过参考6.5.16节的赋值运算符来进一步了解约束和结果,从而证实了这一点。

[…]赋值expression式在赋值之后有左操作数的值, 但不是左值 。[…]

这进一步证实了后增加的结果不是左值

对于预增量,我们可以从6.5.3.1节的前缀增量和减量运算符中看到:

[…]请参阅加法运算符和复合赋值的讨论,以获取有关约束,types,副作用和转换的信息以及操作对指针的影响。

也像后期增量一样指向6.5.16 ,因此C中的预增量结果也不是左值

在C ++中,后期增量也是一个右值 ,更具体地说是一个 ,我们可以通过参考5.2.6节的增量和减量来证实这一点:

[…] 结果是一个prvalue。 结果的types是操作数types的cv不合格版本[…]

关于预增C和C ++有所不同。 在C中,结果是一个右值,而在C ++中,结果是一个左值 ,它解释了为什么++ptr = ptr1; 在C ++中工作,但不是C.

对于C ++,这在5.3.2中有介绍:

[…]结果是更新的操作数; 它是一个左值 ,如果操作数是一个位域,则它是一个位域[…]。

要了解是否:

 ++ptr = ptr1; 

定义良好或不在C ++中,我们需要两个不同的方法,一个用于C ++ 11之前,另一个用于C ++ 11。

Pre C ++ 11此expression式调用未定义的行为 ,因为它在相同的序列点内不止一次地修改对象。 我们可以通过Pre C ++ 11草案标准第5expression式来看到这一点:

除非另有说明,否则未指定单个操作符的操作数和个别expression式的次expression式的顺序以及副作用发生的顺序.57) 在前一个顺序点和下一个顺序点之间,标量对象应该修改其存储值最多一次由一个expression式的评估。 此外,只有在确定要存储的值时才能访问先前值。 对于一个完整expression式的子expression式的每个可允许的sorting,应满足本段的要求; 否则行为是不确定的。 [例如:

  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 

– 例子]

我们递增ptr ,然后分配给它,这是两个修改,在这种情况下,序列点出现在expression式的后面;

对于C + 11,我们应该去缺陷报告637:sorting规则和示例不一致 ,这是导致的缺陷报告:

 i = ++i + 1; 

在C ++ 11中成为明确的行为,而在C ++ 11之前,这是未定义的行为 。 这个报告中的解释是我见过的最好的一个,多次阅读都很有启发性,帮助我从新的angular度理解了许多概念。

导致这个expression式变得定义良好的行为的逻辑如下:

  1. 分配副作用需要在LHS和RHS(5.17 [expr.ass]段落1)的值计算之后进行sorting。

  2. LHS(i)是一个左值,所以它的值计算包括计算i的地址。

  3. 为了计算RHS(++ i + 1),有必要首先对左值expression式++ i进行值计算,然后对结果进行左值到右值的转换。 这保证了增量副作用在计算加法运算之前被sorting,而运算又是在分配副作用之前sorting的。 换句话说,它为这个expression式产生一个明确定义的顺序和最终值。

逻辑有点类似于:

 ++ptr = ptr1; 
  1. LHS和RHS的值计算在分配副作用之前被sorting。

  2. RHS是一个左值,所以它的值计算包括计算ptr1的地址。

  3. 为了计算LHS(++ ptr),有必要先计算左值expression式++ ptr,然后对结果进行左值到右值的转换。 这保证了增量副作用在分配副作用之前被sorting。 换句话说,它为这个expression式产生一个明确定义的顺序和最终值。

注意

OP说:

是的,我明白这是混乱的,你正在处理未分配的内存,但它的工作和编译。

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

对于这些运算符来说,指向非数组对象的指针的行为与指向长度为1的数组的第一个元素的指针相同,该对象的types为元素types。

并进一步告诉我们指向一个过去的数组的末尾是有效的,只要你不取消引用指针:

[…]如果指针操作数和结果指向同一个数组对象的元素, 或者一个超过了数组对象的最后一个元素 ,则评估不会产生溢出; 否则,行为是不确定的。

所以:

 ++ptr ; 

仍然是一个有效的指针。