当前位置: 华文星空 > 知识

i=1,为什么 (++i)+(++i)=6?

2020-09-18知识

为没有耐性看完的人

本题在C/C++中是未定义行为,可能有4,5,6等不同的答案。在其它编程语言中,取决于它如何规定的求值顺序(order of evaluation),不是运算符优先级,见Java语言标准;Javascript语言标准;C#的语言标准;Python语言标准。

为什么是4,5,6请跳到后面看。

关于这个代码的意义?

其一,不要用汇编推导规则。C/C++的规范中有明确说明,哪些是未定义行为,未指明行为,以及实现决定行为。这些都是汇编解释不了的部分。去读书,读好书,实在不确认,再读标准规范。理解不了,看看汇编,帮助理解。

其二,我这个答案想强调的是: 程序不是按顺序执行,而是按规则执行。 大多数C/C++程序员,不要这么写。特别不要「想当然」的这么写。

其三,请出题的老师不要出这种题。出题了又解释不清楚,那是打自己的脸。

其四,实践中,这类求值顺序不定的代码有实际意义,但这个过度简化的代码没有实际意义。

常见的代码 f() + g(),这个代码常见,它可能是UB,准确的说是未指明的行为(unspecified behavior)。为什么呢,因为f()和g()的调用次序不确定。

UB是明确写在语言规范中,」没有定义的行为「本身却是有定义的。

比如C#和Java中它都是5。那是因为C#和Java的语言规范中对这个求值顺序是有定义,左操作数优先,在左操作数被求值以前,右操作数不会求值。但是,他们会有同样的问题:

int i = 1; a[i] = (++i) + 1;

哪么a[1] = ?

真正的解释

本题(++i)+(++i)可能等于6,也可能不等于6。它的结果可能随编译器或平台的不同而不同。但在指定版本编译器,指定平台情况下,它一般只有一个结果,这是可重现的。

括号的优先级高,应该没有疑问,所以题目分成三步

  1. ++i
  2. ++i
  3. (1) + (2)

这就带来了一个聪明,但错误的理解:1步和2步是一样的呀,它的顺序不同不影响结果呀?

我们知道++i的等价于 b = a + 1 来看,这样看就有几个完全不同的理解了。这里的a,b以及后面的c可以看成对i的过程值,在求值的过程中i的取值。比如 i 在求值前,它是值是a,求值后它的值是b。编译器如何使用a,b,c决定了结果不同。

  1. b = a + 1
  2. c = b + 1
  3. b + c

b先求,c后求,但分别使用,结果是5( a = 1, b = 2, c = 3)

  1. b = a + 1
  2. c = a + 1
  3. c + b

b,c分别求值,但相互不影响,结果是4( a = 1, b = 2, c = 2)

  1. b = a + 1
  2. b = b + 1
  3. b + b

b先求,c后求,但c受b影响。c就是b,它们变成一个值,结果是6( a = 1, b = 3)

事实上,我们只有一个变量i=a,并没有b和c。b,c就是副作用。本题不确定的原因是两个副作用出现的次序在语义规则中没有定义。如果我们规定 + 号左边的副作用出现在右边的副作用之前,那么本题b先求,c后求,但c受b影响,最后得6。

C和C++语言中对于 副作用出现的规则,一般以语句或声明的结束为标记 ,单看表达式,很多都是没有定义。

从实现的角度,一般是参考操作符的结合性,左结合的左边副作用先出,右结合的操作符右边的副作用先出现, 或者反过来, 并不是随心所欲。

编程是一个讲道理的活动。