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

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

C如何处理此类赋值与C ++如何处理赋值之间有何区别?


12
据我所知++i,不会在C中返回l值。无论如何,这是UB,因为您在两个连续的序列点之间两次修改了变量。换句话说,不确定值是先增加还是先分配。
bolov 2014年

3
@juanchopanza代码符文,它是UB,因此程序可以及时返回并停止编译过程。所以……是的……
bolov

4
@juanchopanza:也许程序会回到过去并中断编译。编辑:我看到鲍洛夫(Bolov)有同样的想法
本杰明·林德利

2
赋值的结果是C中的一个右值和C ++中的一个左值(++x仅此而已x += 1)。
TC

3
@bolov我认为++ptr = ptr1在C ++中不是UB(> = 11)。前缀++的副作用和的副作用之间存在先后顺序的关系=
dyp 2014年

Answers:


74

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

在C中,++x等效于x += 1(C标准§6.5.3.1/ p2;所有C标准引用均符合WG14 N1570)。在C ++中,++x等效于x += 1ifx不是bool(C ++标准§5.3.2[expr.pre.incr] / p1;所有C ++标准引用均针对WG21 N3936)。

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

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

由于它不是左值,因此无法为其分配:(C标准§6.5.16/ p2-请注意,这是一个约束)

赋值运算符应具有可修改的左值作为其左操作数。

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

赋值运算符(=)和复合赋值运算符都从右到左分组。它们都需要一个可修改的左值作为其左操作数,并返回一个引用左操作数的左值。

所以++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+=


1
在您的最后一句话中,“任务”应指“任务的副作用”?
2014年

实际上,我不明白为什么在C中(非复合)赋值表达式的值被称为其左操作数的值;为什么?它实际上是其操作数的值(然后显然是右值)。当然,在类似情况下i=i+1,如果右运算符表达式再次被求值将不会获得该值,但是对于类似左图所示的左操作数表达式,同样的陈述也不是正确的int a[2]={0,3}; a[a[0]]=1
Marc van Leeuwen 2014年

1
还有一点:C ++中的每个对象都等效于一个对象本身的数组,并且任何数组的最后一个值都是有效的指针值。因此,没有“混乱……未分配的内存”。
Potatoswatter 2014年

1
++ptr = ptr1;两种语言在语法上都是正确的(左手表达式可以是C语言中的一元表达式);或句法正确性是什么意思?
2015年

2
@MarcvanLeeuwen:这是一个示例,其中存在很大差异,并且显然赋值表达式的值不是其正确操作数的值rextester.com/CIWA70704
Ben Voigt

17

在C中,前后增量的结果是右值,我们不能将其赋给右值,我们需要一个左值另请参见:了解C和C ++中左值右值)。通过转到C11标准草案的6.5.2.4 Postfix增量和减量运算符一节,我们可以看到它(强调我的前进):

后缀++运算符的结果操作数的值。[...]有关约束,类型和转换以及操作对指针的影响的信息,请参见加法运算符和复合赋值的讨论。[...]

因此,后递增的结果是一个值,它是同义词右值,我们可以通过将部分确认此6.5.16 赋值运算符我们哪个段落上方的点约束和结果的进一步理解,它说:

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

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

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

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

也指向6.5.16后递增,因此C中的预递增结果也不是左值

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

[...]结果是一个prvalue。结果的类型是操作数类型的cv不合格版本[...]

关于预增量,C和C ++不同。在C中,结果是一个右值,而在C ++中,结果是一个左值,这说明了为什么++ptr = ptr1;在C ++中有效,但在C中不起作用。

对于C ++,这在“5.3.2 增量和减量”一节中进行了介绍,该节指出:

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

要了解是否:

++ptr = ptr1;

在C ++中定义是否正确,我们需要两种不同的方法,一种用于C ++ 11之前的版本,另一种用于C ++ 11。

在C ++ 11之前,此表达式调用未定义的行为,因为它在同一序列点内多次修改了对象。我们可以通过转到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

—结束示例]

我们先递增ptr,然后再赋值,这是两个修改,在这种情况下,序列点出现在表达式后的;

对于C + 11,我们应该转到缺陷报告637:排序规则和示例不一致,这是导致以下结果的缺陷报告:

i = ++i + 1;

在C ++ 11中成为定义良好的行为,而在C ++ 11之前,这是未定义的行为。该报告中的解释是我什至看不到的最好的解释,并且多次阅读是很有启发性的,并帮助我以新的视角理解了许多概念。

导致该表达式成为定义明确的行为的逻辑如下:

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

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

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

逻辑在以下方面有些相似:

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

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

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

注意

OP说:

是的,我知道这很麻烦,您正在处理未分配的内存,但是它可以工作并且可以编译。

指向非数组对象的指针被视为加法运算符的大小为1的数组,我将引用C ++标准草案,但C11几乎具有完全相同的文本。在“5.7 加法运算符”部分:

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

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

[...]如果指针操作数和结果都指向同一数组对象的元素,或者指向数组对象的最后一个元素,则求值不应产生溢出;否则,行为是不确定的。

所以:

++ptr ;

仍然是有效的指针。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.