Questo programma terminerà per ogni numero intero?


14

In un test parziale per la preparazione di GATE c'era una domanda:

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

Ho risposto "Terminerà per tutti i numeri interi", perché anche per alcuni numeri interi negativi, verrà terminato come Errore di overflow dello stack .

Ma il mio amico non era d'accordo nel dire che poiché questo non è un codice implementato e solo uno pseudocodice, sarà una ricorsione infinita in caso di numeri interi negativi.

Quale risposta è corretta e perché?


8
Non è terminato per n = -1. In questi casi vengono considerati principalmente limiti teorici.
Deep Joshi,

9
Se lo overflow dello stack deve essere considerato come una terminazione, allora tutti i programmi verranno chiusi e
vanificherà

10
@ xuq01 while (true);non verrà terminato né, in alcun modo sensato, causerà l'overflow dello stack.
TripeHound,

3
@leftaroundabout Probabilmente non avrei dovuto usare " su qualcosa di sensato " perché è un livello completamente diverso di " sensibile " ... individuare e attuare la ricorsione della coda è bello (o persino sensato ), ma non farlo è solo leggermente " non sensato ". Qualsiasi cosa implementata while(true);in modo da utilizzare qualsiasi stack non sarebbe assolutamente sensata . Il punto è che, a meno che tu non abbia intenzionalmente fatto di tutto per essere imbarazzante, while(true);non terminerà né scatenerà l'overflow dello stack.
TripeHound,

14
@ xuq01 Non credo che la "distruzione dell'universo" sia considerata una soluzione al problema dell'arresto.
TripeHound,

Risposte:


49

La risposta corretta è che questa funzione non termina per tutti i numeri interi (in particolare, non termina con -1). Il tuo amico ha ragione nel dichiarare che questo è pseudocodice e che lo pseudocodice non termina in caso di overflow dello stack. Lo pseudocodice non è definito formalmente, ma l'idea è che faccia ciò che è scritto sulla scatola. Se il codice non dice "termina con un errore di overflow dello stack", non si verifica alcun errore di overflow dello stack.

Anche se questo fosse un vero linguaggio di programmazione, la risposta corretta sarebbe comunque "non termina", a meno che l'uso di uno stack non faccia parte della definizione del linguaggio. La maggior parte delle lingue non specifica il comportamento dei programmi che potrebbero traboccare dallo stack, poiché è difficile sapere con esattezza lo stack che verrà utilizzato da un programma.

Se l'esecuzione del codice su un vero interprete o compilatore provoca un overflow dello stack, in molte lingue, questa è una discrepanza tra la semantica formale della lingua e l'implementazione. Resta generalmente inteso che le implementazioni di un linguaggio faranno solo ciò che può essere fatto su un computer concreto con memoria finita. Se il programma si interrompe con un overflow dello stack, dovresti acquistare un computer più grande, ricompilare il sistema se necessario per supportare tutta quella memoria e riprovare. Se il programma non è terminato, potrebbe essere necessario continuare a farlo per sempre.

Anche il fatto che un programma trabocchi o meno lo stack non è ben definito, dal momento che alcune ottimizzazioni come l' ottimizzazione delle chiamate di coda e la memoizzazione possono consentire una catena infinita di chiamate di funzione nello spazio dello stack con limite costante. Alcune specifiche linguistiche impongono addirittura che le implementazioni eseguano l'ottimizzazione delle chiamate di coda quando possibile (questo è comune nei linguaggi di programmazione funzionale). Per questa funzione, si f(-1)espande in f(f(-2)); la chiamata esterna a fè una chiamata di coda in modo che non spinga nulla nello stack, quindi f(-2)va solo nello stack e questo ritorna -1, quindi lo stack torna allo stesso stato in cui era all'inizio. Pertanto, con l'ottimizzazione delle chiamate di coda, il f(-1)loop continua per sempre nella memoria costante.


3
Un esempio in cui il codice tradotto in un linguaggio di programmazione non provoca alcun overflow dello stack è Haskell. Si let f :: Int -> Int; f n = if even n then n `div` 2 else f (f (n - 1)) in f (-1)
svolge a

5

Se consideriamo questo in termini di linguaggio C, un'implementazione è libera di sostituire il codice con il codice che produce lo stesso risultato in tutti i casi in cui l'originale non invoca comportamenti indefiniti. Quindi può sostituire

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

con

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

Ora l'implementazione è autorizzata ad applicare la ricorsione della coda:

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

E questo scorre per sempre se e solo se n = -1.


Penso, in C, che invocare f(-1)sia un comportamento indefinito (l'implementazione può presumere che ogni thread sia terminato o faccia qualcos'altro in un breve elenco di attività che questa funzione non svolge), quindi il compilatore può effettivamente fare tutto ciò che vuole in quel Astuccio!
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.