HOWTO · C++

Come risolvere l’errore «control reaches end of non-void function» in C++

Scopri perché una funzione C++ non void può raggiungere la parentesi graffa di chiusura, come coprire ogni percorso di ritorno e quando può essere utile un valore di ripiego o un avviso del compilatore.

In questa pagina

Una funzione C++ il cui tipo restituito non è void deve produrre un valore in ogni percorso raggiungibile; l’eccezione è main, che se raggiunge la fine restituisce implicitamente 0. Se il flusso raggiunge la parentesi graffa di chiusura di un’altra funzione non void senza un valore return, il comportamento è indefinito. A seconda del codice e delle opzioni di avviso, il compilatore può anche segnalare il percorso. Per risolvere il problema, esamina ogni ramo e fai in modo che il valore restituito corrisponda allo scopo della funzione.

Capire l’errore

Una funzione non void promette di restituire un valore del tipo dichiarato. La diagnosi del compilatore, spesso chiamata “control reaches end of non-void function”, segnala un percorso che può arrivare alla parentesi graffa di chiusura senza restituire quel valore. Il problema non consiste semplicemente nell’assenza di un’istruzione alla fine: una condizione precedente può lasciare alcuni input senza risultato. Per esempio, int add(int a, int b) { if (a > b) return a + b; } non restituisce alcun valore quando a <= b. Se una funzione non void diversa da main termina in questo modo, il comportamento è indefinito: non è garantito che si verifichi un arresto anomalo o si ottenga un risultato specifico.

Esegui questo esempio:

#include <iostream>

int add(int a, int b) {
    return a + b;
}

int main() {
    std::cout << add(5, 3) << '\n';
}
8

Con questo input il programma restituisce la somma. La versione inline qui sopra mostra la causa della diagnosi; la versione eseguibile la risolve restituendo un valore per ogni input.

Assicurarsi che tutti i percorsi restituiscano un valore

Esamina le condizioni e stabilisci cosa dovrebbe significare ciascun caso. Nell’addizione, entrambi gli ordinamenti degli operandi devono produrre la loro somma. Se la condizione serve per altre operazioni, un return finale può gestire il percorso rimanente; se entrambi i rami svolgono lo stesso lavoro, elimina la condizione:

#include <iostream>

int add(int a, int b) {
    if (a > b) {
        return a + b;
    }
    return a + b;
}

int main() {
    std::cout << add(3, 5) << '\n';
}

Una struttura completa di rami è utile quando i casi hanno significati diversi. Questo esempio restituisce l’operando maggiore e copre entrambi i percorsi:

#include <iostream>

int larger(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

int main() {
    std::cout << larger(5, 3) << '\n';
}

Esamina anche le condizioni annidate e le istruzioni switch: ogni percorso di una funzione non void deve restituire un valore oppure evitare di raggiungere la parentesi graffa di chiusura.

Usare valori di ritorno predefiniti

Un return finale può fungere da valore di ripiego per i casi che non soddisfano una condizione precedente. Usalo solo se quel valore rispetta il contratto della funzione. Per esempio, questa ricerca restituisce un punteggio noto quando trova il nome corrispondente e 0 quando il nome è assente; questo valore di ripiego è appropriato solo se l’applicazione definisce come zero il punteggio di un nome assente.

#include <iostream>
#include <string>

int scoreFor(const std::string& name) {
    if (name == "Ada") {
        return 10;
    }
    return 0;
}

int main() {
    std::cout << scoreFor("Ada") << '\n';
    std::cout << scoreFor("Lin") << '\n';
}

Se l’assenza rappresenta un esito distinto, restituire 0 può nascondere questa differenza. Scegli un tipo restituito e un risultato che esprimano il contratto effettivo, per esempio un valore opzionale o uno stato esplicito. Non aggiungere un valore di ripiego arbitrario solo per eliminare una diagnosi.

Eseguire il debug con gli avvisi del compilatore

Abilita gli avvisi durante la compilazione, così il compilatore può aiutarti a individuare un percorso da esaminare. Per esempio, g++ -Wall -Wextra -Wreturn-type your_code.cpp -o your_program chiede a GCC di segnalare diversi avvisi, incluse, quando applicabile, le diagnosi relative ai percorsi di ritorno. La disponibilità e la formulazione degli avvisi variano in base al compilatore, alla versione, alle opzioni e al codice; un avviso è un indizio per esaminare il flusso di controllo, non la prova che tutti i compilatori trattino il codice allo stesso modo.

Controlla ogni ramo, comprese le istruzioni if annidate e ogni caso di switch. Poi stabilisci cosa dovrebbe fare la funzione per ciascun input e restituisci un valore coerente con quel comportamento. Durante lo sviluppo, lascia attivi gli avvisi per individuare più facilmente percorsi simili.

Conclusione

Quando una funzione C++ non void può raggiungere la parentesi graffa di chiusura senza restituire un valore, esamina il percorso e correggi la logica della funzione. Copri ogni caso con un valore return coerente con lo scopo della funzione. Usa un valore di ripiego solo se ha un significato definito e considera gli avvisi del compilatore un aiuto per individuare i percorsi da controllare.

Domande frequenti

Che cosa causa la diagnosi “control reaches end of non-void function”?

Un percorso raggiungibile di una funzione che restituisce un valore arriva alla parentesi graffa di chiusura senza restituire un valore. I compilatori possono segnalare questa condizione, ma la diagnosi e il comportamento della compilazione dipendono dal compilatore e dalle opzioni.

Ogni ramo di un if deve contenere un return?

No. Un return dopo la condizione può gestire il percorso rimanente. L’importante è che nessun percorso raggiungibile arrivi alla fine senza restituire un valore.

Devo aggiungere return 0; per far scomparire l’avviso?

Solo se zero è un risultato valido secondo il contratto della funzione. Altrimenti, scegli un risultato o un tipo restituito che rappresenti correttamente il caso mancante.

Un avviso del compilatore può individuare il percorso senza valore restituito?

Può segnalare una funzione che potrebbe terminare senza restituire un valore. Usalo per esaminare il flusso di controllo; il testo e il comportamento degli avvisi variano tra compilatori e configurazioni.