Funzioni e Valori di Ritorno in Linguaggio C

Apprendiamo in questa lezione come funzionano i valori di ritorno in linguaggio C. In particolare, vediamo come restituire un valore da una funzione e come utilizzare l'istruzione return.

Valori di ritorno

Abbiamo detto che, in informatica, si usa distinguere tra procedure e funzioni.

Una procedura può essere vista come una macro-istruzione, ossia un aggregato di istruzioni più semplici che vengono eseguite sequenzialmente.

Viceversa, una funzione è un'entità che prende in ingresso dei parametri, effettua delle operazioni su di essi e restituisce un valore. Possiamo vedere una funzione come una scatola nera che prende in ingresso dei dati e restituisce un risultato.

Il linguaggio C non effettua, come fanno altri linguaggi, una distinzione netta tra procedure e funzioni. In C una procedura è semplicemente una funzione che non restituisce alcun valore, ossia è una funzione di tipo void. Viceversa, una funzione è una funzione che restituisce un valore.

Definizione

Procedure in Linguaggio C

Una Procedura in linguaggio C è una funzione con tipo di ritorno void:

void nome_procedura(lista_parametri) {
    // corpo della procedura
}

Una procedura è anche nota come funzione void nel gergo del linguaggio C.

Definizione

Funzioni in Linguaggio C

Una Funzione in linguaggio C è una funzione con tipo di ritorno diverso da void:

tipo_di_ritorno nome_funzione(lista_parametri) {
    // corpo della funzione
    return valore_di_ritorno;
}

Nelle lezioni precedenti abbiamo già visto come definire delle funzioni che restituiscono un risultato. Adesso studiamo i dettagli di questa operazione utilizzando l'istruzione return.

L'istruzione return

Una qualunque funzione non void, ossia che restituisca un risultato, deve necessariamente contenere un'istruzione return che restituisca il valore di ritorno.

La sintassi dell'istruzione return è la seguente:

return espressione;

L'espressione dell'istruzione return può essere una costante, una variabile o un'espressione più complessa.

Ad esempio, è molto comune trovare espressioni return così scritte:

return 0;
return x;
return x + y;

Anche espressioni più complesse possono essere usate. Non è raro, ad esempio, avere espressioni condizionali all'interno di un'istruzione return:

return (x > y) ? x : y;

In questo esempio, viene valutata l'espressione (x > y). Se il risultato è vero, viene restituito il valore di x, altrimenti viene restituito il valore di y.

Un punto importante dell'istruzione return è che, una volta eseguita, il controllo della funzione passa al chiamante. In altre parole, l'istruzione return termina l'esecuzione della funzione e restituisce il controllo al chiamante.

Poiché un'istruzione return può apparire in qualunque punto del corpo della funzione, è possibile che una funzione contenga più di un'istruzione return. In questo caso, solo una delle istruzioni return verrà eseguita.

Prendiamo un esempio per chiarire questo concetto:

int massimo(int x, int y) {
    if (x > y) {
        return x;
    } else {
        return y;
    }
}

In questo esempio, la funzione massimo restituisce il massimo tra due numeri interi x e y. Se x è maggiore di y, viene restituito x, altrimenti viene restituito y. Per cui la funzione ha due istruzioni return, ma solo una di esse verrà eseguita.

Avremmo potuto scrivere la funzione di sopra anche in questo modo:

int massimo(int x, int y) {
    if (x > y) {
        return x;
    }

    return y;
}

La funzione massimo così realizzata, funziona comunque perché, se x è maggiore di y, viene restituito x e la funziona termina. Ossia, la funzione non continua la propria esecuzione dopo l'istruzione return x; quindi l'istruzione return y; non verrà mai eseguita.

Definizione

Istruzione return

L'istruzione return è utilizzata per restituire un valore da una funzione. L'istruzione return è seguita da un'espressione che rappresenta il valore di ritorno:

return espressione;

Quando viene eseguita un'istruzione return, il controllo della funzione passa al chiamante.

Una funzione con tipo di ritorno diverso da void deve necessariamente avere un'istruzione return al proprio interno. In caso contrario, il controllo della funzione raggiunge la fine della stessa ed il valore di ritorno sarà casuale. Il problema potrebbe diventare catastrofico nel momento in cui si cerca di utilizzare il valore di ritorno di una funzione che non ha restituito alcun valore. In tal caso il comportamento del programma diventa impredicibile.

Ad esempio:

int somma(int x, int y) {
    int risultato = x + y;
    /* ERRORE: manca l'istruzione return */
}

In questo esempio, la funzione somma calcola la somma tra due numeri interi x e y, ma non restituisce alcun valore. Se proviamo a memorizzarne il risultato in una variabile, il risultato sarà casuale e il comportamento del programma sarà imprevedibile:

int risultato = somma(3, 4);
/* Da qui in poi il comportamento del programma è imprevedibile */

Molti compilatori, tra cui il compilatore GCC, avvertono dell'errore di mancanza di return in una funzione non void. Ad esempio, il compilatore GCC restituisce il seguente errore:

error: control reaches end of non-void function [-Werror=return-type]

Questo errore, in realtà, è molto più comune di quello che sembra. Ad esempio, quando una funzione contiene una serie di istruzioni if-else e non tutte le possibili casistiche sono state considerate, potrebbe presentarsi il caso in cui nessuna delle istruzioni return venga eseguita. In tal caso, il compilatore avverte dell'errore.

Ad esempio:

int massimo(int x, int y) {
    if (x > y) {
        return x;
    } else if (y > x) {
        return y;
    }
    /* ERRORE: manca l'istruzione return */
}

In questo esempio sembra a prima vista che abbiamo considerato tutte le casistiche possibili, ma in realtà non è così. Se x e y sono uguali, nessuna delle due istruzioni return verrà eseguita. In tal caso, il compilatore avverte dell'errore.

Nota

Mancanza di return in una funzione non void

Una funzione con tipo di ritorno diverso da void deve necessariamente contenere un'istruzione return. In caso contrario, il controllo della funzione raggiunge la fine della stessa ed il valore di ritorno sarà casuale. Il comportamento del programma diventa impredicibile.

Bisogna sempre controllare che tutte le casistiche possibili siano state considerate e che tutte le possibili casistiche abbiano un'istruzione return associata.

In ogni caso, i compilatori moderni avvertono dell'errore di mancanza di return in una funzione non void.

Tipo del Valore di Ritorno

In linea di principio, l'espressione dell'istruzione return deve essere dello stesso tipo del tipo di ritorno previsto dalla funzione.

Ad esempio:

float funzione() {
    return 3.14f;
}

In tal caso l'espressione 3.14f è di tipo float, che è lo stesso tipo di ritorno della funzione funzione.

Nel caso in cui ciò non avviene si possono verificare due casi:

  1. Il compilatore tenta una conversione implicita se possibile.

    Ad esempio, se la funzione ha come tipo di ritorno int ma l'espressione dell'istruzione return è di tipo float, il compilatore effettua una conversione implicita da float a int.

    int funzione() {
        return 3.14f;
    }
    

    Questo risulta possibile perché il compilatore è in grado di convertire un valore float in un valore int. Ovviamente questa conversione viene segnalata come warning dal compilatore in quanto convertire un valore float in un valore int comporta la perdita della parte decimale.

    Il compilatore GCC, ad esempio, restituisce il seguente warning:

    warning: implicit conversion from 'float' to 'int'
    

    Viceversa, se la conversione non comporta perdita di informazione, il compilatore non restituisce alcun warning.

  2. Se la conversione implicita non è possibile, il compilatore restituisce un errore.

    Prendiamo l'esempio che segue:

    int funzione() {
        return "ciao";
    }
    

    In questo caso, l'espressione dell'istruzione return è una stringa, ossia un array di caratteri. Il tipo di ritorno della funzione funzione è int, che è un tipo di dato diverso da char[]. In questo caso, il compilatore restituisce un errore:

    error: return makes integer from pointer without a cast
    

Ricapitolando:

Definizione

Tipo del Valore di Ritorno

L'espressione dell'istruzione return deve essere dello stesso tipo del tipo di ritorno previsto dalla funzione. In caso contrario:

  1. Il compilatore tenta una conversione implicita se possibile.
  2. Se la conversione implicita non è possibile, il compilatore restituisce un errore.

return e funzioni void

Abbiamo detto che una funzione void non restituisce alcun valore. Quindi, in questo caso, un'istruzione return non è necessaria.

Tuttavia, è possibile utilizzare un'istruzione return in una funzione void. In tal caso, l'istruzione return termina l'esecuzione della funzione e restituisce il controllo al chiamante. Quando si usa per questi casi, l'istruzione return non deve essere seguita da alcuna espressione.

La sintassi diventa:

return;

L'utilità di usare una return in una funzione void è che, in caso di errori o condizioni particolari, si può terminare l'esecuzione della funzione e restituire il controllo al chiamante.

Ad esempio, supponiamo di avere una funzione void che pretende in ingresso un numero intero positivo. Se il numero in ingresso è negativo, possiamo terminare l'esecuzione della funzione e restituire il controllo al chiamante:

void funzione(int x) {
    if (x < 0) {
        printf("Errore: il numero deve essere positivo\n");
        return;
    }

    // corpo della funzione
}

In questo esempio, se il numero x è minore di zero, viene stampato un messaggio di errore e l'esecuzione della funzione termina.

Definizione

return in Funzioni void

Una funzione void può contenere al proprio interno un'istruzione return. In tal caso, l'istruzione return termina l'esecuzione della funzione e restituisce il controllo al chiamante.

Usata in questo contesto l'istruzione return non deve essere seguita da alcuna espressione:

return;

In Sintesi

Abbiamo approfondito, in questa lezione, l'uso dell'istruzione return in una funzione:

  • L'istruzione return è utilizzata per restituire un valore da una funzione;
  • Una funzione con tipo di ritorno diverso da void deve necessariamente contenere almeno un'istruzione return;
  • L'espressione dell'istruzione return deve essere dello stesso tipo del tipo di ritorno previsto dalla funzione;
  • Se la conversione implicita non è possibile, il compilatore restituisce un errore;
  • Una funzione void non restituisce alcun valore, ma può contenere un'istruzione return per terminare in modo anticipato l'esecuzione della funzione stessa.