你的程序遭受了未定义的行为.
在第一次通话中factorial(5)
,你在哪里有
return number * factorial(--number);
你想象这会计算
5 * factorial(4);
但这并不能保证!
如果编译器以不同的顺序查看它怎么办?
如果先在右侧工作怎么办?
如果它首先执行相当于:
temporary_result = factorial(--number);
然后进行乘法:
return number * temporary_result;
如果编译器按照这个顺序执行,那么temporary_result
将factorial(4)
,它会返回 4 倍,这不会是5!
。基本上,如果编译器按照这个顺序执行它 - 它可能会! - 然后number
“太快”减少。
您可能没有想到编译器可以这样做。
您可能会想象该表达式总是“从左到右解析”。
但这些想象并不正确。
(也可以看看这个答案 https://stackoverflow.com/questions/31087537/why-does-a-b-have-the-same-behavior-as-a-b/31088592#31088592有关评估顺序的更多讨论。)
我说过该表达式会导致“未定义的行为”,这个表达式就是一个典型的例子。这个表达式未定义的原因是它内部发生了太多的事情。
表达方式有问题
return number * factorial(--number);
是这个变量number
在其中使用它的价值,and同一个变量number
也在其中进行修改。这种模式基本上是毒药。
让我们标记两个位置number
出现了,这样我们就可以非常清楚地谈论它们:
return number * factorial(--number);
/* A */ /* B */
在 A 点我们取变量的值number
.
在 B 点我们修改变量的值number
.
但问题是,在 A 点,我们得到的是“旧”值还是“新”值number
?
我们是在 B 点修改之前还是之后得到的?
正如我已经说过的,答案是:我们不知道。 C中没有规则告诉我们。
同样,您可能认为存在从左到右评估的规则,但事实并非如此。因为没有规则说明应该如何解析这样的表达式,所以编译器可以做任何它想做的事情。它可以以“正确”的方式或“错误”的方式解析它,或者它可以做一些更奇怪和意想不到的事情。 (而且,实际上,首先没有“正确”或“错误”的方法来解析这样的未定义表达式。)
解决这个问题的方法是:不要这样做!
不要在只有一个变量的地方编写表达式(例如number
) 既被使用又被修改。
在这种情况下,正如您已经发现的,有一个简单的修复方法:
return number * factorial(number - 1);
现在,我们实际上并没有尝试修改变量的值number
(如表达式--number
did),我们只是在将较小的值传递给递归调用之前从中减去 1。
所以现在,我们没有违反规则,我们没有使用和修改number
在同一个表达中。
我们只是使用它的值两次,这很好。
有关此类表达式中未定义行为的更多信息(更多!),请参阅为什么这些构造使用增量前和增量后未定义的行为? https://stackoverflow.com/questions/949433/why-are-these-constructs-using-pre-and-post-increment-undefined-behavior