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 += 1ptr1的值计算之后排序的。 += 执行的赋值在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++ 关于 ++ 运算符的区别的主要内容,如果未能解决你的问题,请参考以下文章

C++ c++与C语言的区别第三讲

C++中赋值运算操作符和=重载有啥区别?

c的free和c++的delete的区别

在c或c ++中通过结构对象变量创建树的点运算符和箭头运算符之间的区别[重复]

关于c++的运算符重载那些事

关于C++运算符重载和友元的概念