Potrebbe aiutarti a pensare a questo in termini di come le ottimizzazioni di coda sono effettivamente implementate. Questo non fa parte della definizione, ovviamente, ma motiva la definizione.
In genere quando viene chiamata una funzione, il codice chiamante memorizzerà tutti i valori di registro di cui avrà bisogno in seguito, nello stack. Memorizzerà anche un indirizzo di ritorno, indicando l'istruzione successiva dopo la chiamata. Farà tutto ciò che è necessario per garantire che il puntatore dello stack sia impostato correttamente per il destinatario. Quindi salterà all'indirizzo di destinazione [*] (in questo caso, la stessa funzione). Al ritorno, sa che il valore di ritorno è nel posto specificato dalla convenzione di chiamata (registro o stack).
Per una chiamata di coda, il chiamante non lo fa. Ignora qualsiasi valore di registro, perché sa che non ne avrà più bisogno in seguito. Imposta il puntatore dello stack in modo che il callee utilizzi lo stesso stack del chiamante e non si imposta come indirizzo di ritorno, ma salta semplicemente all'indirizzo di destinazione.Così, il chiamato sovrascrive la stessa regione di stack, metterà il suo valore di ritorno nella stessa posizione in cui il chiamante avrebbe inserito il suo valore di ritorno, e quando ritorna, non ritornerà al suo chiamante, ma ritornerà al suo chiamante chiamante.
Pertanto, in modo informale, una funzione è ricorsiva in coda quando è possibile che si verifichi un'ottimizzazione della chiamata di coda e quando l'obiettivo della chiamata di coda è la funzione stessa. L'effetto è più o meno lo stesso come se la funzione contenesse un ciclo, e invece di chiamare se stesso, la chiamata di coda salta all'inizio del ciclo. Ciò significa che non ci devono essere variabili necessarie dopo la chiamata (e in effetti nessuna "operazione da fare", che in una lingua come C++ non significa nulla da distruggere), e il valore di ritorno della coda deve essere restituito dal chiamante.
Questo è tutto per la ricorsione di coda semplice/banale. Ci sono trasformazioni che possono essere usate per creare qualcosa di ricorsivo di coda che non è già, per esempio introducendo parametri extra, che memorizzano alcune informazioni usate dal livello di "più basso" di ricorsione, per fare un lavoro che altrimenti sarebbe fatto l'uscita". Così, per esempio:
int triangle(int n) {
if (n == 0) return 0;
return n + triangle(n-1);
}
può essere fatto ricorsiva in coda, sia da parte del programmatore o automaticamente da un compilatore abbastanza intelligente, in questo modo:
int triangle(int n, int accumulator = 0) {
if (n == 0) return accumulator;
return triangle(n-1, accumulator + n);
}
Pertanto, l'ex funzione potrebbe essere descritto come " coda ricorsiva "da qualcuno che sta parlando di un linguaggio/compilatore abbastanza intelligente. Preparati per quell'uso variante.
[*] La memorizzazione di un indirizzo di ritorno, lo spostamento del puntatore dello stack e il salto, possono o non possono essere racchiusi in un unico codice operativo dall'architettura, ma anche se ciò non accade normalmente.