La Variabile Globale errno in Linguaggio C

Concetti Chiave
  • errno è una variabile globale di tipo int definita nell'header <errno.h>, utilizzata per segnalare errori nelle funzioni di libreria standard C.
  • Quando una funzione di libreria standard C incontra un errore, imposta errno con un codice di errore specifico, che può essere utilizzato per determinare la natura dell'errore.
  • È buona pratica resettare errno a 0 prima di chiamare una funzione che potrebbe fallire, in modo da poter distinguere tra un errore effettivo e il valore precedente di errno.
  • Alcuni dei valori standard di errno includono EDOM (errore di dominio matematico) e ERANGE (risultato fuori dall'intervallo rappresentabile).
  • La gestione degli errori tramite errno consente di scrivere codice più robusto e resiliente in linguaggio C.

La variabile globale errno

Nella lezione precedente abbiamo visto come gestire gli errori in linguaggio C utilizzando i valori di ritorno delle funzioni. In questa lezione esploreremo un'altra tecnica comune per la gestione degli errori in C, che si basa sull'uso della variabile globale errno.

L'uso di errno discende dalla convenzione adoperata nel sistema operativo UNIX (e anche linux) dove, ogniqualvolta si verifica un errore in una chiamata di sistema, viene impostata una variabile globale chiamata errno con un codice di errore specifico. Questo codice può poi essere utilizzato dal programma per determinare la natura dell'errore e agire di conseguenza.

Qui ci limiteremo a spiegare come funziona errno in linguaggio C e quali valori può assumere secondo lo standard del linguaggio C, tralasciando, invece, le specifiche implementazioni del sistema operativo.

In generale, errno è una variabile globale di tipo int definita nell'header <errno.h> (Anche se può anche essere definita come macro, ma questo è un dettaglio di implementazione). Alcune delle funzioni di libreria standard C, come tutte le funzioni matematiche, quando incontrano un errore, impostano errno con un valore che rappresenta il tipo di errore verificatosi. In altre parole, l'errore non viene segnalato attraverso il valore di ritorno della funzione, ma piuttosto attraverso la variabile globale errno.

Per cui, lo schema generale per l'uso di errno è il seguente:

#include <errno.h>

/* ... */

/* 1. Si resetta errno a 0 prima della chiamata alla funzione */
errno = 0;

/* 2. Si chiama la funzione che può fallire */
valore = funzione_che_puo_fallire(parametri);

/* 3. Si controlla se errno è stato modificato */
if (errno != 0) {
    // Gestisci l'errore in base al valore di errno
} else {
    // Procedi con l'elaborazione normale
}

Notiamo due dettagli importanti in questo schema:

  1. Non c'è bisogno di dichiarare errno, poiché è già definita nell'header <errno.h>.

    Anzi, è un errore ridichiararla nel proprio codice, in quanto potrebbe oscurare la definizione originale e causare comportamenti imprevisti.

  2. La variabile errno viene modificata solo quando si verifica un errore.

    Se la funzione chiamata ha successo, errno rimane invariato. Per questo motivo, è buona pratica resettare errno a 0 prima di chiamare una funzione che potrebbe fallire, in modo da poter distinguere tra un errore effettivo e il valore precedente di errno.

Ad esempio, se dobbiamo chiamare due funzioni che possono fallire, dobbiamo resettare errno prima di ciascuna chiamata. In caso contrario, potremmo confondere un errore della prima funzione con un errore della seconda:

#include <errno.h>

/* ... */
errno = 0;
valore1 = funzione_che_puo_fallire1(parametri1);

if (errno != 0) {
    // Gestisci l'errore di funzione_che_puo_fallire1
}

errno = 0;
valore2 = funzione_che_puo_fallire2(parametri2);

if (errno != 0) {
    // Gestisci l'errore di funzione_che_puo_fallire2
}
Nota

Nessuna funzione di libreria standard C imposta errno a 0

È importante notare che nessuna funzione della libreria standard C imposta errno a 0 in caso di successo. Pertanto, è responsabilità del programmatore resettare errno a 0 prima di chiamare una funzione che potrebbe fallire, se si intende utilizzare errno per rilevare errori.

Esempio: Radice quadrata

Vediamo un esempio pratico di utilizzo di errno con la funzione sqrt della libreria matematica standard C, che calcola la radice quadrata di un numero.

La funzione sqrt restituisce un valore di tipo double, ma se viene chiamata con un numero negativo, non può calcolare la radice quadrata reale e imposta errno a EDOM, che indica un errore di dominio matematico.

Quindi, possiamo scrivere un programma che utilizza sqrt e gestisce l'errore tramite errno:

#include <stdio.h>
#include <math.h>
#include <errno.h>

int main() {
    double numero;

    printf("Inserisci un numero per calcolare la sua radice quadrata: ");
    scanf("%lf", &numero);

    errno = 0; // Resetta errno prima della chiamata
    double risultato = sqrt(numero);

    if (errno == EDOM) {
        printf("Errore: Impossibile calcolare la "
               "radice quadrata di un numero negativo.\n");
    } else {
        printf("La radice quadrata di %.2f è %.2f\n", numero, risultato);
    }

    return 0;
}

Si noti che per compilare questo programma, potrebbe essere necessario collegare la libreria matematica utilizzando l'opzione -lm con il compilatore GCC:

gcc -o esempio_sqrt esempio_sqrt.c -lm

Nell'esempio sopra, se l'utente inserisce un numero negativo, sqrt imposterà errno a EDOM, e il programma stamperà un messaggio di errore appropriato. Altrimenti, stamperà il risultato della radice quadrata.

Valori di errno

I valori che la variabile globale errno può assumere sono definiti nell'header <errno.h>.

In generale, il sistema operativo UNIX (e Linux) e lo standard POSIX definiscono diversi codici di errore comuni che vengono impostati in errno. Lo standard C, tuttavia, ne definisce solo alcuni, lasciando agli ambienti specifici l'implementazione di ulteriori codici di errore.

I valori standard di errno definiti nello standard C sono delle macro costanti, tra cui:

  • EDOM: Viene impostato quando un'operazione matematica riceve un argomento fuori dal dominio valido (ad esempio, calcolare la radice quadrata di un numero negativo).
  • ERANGE: Viene impostato quando il risultato di un'operazione matematica è fuori dall'intervallo rappresentabile (ad esempio, un overflow). Ad esempio, se passiamo il valore 1000 alla funzione exp, che calcola l'esponenziale, il risultato sarà troppo grande per essere rappresentato come un double, in quanto e^{1000} è un numero estremamente grande non rappresentabile da una variabile di tipo double. In questo caso, errno verrà impostato a ERANGE.

Molte funzioni matematiche potrebbero incontrare entrambe gli errori di sopra. Quindi, è buona pratica controllare entrambi i valori di errno dopo aver chiamato una funzione matematica.