C++ Funzione ricorsiva

Hale2
Salve a tutti :)
Ho un dubbio sulle funzioni ricorsive. C'è un esercizio dove devo scrivere una funzione ricorsiva che calcola la somma dei soli elementi multipli di 3 che si trovano in posizione pari di una lista di elementi di tipo elem. si assume che il primo elemento della lista sia in posizione 1.
struct elem
{
int info;
elem*pun;
};

io l'ho svolto in questa maniera:

int ricorsiva(elem*testa, int conta=0)
{
if(testa==NULL)
return conta;
if(testa->pun-==NULL)
return conta;
if(testa->pun->info%3==0)
conta=conta+testa->pun->info;
ricorsiva(testa->pun->pun,conta)
}

Questa funzione dovrebbe essere sbagliata perché dovrei scrivere return ricorsiva( ..... ) dato che la funzione non è void. Ma se eseguo il programma come l'ho scritto io funziona..... Sapete dirmi PERCHE'????

Grazieee >.<

Risposte
apatriarca
Non so perché funziona, sono in effetti abbastanza certo che in molti compilatori quel codice restituirebbe un errore di compilazione. Quale compilatore stai usando? Su quale sistema?

claudio862
Dal punto di vista formale, quando una funzione non void termina senza un'istruzione return, il comportamento è indefinito, quindi all'apparenza può benissimo funzionare.
In pratica, potrebbe succedere che il compilatore prenda come valore da restituire l'ultima espressione, oppure il valore dell'ultima variabile sullo stack (conta). Bisognerebbe vedere il codice generato. Il punto è che il risultato non è mai garantito, quindi quella funzione è sempre e comunque sbagliata, anche se non sembra.

P.S.
Abilitando i warning questo errore si nota immediatamente.

apatriarca
Ritengo che, se si è principianti è normalmente una buona idea abilitare TUTTI i warning e forse addirittura considerarli come errore. In gcc le opzioni dovrebbero essere -Wall e -Werror (se si deve fare un codice che funzioni anche su altri sistemi forse si potrebbe anche aggiungere -pedantic-error o almeno -pedantic..). Su altri compilatori le opzioni si settano ovviamente in modo diverso.

@claudio86: immagino che l'effetto dipenda sostanzialmente dal tipo di convenzione usata dalla funzione per ricevere i parametri e restituire il valore di ritorno. Siccome è a volte possibile cambiare questa convenzione, direi che non è possibile neanche fare affidamento sul comportamento del singolo compilatore in questo caso..
EDIT: Ripensadoci il valore di ritorno potrebbe anche semplicemente essere il valore di un qualche registro e quindi potrebbe addirittura cambiare tra diverse versioni delle funzioni in modo del tutto casuale. Insomma, questo è il genere di situazioni che vanno evitate a tutti i costi.

Hale2
I warning sono già abilitati ma grazie per le risposte :) Comunque sto usando il terminale su Debian... Magari con un altra lista mi segnalerebbe l'errore

claudio862
"apatriarca":
Ritengo che, se si è principianti è normalmente una buona idea abilitare TUTTI i warning e forse addirittura considerarli come errore. In gcc le opzioni dovrebbero essere -Wall e -Werror (se si deve fare un codice che funzioni anche su altri sistemi forse si potrebbe anche aggiungere -pedantic-error o almeno -pedantic..). Su altri compilatori le opzioni si settano ovviamente in modo diverso.

g++ -std=c++98 -pedantic -Wall -Wextra file.cpp -o file.exe

Bisogna specificare esplicitamente di usare lo standard C++98, perché GCC di default NON usa C++ standard (e continua a non farlo se non si usa anche -pedantic o -pedantic-error). I valori utili del parametro -std sono c++98, c++0x, c++11, c89, c99, c11 (a seconda di quale versione del linguaggio si vuole usare). Qui avevo scritto qualche appunto su vari compilatori (mi sbagliavo però: Clang supporta C++11).


@claudio86: immagino che l'effetto dipenda sostanzialmente dal tipo di convenzione usata dalla funzione per ricevere i parametri e restituire il valore di ritorno. Siccome è a volte possibile cambiare questa convenzione, direi che non è possibile neanche fare affidamento sul comportamento del singolo compilatore in questo caso..
EDIT: Ripensadoci il valore di ritorno potrebbe anche semplicemente essere il valore di un qualche registro e quindi potrebbe addirittura cambiare tra diverse versioni delle funzioni in modo del tutto casuale. Insomma, questo è il genere di situazioni che vanno evitate a tutti i costi.

In realtà credo sia anche più arbitrario. Qualunque convenzione usi il compilatore, il chiamante leggerà un risultato. Cosa ci sia dentro il risultato dipende da cosa ci avrà messo il compilatore, e se non c'è un return, potrebbe davvero essere qualsiasi cosa.

Un problema che invece potrebbe dipendere dalla convenzione di chiamata è ad esempio dichiarare una funzione come se restituisse qualcosa, mentre invece è una funzione void. Se il compilatore usa i registri per passare parametri e valore restituito, potrebbe funzionare lo stesso. Se invece passa i parametri sullo stack, probabilmente quest'ultimo verrebbe corrotto, e si avrebbe un crash quasi immediato.
Questo è un esempio in cui l'errore si potrebbe manifestare solo abilitando le ottimizzazioni, ma il codice sarebbe sempre e comunque sbagliato.

apatriarca
Ogni convenzione di chiamata definisce abbastanza esplicitamente in che modo vengono passati i valori di ritorno, per cui in generale credo che sia possibile, data una convenzione di chiamata, prevedere in che modo il valore viene letto dal codice chiamante. Ma certo il valore letto è in generale non prevedibile e potrebbe non essere neanche stato scritto dalla funzione stessa ma essere già presente precedentemente. Per certi versi, il caso in cui questo comportamento causi il crash dell'applicazione è da considerarsi probabilmente quello più favorevole. Un caso come questo in cui il codice sembra funzionare è forse quello più problematico perché potrebbe smettere di colpo di funzionare e restituire valori a caso, oppure funzionare solo in alcune situazioni o per alcuni input.. Si usano i warning del compilatore proprio per evitare situazioni del genere.

claudio862
"apatriarca":
Per certi versi, il caso in cui questo comportamento causi il crash dell'applicazione è da considerarsi probabilmente quello più favorevole. Un caso come questo in cui il codice sembra funzionare è forse quello più problematico perché potrebbe smettere di colpo di funzionare e restituire valori a caso, oppure funzionare solo in alcune situazioni o per alcuni input.

Esatto:
"Scott Meyers, Effective C++, 2nd ed.":
We all know what "undefined" means: it means it works during development, it works during testing, and it blows up in your most important customers' faces.

giuscri
[ot]
"claudio86":
[quote="Scott Meyers, Effective C++, 2nd ed."]We all know what "undefined" means: it means it works during development, it works during testing, and it blows up in your most important customers' faces.
[/quote]
Un mio professore dice che quando si legge undefined bisogna sempre pensare alle peggiori catastrofi naturali: deriva dei continenti, tsunami, etc. :lol:[/ot]

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.