为什么C++中多个自增/自减操作是有效的,而在C语言中不是?

9

test.(c/cpp)

#include <stdio.h>

int main(int argc, char** argv)
{
  int a = 0, b = 0;
  printf("a = %d, b = %d\n", a, b);
  b = (++a)--;
  printf("a = %d, b = %d\n", a, b);

  return 0;
}

如果我将上述内容保存为.cpp文件,它会在执行时进行编译并输出以下结果:
a = 0, b = 0
a = 0, b = 1

然而,如果我将它保存为.c文件,就会出现以下错误:
test.c:7:12: error: lvalue required as decrement operator.
< p>在(newValue)--操作之前,(++a)操作不应该被解决吗?有人对此有什么见解吗?


4
这行代码 b = (++a)--; 是否属于未定义行为呢? - LihO
3
@LihO问:为什么a的递增是在其评估之前进行排序的? - Andy Prowl
为什么不直接写成 b = a + 1 呢? - Gaurav Agarwal
@LihO:我同意你的观点,你在最后一个例子中是正确的,但是在OP的问题中不是UB(未定义行为)在我看来。 - Andy Prowl
1
我认为真正需要理解的教训是,C 代码行数越少并不意味着程序运行得更快。最好写2或3行代码,这样更明显和易于理解。我建议只使用预增/减操作符,并且仅在非常特殊的情况下使用后增/减操作符(即你无法找到其他编写方式时)。 - Josh Petitt
显示剩余3条评论
4个回答

16

在C语言中,前缀和后缀增量/减量运算符的结果不是一个lvalue。

在C++中,后缀增量/减量运算符的结果也不是一个lvalue,但前缀增量/减量运算符的结果是一个lvalue。

现在在C++中执行像(++a)--这样的操作是未定义的行为,因为您在两个序列点之间修改了对象值两次。

编辑:根据@bames53的评论。在C++98/C++03中这是未定义的行为,但是C++11对序列点的理念进行了更改,现在此表达式被定义了。


3
C++11取消了序列点的概念,而是只要求读取和修改按顺序进行,这在(++a)--中得到了满足。详见这里 - bames53
不同意 (++a)-- 是良好定义的说法。bames53 链接到的页面适用于 --(++a),但后置增量没有相同的顺序。 - M.M
@M.M 在 (++a)-- 中,++a 的副作用相对于后缀 -- 是按照 C++11, 5.17p1 规定的顺序进行的: "在所有情况下,赋值都是在右操作数和左操作数的值计算之后、赋值表达式的值计算之前进行的。" (其中 5.3p2 表示 "如果 x 不是 bool 类型,则表达式 ++x 等同于 x+=1")。 - ouah
@ouah,“++a”中涉及的赋值操作与减量操作没有顺序(我不确定您认为引用的哪个部分是相关的——减量不是“a+=1”的操作数之一)。也许一个新的问题会更合适,而不是在评论中争论。 - M.M
@M.M 我指的是引用中的第二部分:“在赋值表达式的值计算之前,该赋值被排序……”。正如我们所知道的后缀--(5.2.6p1),“--表达式的值计算在操作数对象的修改之前排序。”对于我来说,++a的副作用应该先于--的副作用。我在SO上不再活跃了,所以如果您还没有被说服,可以请提出一个新问题,当然,如果我错了,我很乐意修改我的答案。 - ouah

3
在C和C++中,存在左值表达式和右值表达式,前者可用于等号操作符的左侧,而后者则不行。由于支持引用语义,因此C++允许更多的东西成为左值。
++ a = 3; /* makes sense in C++ but not in C. */

增量和减量运算符类似于赋值,因为它们修改它们的参数。
在C++03中,(++a)--会导致未定义的行为,因为两个不按顺序相对于彼此的操作正在修改同一个变量。(即使一个是“pre”,另一个是“post”,它们也是无序的,因为没有,&&?或类似的符号。)
在C++11中,表达式现在做你期望的事情。但是C11没有改变任何这样的规则,这是语法错误。

仅仅是因为C语言没有将运算符定义为产生左值。这是因为它支持引用语义。 - bames53
@bames53 那只是达到目的的手段... - Potatoswatter

2

对于任何想要了解标准中明确区别的人,C99,§6.5.3/2说:

前缀++运算符的操作数的值会增加。结果是操作数在增加后的新

相比之下,C++11,§5.3.2/1说:

结果是更新后的操作数;它是一个lvalue,如果操作数是位域,则是位域。

[在两种情况下都强调了]

还要注意,尽管(++a)--aint时会导致未定义的行为(至少在C++03中),但如果a是某个用户定义的类型,则您正在使用自己重载的++--,则行为将被定义 - 在这种情况下,您将获得以下等效内容:

a.operator++().operator--(0);

由于每个操作符都会导致函数调用(无法重叠),因此您实际上确实有序列点来强制定义行为(请注意,我并不推荐使用它,只是指出在这种情况下行为实际上是定义的)。


0

§5.2.7 自增和自减:

后缀 ++ 表达式的值是它的操作数的值。 [ ... ] 操作数必须是可修改的 lvalue

你在 C 编译中遇到的错误提示表明这只是 C++ 中存在的一个特性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接