Рекурсивная функция для нахождения факториала числа

Я получаю вывод 24, который является факториалом для 4, но я должен получить вывод для 5 факториала, который равен 120.

#include <stdio.h>
int factorial(int number){
    if(number==1){
        return number;
    }
    return number*factorial(--number);
}
int main(){
    int a=factorial(5);
    printf("%d",a);
}

person ash54321    schedule 17.06.2021    source источник
comment
Это дает 6 за 4! и 24 за 5! и 120 за 6! Вы видите закономерность?   -  person Weather Vane    schedule 17.06.2021
comment
да, но я хочу знать, почему мой код неверен, где я сделал что-то не так, если я заменю --number на число-1, я получу правильный ответ, хотя   -  person ash54321    schedule 17.06.2021
comment
В number*factorial(--number) какое значение будет использоваться для первого number? number уменьшается на некоторое время между предыдущей точкой последовательности и --number. Но никто точно не знает, когда.   -  person Weather Vane    schedule 17.06.2021
comment
почему number заменяется на --number еще до выполнения --number   -  person ash54321    schedule 17.06.2021
comment
Поскольку предварительный декремент происходит через какое-то время после предыдущей точки последовательности. И функция вызывается до выполнения умножения.   -  person Weather Vane    schedule 17.06.2021
comment
Еще один совет. ++ и -- могут быть фантастически полезными операторами. Но, пожалуйста, не используйте их, когда они вам не нужны. Вы помните, почему вы написали factorial(--number) в первую очередь? Вы видите, как factorial(number-1) делает именно то, что вам нужно? Вам нужно вычесть 1 из значения number перед передачей его в factorial() для рекурсивного вызова, но вам не нужно ничего сохранять обратно в number. Помните, что -- означает не просто вычесть 1. Это означает вычесть 1 и сохранить обратно.   -  person Steve Summit    schedule 17.06.2021


Ответы (1)


Ваша программа имеет неопределенное поведение.

При первом вызове 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
comment
но это работает, если мы использовали number - 1 вместо --number - person IcanCode; 17.06.2021
comment
@IcanCode number - 1 не изменяет значение number, а --number изменяет. - person Weather Vane; 17.06.2021
comment
я использую vscode, но я не вижу никаких предупреждений или предупреждений неопределенное поведение - person ash54321; 17.06.2021
comment
Может быть, он просто не может обнаружить это неопределенное поведение в частности, или, может быть, он ожидает, что вы делаете это намеренно, потому что вы знаете, что будет делать ваш компилятор, и вы не собираетесь использовать другой компилятор. - person altermetax; 17.06.2021
comment
@ash54321 ash54321 К сожалению, явные предупреждения о неопределенном поведении довольно редки. Вы должны знать, чтобы избежать этого самостоятельно. Компилятор не всегда предупредит вас -- фактически в некоторых случаях он не может предупредить вас. - person Steve Summit; 17.06.2021
comment
Разве это не технически неопределенное поведение, и все еще допустимое с точки зрения компиляторов. Число изменяется в выражении только один раз, но компилятор может делать это в любом порядке. - person jo-art; 18.06.2021
comment
@jo-art Это не определено. См. цитаты в связанном вопросе . - person Steve Summit; 18.06.2021
comment
@ash54321 ash54321 Неопределенное поведение похоже на пешую прогулку по большому шоссе. Вам может это сойти с рук, особенно посреди ночи, когда нет пробок. Полицейский может сделать вам предупреждение. Полицейский может дать вам цитату. Или вас может сбить полуприцеп. - person Steve Summit; 18.06.2021