Ваша программа имеет неопределенное поведение.
При первом вызове factorial(5)
, где у вас есть
return number * factorial(--number);
вы представляете, что это будет вычислять
5 * factorial(4);
Но это не гарантируется!
Что, если компилятор просматривает его в другом порядке?
Что, если он сначала работает с правой частью?
Что, если он сначала делает эквивалент:
temporary_result = factorial(--number);
а затем выполняет умножение:
return number * temporary_result;
Если компилятор сделает это в таком порядке, то temporary_result
будет factorial(4)
, и он вернет в 4 раза больше, чем 5!
. По сути, если компилятор делает это в таком порядке — а может быть! -- тогда number
слишком быстро уменьшается.
Возможно, вы не представляли, что компилятор может работать таким образом.
Вы могли вообразить, что выражение всегда будет анализироваться слева направо.
Но это неверно.
(См. также этот ответ для дальнейшего обсуждения в порядке оценки.)
Я сказал, что выражение вызывает неопределенное поведение, и это выражение является классическим примером. Что делает это выражение неопределенным, так это то, что внутри него происходит слишком много всего.
Проблема с выражением
return number * factorial(--number);
заключается в том, что значение переменной number
используется внутри нее, и та же самая переменная number
также изменяется внутри нее. И эта схема, по сути, яд.
Давайте обозначим два места, где появляется number
, чтобы мы могли говорить о них очень четко:
return number * factorial(--number);
/* A */ /* B */
В точке A мы берем значение переменной number
.
В точке B мы изменяем значение переменной number
.
Но вопрос в том, в точке A получаем ли мы старое или новое значение из number
?
Получаем ли мы его до или после того, как точка B изменила его?
И ответ, как я уже сказал, таков: мы не знаем. В C нет правила, которое могло бы сказать нам.
Опять же, вы могли подумать, что существует правило оценки слева направо, но это не так. Поскольку не существует правила, указывающего, как должно анализироваться подобное выражение, компилятор может делать все, что захочет. Он может разобрать его правильно или неправильно, а может сделать что-то еще более причудливое и неожиданное. (И на самом деле, нет правильного или неправильного способа разобрать неопределенное выражение, подобное этому.)
Решение этой проблемы: не делайте этого!
Не пишите выражения, в которых одна переменная (например, number
) одновременно используется и изменяется.
В этом случае, как вы уже обнаружили, есть простое решение:
return number * factorial(number - 1);
Теперь мы на самом деле не пытаемся изменить значение переменной number
(как это делало выражение --number
), мы просто вычитаем из него 1 перед передачей меньшего значения рекурсивному вызову. Итак, теперь мы не нарушаем правило, мы не используем и не изменяем number
в одном и том же выражении. Мы просто используем его значение дважды, и это нормально.
Для получения дополнительной (намного больше!) информации о неопределенном поведении в подобных выражениях см. Почему эти конструкции используют неопределенное поведение до и после увеличения?
person
Steve Summit
schedule
17.06.2021
6
за 4! и24
за 5! и120
за 6! Вы видите закономерность? - person Weather Vane   schedule 17.06.2021number*factorial(--number)
какое значение будет использоваться для первогоnumber
?number
уменьшается на некоторое время между предыдущей точкой последовательности и--number
. Но никто точно не знает, когда. - person Weather Vane   schedule 17.06.2021number
заменяется на--number
еще до выполнения--number
- person ash54321   schedule 17.06.2021++
и--
могут быть фантастически полезными операторами. Но, пожалуйста, не используйте их, когда они вам не нужны. Вы помните, почему вы написалиfactorial(--number)
в первую очередь? Вы видите, какfactorial(number-1)
делает именно то, что вам нужно? Вам нужно вычесть 1 из значенияnumber
перед передачей его вfactorial()
для рекурсивного вызова, но вам не нужно ничего сохранять обратно вnumber
. Помните, что--
означает не просто вычесть 1. Это означает вычесть 1 и сохранить обратно. - person Steve Summit   schedule 17.06.2021