C和c++在++操作符方面的区别

The difference between C and C++ regarding the ++ operator

本文关键字:方面 区别 操作符 c++      更新时间:2023-10-16

我一直在摆弄一些代码,看到一些我不明白"为什么"的东西。

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。很好。

前缀1在c++中编译和工作。是的,我知道这很混乱,而且您正在处理未分配的内存,但它可以工作并编译。在C语言中,这不会编译,返回与后缀"左值要求作为赋值的左操作数"相同的错误。无论如何编写,使用两个"="操作符展开或使用"++ptr"语法展开,都会发生这种情况。

C处理这种赋值的方式和c++处理它的方式有什么不同?

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

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

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

赋值操作符将值存储在操作对象指定的对象中左操作数。赋值表达式具有左侧的值赋值后的操作数,但不是左值。

因为它不是左值,所以你不能给它赋值:(C standard§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 += 1ptr1的值计算之后进行排序的。+=执行的赋值在ptr += 1的值计算之前进行排序,+=需要的所有值计算都必须在该赋值之前进行排序。因此,这里的顺序是定义良好的,没有未定义的行为。

在C语言中,前后递增的结果都是右值,我们不能赋值给右值,我们需要一个左值(也参见:理解C和c++中的左值和右值)。我们可以通过C11标准草案6.5.2.4后缀自增和自减运算符看到(强调向前):

结果后缀++操作符的值的操作数。[…参见关于加法运算符和化合物的讨论约束、类型和转换信息的赋值指针操作的效果。[…]

所以后增的结果是一个,它是右值的同义词,我们可以通过6.5.16赋值操作符一节来确认这一点,上面的段落指出,为了进一步理解约束和结果,它说:

[…赋值表达式的左操作数的值在赋值,,但不是左值。[…]

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

对于预增量,我们可以从6.5.3.1部分看到前缀自增和自减操作符,其中表示:

[…参见关于加法运算符和复合赋值的讨论有关约束、类型、副作用和转换的信息指针操作的影响。

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

在c++中后增量也是右值,更具体地说是右值,我们可以通过5.2.6自增和自减一节来确认这一点,该节说:

[…结果为右值。结果的类型为cv-不合格操作数类型的版本[…]

对于预增量C和c++是不同的。在C中,结果是右值,而在c++中,结果是左值,这解释了为什么++ptr = ptr1;在c++中工作而不是C。

对于c++,这在5.3.2递增和递减一节中介绍,其中说:

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

了解是否:

++ptr = ptr1;

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

c++ 11之前,此表达式调用未定义行为,因为它在同一序列点内多次修改对象。我们可以在c++ 11之前的标准草案5Expressions中看到这一点,其中说:

除特别说明外,单个操作数的求值顺序单个表达式的操作符和子表达式,以及顺序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,我们应该转到缺陷报告637:排序规则和示例不一致,哪个缺陷报告导致:

i = ++i + 1;

在c++ 11中成为定义良好的行为,而在c++ 11之前,这是未定义的行为。这个报告中的解释是我见过的最好的解释之一,读了很多遍很有启发,帮助我从新的角度理解了很多概念。

导致这个表达式成为定义良好的行为的逻辑如下:

  1. 赋值副作用需要在LHS和RHS (5.17 [expr。[a]第1段。

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

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

下面的逻辑有点类似:

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

  2. RHS为左值,因此计算RHS值需要计算ptr1的地址。

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

注意

OP说:

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

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

对于这些操作符,指向非数组对象的指针的数组第一个元素的指针的行为相同长度为1,以对象的类型作为元素类型

,并进一步告诉我们,只要不解除对指针的引用,指向数组末尾后面的指针是有效的:

[…如果操作数指针和结果都指向相同的数组对象,或数组的最后一个元素的后面,评价不得产生溢流;否则,行为未定义

:

++ptr ;

仍然是一个有效指针。