La logica alla base delle funzioni della libreria C non imposta mai errno su zero


9

La norma C impone che nessuna funzione di libreria standard C debba essere impostata errnosu zero. Perché esattamente questo?

Potrei capire che è utile per chiamare diverse funzioni e controllare solo errnodopo l'ultima, ad esempio:

errno = 0;
double x = strtod(str1, NULL);
long y = strtol(str2, NULL);
if (errno)
    // either "strtod" or "strtol" failed
else
    // both succeeded

Tuttavia, questa non è considerata "cattiva pratica"? Dal momento che stai controllando solo errnoalla fine, sai solo che una delle funzioni non è riuscita, ma non quale funzione è fallita. Sapere semplicemente che qualcosa è fallito è abbastanza buono per la maggior parte dei programmi pratici?

Ho provato a cercare vari documenti di C Rationale, ma molti di essi non hanno molti dettagli <errno.h>.


1
Immagino sia "non pagare per quello che non vuoi". Se ti interessa errno, puoi sempre impostarlo su zero te stesso.
Kerrek SB,

2
Qualcos'altro da tenere presente è che una funzione può essere impostata errnosu un valore diverso da zero anche se ha esito positivo. (Potrebbe chiamare qualche altra funzione che fallisce, ma che non costituisce un fallimento nella funzione esterna.)
Keith Thompson

Risposte:


10

La libreria C non è impostata errnosu 0 per motivi storici 1 . POSIX non afferma più che le sue librerie non modificheranno il valore in caso di successo e la nuova pagina man di Linuxerrno.h riflette questo:

Il <errno.h>file di intestazione definisce la variabile intera errno, che viene impostata dalle chiamate di sistema e da alcune funzioni della libreria in caso di errore per indicare cosa è andato storto. Il suo valore è significativo solo quando il valore restituito della chiamata indicava un errore (ovvero, -1dalla maggior parte delle chiamate di sistema -1o NULLdalla maggior parte delle funzioni di libreria); una funzione che riesce è permesso di cambiare errno.

L' ANSI C Rationale afferma che il comitato ha ritenuto più pratico adottare e standardizzare la pratica esistente dell'uso errno.

Il meccanismo di segnalazione degli errori incentrato sull'impostazione di errnoè generalmente considerato con tolleranza nella migliore delle ipotesi. Richiede un `` accoppiamento patologico '' tra le funzioni della libreria e si avvale di una cella di memoria scrivibile statica, che interferisce con la costruzione di librerie condivisibili. Tuttavia, il Comitato ha preferito standardizzare questa macchina esistente, per quanto carente, piuttosto che inventare qualcosa di più ambizioso.

Esiste quasi sempre un modo per verificare la presenza di errori al di fuori del controllo se errnoimpostato. Controllare se errnoimpostato non è sempre affidabile, poiché alcune chiamate richiedono la chiamata di un'API separata per ottenere il motivo dell'errore. Ad esempio, ferror()viene utilizzato per verificare la presenza di un errore se si ottiene un risultato breve da fread()o fwrite().

È interessante notare che il tuo esempio di utilizzo strtod()è uno dei casi in cui errnoè necessario impostare 0 prima della chiamata per rilevare correttamente se si è verificato un errore. Tutte le strto*()funzioni da stringa a numero hanno questo requisito, perché viene restituito un valore di ritorno valido anche a fronte di un errore.

errno = 0;
char *endptr;
double x = strtod(str1, &endptr);
if (endptr == str1) {
    /*...parse error */
} else if (errno == ERANGE) {
    if (x == 0) {
        /*...underflow */
    } else if (x == HUGE_VAL) {
        /*...positive overflow */
    } else if (x == -HUGE_VAL) {
        /*...negative overflow */
    } else {
        /*...unknown range error? */
    }
}

Il codice sopra riportato si basa sul comportamento di strtod()come documentato su Linux . Lo standard C stabilisce solo che il flusso insufficiente non può restituire un valore maggiore del positivo più piccolo doublee se errnoè impostata o meno l' ERANGEimplementazione è definita 2 .

Esiste in realtà un approfondito avviso di avviso che consiglia di impostare sempre errnosu 0 prima di una chiamata in biblioteca e di controllarne il valore dopo che la chiamata indica che si è verificato un errore . Questo perché alcune chiamate in libreria verranno impostate errnoanche se la chiamata stessa ha avuto esito positivo 3 .

Il valore di errnoè 0 all'avvio del programma, ma non è mai impostato su 0 da nessuna funzione di libreria. Il valore di errnopuò essere impostato su un valore diverso da zero da una chiamata della funzione di libreria in presenza o meno di un errore, purché l'uso di errnonon sia documentato nella descrizione della funzione nella norma C. È significativo che un programma controlli i contenuti errnosolo dopo che è stato segnalato un errore. Più precisamente, ha errnosenso solo dopo che una funzione di libreria impostata errnosu errore ha restituito un codice di errore.


1. In precedenza ho affermato che era per evitare di mascherare un errore da una chiamata precedente. Non riesco a trovare alcuna prova a sostegno di questa affermazione. Ho anche avuto un printf()esempio falso .
2. Grazie a @chux per averlo segnalato. Il riferimento è C.11 §7.22.1.3 ¶10.
3. Segnalato da @KeithThompson in un commento.


Problema minore: sembra che il flusso insufficiente possa dare un risultato diverso da 0: "le funzioni restituiscono un valore la cui grandezza non è maggiore del numero positivo normalizzato più piccolo nel tipo di ritorno" C11 7.22.1.3.10.
chux - Ripristina Monica l'

@chux: grazie. Farò una modifica. Poiché l'impostazione errnosu ERANGEè un'implementazione definita in caso di underflow, in realtà non esiste un modo portatile per rilevare underflow. Il mio codice ha seguito quello che ho trovato nella pagina man di Linux sul mio sistema.
jxh

I tuoi commenti sulle strto*funzioni sono esattamente il motivo per cui ho chiesto se il mio esempio sarebbe stato considerato una cattiva pratica, ma è l'unico modo in cui errnonon essere impostato su zero sarebbe utile o addirittura applicabile.

@DrewMcGowen: Immagino che l'unico punto che puoi prendere sia che errnopossa essere impostato anche in caso di successo, quindi il solo fatto di averlo impostato non è un indicatore abbastanza valido del verificarsi di un errore. Devi controllare i risultati delle singole chiamate stesse per sapere se si è verificato un errore.
jxh

1

Puoi fare un controllo degli errori per entrambe le chiamate di funzione, se ti interessa davvero.

errno = 0;
double x = strtod(str1, NULL);
if (errno)
    // strtod"  failed
else
    // "strtod" succeeded

long y = strtol(str2, NULL);
if (errno)
    // "strtol" failed
else
    // "strtol" succeeded

Dato che non sappiamo mai come la funzione mach ha chiamato in un processo, come può la lib impostare errnos per ogni chiamata di funzione, giusto?


1

Cambiare arbitrariamente l'errorno è analogo a "catturare e deglutire" un'eccezione. Prima che esistessero eccezioni che si propagassero attraverso diversi livelli di un programma e alla fine raggiungessero un punto in cui il chiamante avrebbe catturato e risposto all'eccezione in qualche modo o semplicemente passato il dollaro, c'erano errori. Non alterare l'errno, a meno che tu non stia gestendo, scavalcando, trattando come irrilevante l'errore, è essenziale per questo paradigma di propagazione della gestione degli errori di prima generazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.