Gestione degli errori da chiamate di sistema e funzioni di libreria
Le chiamate di sistema e le funzioni di libreria restituiscono sempre un valore che indica il risultato dell'operazione. È fondamentale controllare questi valori per rilevare eventuali errori.
Gestione degli errori
Quasi tutte le chiamate di sistema e le funzioni di libreria restituiscono un valore di stato che indica se l'operazione è riuscita o meno. Questo valore deve essere sempre controllato per verificare il successo della chiamata. In caso di fallimento, è necessario intervenire: almeno il programma dovrebbe mostrare un messaggio di errore che avvisa di un evento inatteso.
Anche se può sembrare allettante risparmiare tempo di digitazione omettendo questi controlli (soprattutto dopo aver visto esempi di programmi UNIX e Linux in cui i valori di stato non vengono verificati), si tratta di un falso risparmio. Molte ore di debug possono andare perse perché non è stato effettuato il controllo sul valore di ritorno di una chiamata di sistema o di una funzione di libreria che "non potrebbe mai fallire".
Pochissime chiamate di sistema non falliscono mai. Per esempio, getpid() restituisce sempre con successo l'ID di un processo, e _exit() termina sempre un processo. Non è necessario controllare i valori di ritorno di tali chiamate.
Gestione degli errori delle chiamate di sistema
La pagina di manuale di ogni chiamata di sistema documenta i possibili valori di ritorno, indicando quali di essi segnalano un errore. Di solito, un errore è indicato dal valore -1. Pertanto, una chiamata di sistema può essere verificata con codice simile al seguente:
/* Chiamata di sistema per aprire un file */
fd = open(pathname, flags, mode);
if (fd == -1) {
/* Code to handle the error */
}
/* ... */
if (close(fd) == -1) {
/* Code to handle the error */
}
Quando una chiamata di sistema fallisce, imposta la variabile globale intera errno a un valore positivo che identifica l'errore specifico. L'inclusione dell'header <errno.h> fornisce la dichiarazione di errno e un insieme di costanti per i vari numeri di errore. Tutti questi nomi simbolici iniziano con E. La sezione ERRORS di ogni pagina di manuale elenca i possibili valori di errno che possono essere restituiti dalla chiamata. Ecco un semplice esempio di utilizzo di errno per diagnosticare un errore di chiamata di sistema:
cnt = read(fd, buf, numbytes);
if (cnt == -1) {
if (errno == EINTR) {
fprintf(stderr, "la lettura è stata interrotta da un segnale\n");
} else {
/* Si è verificato un altro errore */
}
}
Le chiamate di sistema e le funzioni di libreria che hanno successo non azzerano mai errno, quindi questa variabile può contenere un valore diverso da zero a causa di un errore di una chiamata precedente. Inoltre, SUSv3 consente a una chiamata di funzione riuscita di impostare errno a un valore non zero (sebbene poche funzioni lo facciano). Perciò, quando si verifica un errore, occorre prima controllare se il valore di ritorno della funzione indica un errore e solo allora esaminare errno per determinarne la causa.
Alcune chiamate di sistema (ad esempio getpriority()) possono legittimamente restituire -1 anche in caso di successo. Per capire se si è verificato un errore in tali chiamate, si imposta errno a 0 prima della chiamata e lo si controlla dopo. Se la chiamata restituisce -1 e errno è diverso da zero, è avvenuto un errore. (Lo stesso vale per alcune funzioni di libreria).
Una prassi comune dopo una chiamata di sistema fallita è stampare un messaggio di errore basato sul valore di errno. Per questo scopo esistono le funzioni di libreria perror() e strerror().
La funzione perror() stampa la stringa puntata dal suo argomento msg, seguita da un messaggio corrispondente al valore corrente di errno.
#include <stdio.h>
void perror(const char *msg);
Un modo semplice per gestire gli errori delle chiamate di sistema è il seguente:
fd = open(pathname, flags, mode);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
La funzione strerror() restituisce la stringa di errore corrispondente al numero di errore fornito nel suo argomento errnum.
#include <string.h>
char *strerror(int errnum);
La funzione strerror() restituisce un puntatore alla stringa di errore corrispondente a errnum.
La stringa restituita da strerror() può essere allocata staticamente, il che significa che potrebbe essere sovrascritta da successive chiamate a strerror().
Se errnum specifica un numero di errore non riconosciuto, strerror() restituisce una stringa del tipo Unknown error nnn. Su altre implementazioni, invece, strerror() può restituire NULL in questo caso.
Poiché le funzioni perror() e strerror() sono sensibili alla localizzazione (studieremo la localizzazione più avanti), le descrizioni degli errori vengono visualizzate nella lingua locale.
Gestione degli errori dalle funzioni di libreria
Le varie funzioni di libreria restituiscono tipi di dato diversi e valori differenti per indicare un fallimento (consultare la pagina di manuale di ciascuna funzione). Per i nostri scopi, le funzioni di libreria possono essere suddivise nelle seguenti categorie:
- Alcune funzioni di libreria restituiscono le informazioni di errore esattamente come le chiamate di sistema: valore di ritorno
-1, conerrnoche indica l'errore specifico. Un esempio èremove(), che elimina un file (usando la chiamata di sistemaunlink()) o una directory (usandormdir()). Gli errori di queste funzioni possono essere diagnosticati nello stesso modo delle chiamate di sistema. - Alcune funzioni di libreria restituiscono un valore diverso da
-1in caso di errore, ma impostano comunqueerrnoper indicare la condizione di errore. Per esempio,fopen()restituisce un puntatoreNULLin caso di errore, e l'impostazione dierrnodipende dalla chiamata di sistema sottostante che è fallita. Anche in questo caso è possibile usareperror()estrerror()per diagnosticare l'errore. - Altre funzioni di libreria non usano affatto
errno. Il metodo per determinare l'esistenza e la causa di un errore dipende dalla funzione specifica ed è documentato nella relativa pagina di manuale. Per queste funzioni è un errore utilizzareerrno,perror()ostrerror()per la diagnosi. ```
In Sintesi
Le chiamate di sistema e le funzioni di libreria restituiscono sempre un valore di stato che deve essere verificato per individuare errori. Quando un errore è segnalato, errno contiene un codice specifico che può essere tradotto in messaggi leggibili tramite perror() o strerror(). Alcune funzioni richiedono controlli speciali, come l'inizializzazione di errno a zero prima della chiamata. Infine, è importante consultare le pagine di manuale per capire come ogni funzione gestisce gli errori.