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

时间:2014-09-03 22:01:16

标签: c++ c increment prefix

我一直在愚弄一些代码,看到一些我不理解“为什么”的东西。

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”。很公平。

前缀一个编译和WORKS在C ++中。是的,我理解它很乱,你正在处理未分配的内存,但它可以工作和编译。在C中,这不会编译,返回与后缀“左值作为赋值的左操作数所需的左值”相同的错误。无论如何编写,使用两个“=”运算符或“++ ptr”语法进行扩展,都会发生这种情况。

C处理此类赋值的方式与C ++如何处理它有什么区别?

2 个答案:

答案 0 :(得分:73)

在C和C ++中,x++的结果都是右值,因此您无法分配它。

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

在C中,赋值表达式的结果是rvalue(C标准§6.5.16/ p3):

  

赋值运算符将值存储在由指定的对象中   左操作数。赋值表达式的值为left   赋值后的操作数,但不是左值。

因为它不是左值,你不能分配它:( C标准§6.5.16/ p2 - 注意这是一个约束)

  

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

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

  

赋值运算符(=)和复合赋值运算符all   小组从右到左。所有都需要左侧可修改的左值   操作数并返回一个引用左操作数的左值。

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

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

在C ++ 11中,++ptr = ptr1的行为变得很明确。如果我们将其重写为

,则更清楚
(ptr += 1) = ptr1;

从C ++ 11开始,C ++标准提供了(§5.17[expr.ass] / p1)

  

在所有情况下,在计算值之后对赋值进行排序   左右操作数,以及值的计算之前   赋值表达式。关于   不确定顺序的函数调用,化合物的操作   任务是一次评估。

因此,在=ptr += 1的值计算之后,ptr1执行的分配将被排序。由+=执行的赋值在ptr += 1的值计算之前排序,并且+=所需的所有值计算必须在该赋值之前排序。因此,这里的排序是明确定义的,并且没有未定义的行为。

答案 1 :(得分:17)

在C中,前后增量的结果是rvalues,我们无法分配到 rvalue ,我们需要左值 also see: Understanding lvalues and rvalues in C and C++ )。我们可以通过转到draft C11 standard部分6.5.2.4 Postfix递增和递减运算符来看到(强调我的前进):

  

后缀++运算符结果的值   操作数。 [...]参见添加剂操作者和化合物的讨论   分配有关约束,类型和转换的信息   操作对指针的影响。 [...]

因此,后递增的结果是,它与 rvalue 同义,我们可以通过转到6.5.16 部分来确认操作符以上段落指示我们进一步理解约束和结果,它说:

  

[...]赋值表达式具有左后操作数的值   分配,但不是左值。[...]

进一步确认后增量的结果不是左值

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

  

[...]参见附加算子和复合赋值的讨论   有关约束,类型,副作用和转换的信息   操作对指针的影响。

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

在C ++中,后递增也是 rvalue ,更具体地说是 prvalue 我们可以通过转到5.2.6 部分来确认这一点。递增和递减说:

  

[...] 结果是prvalue。结果的类型是cv-unqualified   操作数类型的版本[...]

关于预增量C和C ++的不同。在C中,结果是 rvalue ,而在C ++中,结果是 lvalue ,这解释了为什么++ptr = ptr1;在C ++中工作而不是C。

对于C ++,这将在5.3.2 增量和减量部分中介绍:

  

[...]结果是更新的操作数; 这是一个左值,它是一个   如果操作数是位字段,则为位字段。[...]

了解是否:

++ptr = ptr1;

在C ++中是否定义良好我们需要两种不同的方法,一种用于预C ++ 11,另一种用于C ++ 11。

Pre C ++ 11此表达式调用undefined behavior,因为它在同一序列点中多次修改对象。我们可以通过转到Pre C ++ 11草案标准部分5 表达式来看到这一点,其中说:

  

除非另有说明,否则评估个人操作数的顺序   单个表达式的运算符和子表达式以及顺序   在哪些副作用发生,是未指明的.57)之间   上一个和下一个序列点标量对象应该存储它   通过表达式的评估最多修改一次值。   此外,只能访问先前值以确定   要存储的值。应满足本款的要求   对于每个允许的完整子表达式的排序   表达;否则行为未定义。 [例如:

 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,我们应该转到defect report 637: Sequencing rules and example disagree ,这是导致以下内容的缺陷报告:

i = ++i + 1;

在C ++ 11中成为明确定义的行为,而在C ++ 11之前,这是undefined behavior。本报告中的解释是我见过的最好的解释之一,多次读它是有启发性的,并帮助我以新的眼光理解了许多概念。

导致此表​​达式变为明确定义的行为的逻辑如下:

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

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

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

  4. 逻辑有点相似:

    ++ptr = ptr1;
    
    1. 在分配副作用之前对LHS和RHS的值计算进行排序。

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

    3. 为了计算LHS(++ ptr)的值,有必要首先对左值表达式++ ptr进行值计算,然后对结果进行左值到右值的转换。这保证了在赋值副作用之前对递增副作用进行排序。换句话说,它为此表达式生成一个明确定义的顺序和最终值。

    4. 注意

      OP说:

        

      是的,我理解它很乱,你处理的是未分配的   记忆,但它可以工作和编译。

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

        

      出于这些运算符的目的,指向非阵列对象的指针   行为与指向数组的第一个元素的指针相同   长度为1,对象的类型为元素类型。

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

        

      [...]如果指针操作数和结果都指向元素   相同的数组对象,或超过数组的最后一个元素   对象,评估不得产生溢出;否则,   行为未定义。

      这样:

      ++ptr ;
      

      仍然是一个有效的指针。