C 和 C++ 关于 ++ 运算符的区别
Posted
技术标签:
【中文标题】C 和 C++ 关于 ++ 运算符的区别【英文标题】:The difference between C and C++ regarding the ++ operator 【发布时间】:2014-10-28 13:05:37 【问题描述】:我一直在胡闹一些代码,看到了一些我不明白“为什么”的东西。
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++ 处理它的方式有什么区别?
【问题讨论】:
据我所知++i
不会在 C 中返回左值。无论如何,这是 UB,因为您在两个连续的序列点之间修改了变量 2 次。换句话说,不指定值是先递增还是先赋值。
@juanchopanza 代码runes,它是UB,所以程序会及时返回并停止编译过程。所以……是的……
@juanchopanza:也许程序会回到过去并中断编译。编辑:我看到 bolov 有同样的想法
赋值的结果是 C 中的右值和 C++ 中的左值(++x
只不过是x += 1
)。
@bolov 我认为++ptr = ptr1
不是 C++ 中的 UB (>= 11)。前缀++
的副作用和=
的副作用之间存在先序关系。
【参考方案1】:
在 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 中,赋值表达式的结果是一个右值(C 标准 §6.5.16/p3):
赋值运算符将值存储在由 左操作数。赋值表达式具有左边的值 赋值后的操作数,但不是左值。
因为它不是左值,所以不能分配给它:(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
的值计算之前排序,+=
所需的所有值计算都必须在该赋值之前排序。因此,这里的顺序是明确定义的,没有未定义的行为。
【讨论】:
在你的最后一句话中,“任务”应该是指“任务的副作用”? 实际上我不明白为什么在 C 中,(非复合)赋值表达式的值被称为其左操作数的值;它实际上是它的 right 操作数的值(显然它是一个右值)。当然,在像i=i+1
这样的情况下,如果再次计算右操作数表达式,它不会得到的值,但类似的语句对于左操作数表达式也不是真的,在像int a[2]=0,3; a[a[0]]=1
这样有些人为的情况下。跨度>
还有一点:C++ 中的每个对象都等价于一个由一个对象组成的数组——它本身——并且任何数组的末尾值都是一个有效的指针值。所以没有“混乱……未分配的内存”。
++ptr = ptr1;
在两种语言中的语法都是正确的(左边的表达式可以是 C 中的 unary-expression);或者您所说的语法正确性是什么意思?
@MarcvanLeeuwen:这是一个差别很大的例子,很明显赋值表达式的值不是其右操作数的值:rextester.com/CIWA70704【参考方案2】:
在C中,前后递增的结果是右值,我们不能分配给一个右值,我们需要一个左值(also see: Understanding lvalues and rvalues in C and C++ em>) 。我们可以通过转到draft C11 standard 部分看到6.5.2.4
后缀递增和递减运算符(强调我的前进):
后缀++运算符的结果是 操作数。 [...] 参见关于加法运算符和复合的讨论 有关约束、类型和转换的信息的分配和 操作对指针的影响。 [...]
所以后增量的结果是一个值,它是右值的同义词,我们可以通过转到6.5.16
部分来确认这一点赋值运算符 上面的段落指出我们要进一步理解约束和结果,它说:
[...] 赋值表达式在 赋值,但不是左值。[...]
进一步确认后增量的结果不是左值。
对于预增量,我们可以从 6.5.3.1
部分看到 前缀增量和减量运算符,其中说:
[...]参见关于加法运算符和复合赋值的讨论 有关约束、类型、副作用和转换的信息,以及 操作对指针的影响。
也像后增量一样指向6.5.16
,因此在 C 中预增量的结果也不是 左值。
在 C++ 中,后增量也是一个 rvalue,更具体地说是一个 prvalue,我们可以通过转到 5.2.6
部分来确认这一点增量和减量 em> 上面写着:
[...]结果是prvalue。结果的类型是cv-unqualified 操作数类型的版本[...]
关于预增量 C 和 C++ 不同。在 C 中,结果是一个 rvalue,而在 C++ 中,结果是一个 lvalue,这就解释了为什么 ++ptr = ptr1;
在 C++ 中有效,但在 C 中无效。
对于 C++,这在 5.3.2
部分中进行了介绍 递增和递减 说:
[...]结果是更新后的操作数; 它是一个左值,它是一个 如果操作数是位域,则位域。[...]
了解是否:
++ptr = ptr1;
在 C++ 中是否定义良好,我们需要两种不同的方法,一种用于 C++11 之前的方法,另一种用于 C++11。
在 C++11 之前,此表达式调用 undefined behavior,因为它在同一序列点内多次修改对象。我们可以通过转到 Pre C++11 草案标准部分 5
Expressions 看到这一点:
除非另有说明,个别操作数的求值顺序 单个表达式的运算符和子表达式,以及顺序 副作用发生的位置,未指定。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,我们应该转到 defect report 637: Sequencing rules and example disagree ,这是导致的缺陷报告:
i = ++i + 1;
在 C++11 中成为定义明确的行为,而在 C++11 之前这是undefined behavior。这份报告中的解释是我见过的最好的解释之一,多次阅读它很有启发性,帮助我从新的角度理解了许多概念。
导致该表达式成为明确定义的行为的逻辑如下:
赋值副作用需要在其 LHS 和 RHS 的值计算之后进行排序(5.17 [expr.ass] 第 1 段)。
LHS(i)是一个左值,所以它的值计算涉及到计算i的地址。
为了对 RHS (++i + 1) 进行值计算,必须首先对左值表达式 ++i 进行值计算,然后对结果进行左值到右值的转换。这保证了递增副作用在加法运算的计算之前排序,而加法运算又在赋值副作用之前排序。换句话说,它为这个表达式生成了一个定义明确的顺序和最终值。
逻辑有点类似:
++ptr = ptr1;
LHS 和 RHS 的值计算在赋值副作用之前排序。
RHS是一个左值,所以它的值计算涉及计算ptr1的地址。
为了对 LHS (++ptr) 进行值计算,必须首先对左值表达式 ++ptr 进行值计算,然后对结果进行左值到右值的转换。这保证了增量副作用在赋值副作用之前排序。换句话说,它为这个表达式生成了一个定义明确的顺序和最终值。
注意
OP 说:
是的,我知道这很混乱,您正在处理未分配的问题 内存,但它可以工作并编译。
对于加法运算符,指向非数组对象的指针被认为是大小为 1 的数组,我将引用 C++ 标准草案,但 C11 的文本几乎完全相同。来自5.7
部分加法运算符:
对于这些运算符,指向非数组对象的指针 行为与指向数组的第一个元素的指针相同 长度为一,对象的类型作为其元素类型。
并进一步告诉我们,只要您不取消引用指针,指向数组末尾之后的一个是有效的:
[...]如果指针操作数和结果都指向 相同的数组对象,或数组的最后一个元素 对象,评估不会产生溢出;否则, 行为未定义。
所以:
++ptr ;
仍然是一个有效的指针。
【讨论】:
以上是关于C 和 C++ 关于 ++ 运算符的区别的主要内容,如果未能解决你的问题,请参考以下文章