Чи припиниться ця програма для кожного цілого?


14

У тесті на частину підготовки до GATE виникло питання:

f(n):
     if n is even: f(n) = n/2
     else f(n) = f(f(n-1))

Я відповів "Це закінчиться для всіх цілих чисел", оскільки навіть для деяких негативних цілих чисел воно закінчиться як помилка переповнення стека .

Але мій друг не погодився сказати, що оскільки це не реалізований код, а просто псевдокод, це буде нескінченна рекурсія у випадку якихось негативних цілих чисел.

Яка відповідь правильна і чому?


8
Він не припиняється при n = -1. Переважно теоретичні межі розглядаються в таких випадках.
Глибокий Джоші

9
Якщо переповнення стека розглядатиметься як припинення, то всі програми припиняться, і це перемагає мету цього питання ...
xuq01

10
@ xuq01 while (true);не припиняється і, в чомусь розумному, не спричиняє переповнення стека.
TripeHound

3
@leftaroundabout я, мабуть, не повинен був використовувати " на що-небудь розумне ", тому що це зовсім інший рівень " розумних " ... виявлення та реалізація хвостової рекурсії є приємною (або навіть розумною ), але робити це лише трохи " не розумно" ". Все, що реалізовано while(true);таким чином, що використовує будь-який стек, безумовно, було б нерозумним . Справа в тому, що, якщо ви навмисно не вийшли зі свого шляху бути незграбними, while(true);це не припинить і не спровокує переповнення стека.
TripeHound

14
@ xuq01 Я не думаю, що "руйнування Всесвіту" не вважається вирішенням проблеми зупинки.
TripeHound

Відповіді:


49

Правильна відповідь полягає в тому, що ця функція не припиняється для всіх цілих чисел (конкретно, вона не закінчується на -1). Ваш друг вірно заявив, що це псевдокод, а псевдокод не закінчується при переповненні стека. Псевдокод формально не визначений, але ідея полягає в тому, щоб він робив те, що говориться на бляшанці. Якщо в коді не сказано "припинити з помилкою переповнення стека", помилка переповнення стека не буде.

Навіть якби це була справжня мова програмування, правильною відповіддю все одно було б "не припиняється", якщо тільки використання стека не є частиною визначення мови. Більшість мов не визначають поведінку програм, які можуть переповнювати стек, тому що важко точно знати, скільки стека буде використовувати програма.

Якщо запуск коду на фактичному інтерпретаторі чи компіляторі викликає переповнення стека багатьма мовами, це розбіжність між формальною семантикою мови та реалізацією. Як правило, розуміється, що реалізація мови зробить лише те, що можна зробити на конкретному комп'ютері з обмеженою пам'яттю. Якщо програма вмирає з переповненням стека, ви повинні придбати більший комп'ютер, перекомпілювати систему, якщо потрібно для підтримки всієї цієї пам’яті, та повторіть спробу. Якщо програма не припиняється, можливо, вам доведеться продовжувати робити це назавжди.

Навіть той факт, що програма буде або не переповнює стек, не є чітко визначеним, оскільки деякі оптимізації, такі як оптимізація хвостових викликів та запам'ятовування, можуть дозволити нескінченний ланцюг викликів функцій у постійному обмеженому просторі стека. У деяких специфікаціях мови навіть передбачено, що реалізація виконує оптимізацію хвостових викликів, коли це можливо (це звичайно для функціональних мов програмування). Ця функція f(-1)розширюється до f(f(-2)); зовнішній виклик до f- це хвостовий дзвінок, тому він нічого не натискає на стек, таким чином, f(-2)переходить лише до стека, і він повертається -1, тому стек повертається до того ж стану, в якому він був на початку. Таким чином, з оптимізацією хвостових викликів f(-1)петлі назавжди в постійній пам'яті.


3
Приклад, коли код, перекладений на мову програмування, не призводить до переповнення стека, - Haskell. Це просто let f :: Int -> Int; f n = if even n then n `div` 2 else f (f (n - 1)) in f (-1)
циклічно

5

Якщо ми розглянемо це з точки зору мови С, реалізація може вільно замінити код на код, який дає однаковий результат у всіх випадках, коли оригінал не посилається на невизначене поведінку. Так він може замінити

f(n):
   if n is even: f(n) = n/2
   else f(n) = f(f(n-1))

з

f(n):
   if n is even: f(n) = n/2
   else f(n) = f((n-1) / 2)

Тепер реалізація дозволена застосувати хвостову рекурсію:

f(n):
   while n is not even do n = (n-1) / 2
   f(n) = n/2

І ця петля назавжди тоді і тільки тоді, коли n = -1.


Я думаю, що в C це викликання f(-1)- це невизначена поведінка (реалізація може припускати, що кожен потік або припиняється, або виконує щось інше у короткому списку видів діяльності, який ця функція не виконує), тож компілятор може насправді робити все, що хоче в цьому справи!
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.