Hannah Strerror e le sue sorelle |
Si era detto: decisioni. La strerror() ha molte personalità, quindi quale scelgo? la strerror() o la strerror_r()? E se uso quest'ultima quale scelgo, la versione XSI-compliant o la versione GNU-specific? (per non parlare, poi, delle altre varianti, la strerror_l(), la strerror_s(), ecc., ma queste sono varianti secondarie).
Cominciamo con la prima domanda: strerror() o strerror_r()? Anticamente esisteva solo la prima, ma poi sono apparsi i thread e sono cominciati i problemi, perché nel codice multi-thread ci sono alcune parti critiche dove bisognerebbe usare solo funzioni thread-safe. La strerror() non è dichiarata thread-safe nello standard, e per capire il perché basta analizzare una implementazione semplificata (però molto simile alle implementazioni reali che possiamo trovare nelle varie libc disponibili). Vai col codice!
#include <stdio.h> // stdio.h include sys_errlist.h che dichiara le variabili // globali _sys_errlist (array errori) e _sys_nerr (num.errori) static char buf[256]; // buffer globale statico per la stringa da ritornare char *strerror(int errnum) { // test se errnum è un valore valido if (errnum < 0 || errnum >= _sys_nerr || _sys_errlist[errnum] == NULL) { // errore sconosciuto: copio in buf un messaggio di errore generico snprintf(buf, sizeof(buf), "Unknown error %d", errnum); } else { // errore conosciuto: copio in buf il messaggio corrispondente snprintf(buf, sizeof(buf), "%s", _sys_errlist[errnum]); } // ritorno buf che ora contiene il messaggio di errore return buf; }risulta evidente dal codice (ben commentato, come sempre, così non devo spiegarlo riga per riga) che la strerror() non ritorna direttamente _sys_errlist[errnum] (e se fosse così sarebbe thread-safe) ma compone un messaggio di errore (per trattare anche gli errnum non validi) usando un buffer globale statico buf: quindi, se due thread di una applicazione usano (quasi) contemporaneamente la strerror() il contenuto di buf non sarà attendibile (prevale il thread che ha scritto per ultimo).
(altra parentesi: non è impossibile scrivere una strerror() che sia thread-safe, e in alcuni sistemi lo è: ma visto che secondo lo standard non lo è, non possiamo essere sicuri che sul sistema che stiamo usando (o sul sistema su cui, un giorno, girerà la applicazione che stiamo scrivendo) non ci sia una implementazione come quella appena descritta, quindi...)
Allora, per il Software multi-thread è nata la strerror_r() che è thread-safe. Come funziona? vai col codice!
#include <stdio.h> // stdio.h include sys_errlist.h che dichiara le variabili // globali _sys_errlist (array errori) e _sys_nerr (num.errori) char *strerror_r(int errnum, char *buf, size_t buflen); { // test se errnum è un valore valido if (errnum < 0 || errnum >= _sys_nerr || _sys_errlist[errnum] == NULL) { // errore sconosciuto: copio in buf un messaggio di errore generico snprintf(buf, buflen, "Unknown error %d", errnum); } else { // errore conosciuto: copio in buf il messaggio corrispondente snprintf(buf, buflen, "%s", _sys_errlist[errnum]); } // ritorno buf che ora contiene il messaggio di errore return buf; }anche in questo caso si tratta di codice semplificato, ma molto vicino alla realtà: il trucco è semplice, invece di usare un buffer globale statico (che è la fonte dei problemi della strerror()) il chiamante della funzione si deve preoccupare di allocare e passare un buffer (e la sua lunghezza) alla strerror_r(). In questo modo il buffer che usa la strerror_r() è locale al thread che la chiama, e non può essere sovrascritto da un altro thread concorrente. Abbiamo sacrificato un po' di semplicità d'uso ma abbiamo ottenuto l'agognato comportamento thread-safe!
Ed ora aggiungiamo un po' di complicazione: la versione di strerror_r() appena mostrata è la GNU-specific. Ma, sfortunatamente, esiste anche la XSI-compliant, che è la seguente:
int strerror_r(int errnum, char *buf, size_t buflen);Come si nota questa seconda versione non ritorna il buffer con la error-string, ma ritorna, invece, un codice di errore, e la stringa trovata bisogna ripescarla direttamente nel buffer che abbiamo passato. Per quanto riguarda il codice di ritorno è 0 in caso di successo e, in base alla versione di libc in uso, potrebbe ritornare -1 in caso di errore (settando errno al valore specifico di errore) oppure un valore positivo corrispondente a errno (bah, questo doppio comportamento non è proprio il massimo della semplicità d'uso...). Per usare questa versione o la GNU-specific bisogna giocare opportunamente con i flag _GNU_SOURCE, _POSIX_C_SOURCE e _XOPEN_SOURCE del preprocessore (come descritto nel manuale della strerror()).
E ora siamo pronti per la seconda decisione: quale usiamo, la GNU-specific o la XSI-compliant? Beh, io direi che quando scriviamo del codice per trattare dei codici di errore probabilmente non ci interessa trattare anche gli errori generati in questa fase (e nella fase successiva, ecc., ecc., un loop infinito di ricerca degli errori!); ci interessa, invece, scrivere codice lineare e semplice... per toglierci il dubbio possiamo analizzare due piccoli esempi d'uso:
GNU-specific if ((my_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // errore socket() char errbuf[MAX_ERROR_LEN]; // buffer per strerror_r() printf("socket() error (%s)\n", strerror_r(errno, errbuf, sizeof(errbuf))); return EXIT_FAILURE; }
XSI-compliant if ((my_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // errore socket() char errbuf[MAX_ERROR_LEN]; // buffer per strerror_r() int my_error = strerror_r(errno, errbuf, sizeof(errbuf))); if (! my_error) printf("socket() error (%s)\n", errbuf); else { // tratto l'errore (magari usando di nuovo strerror_r()?) ... } return EXIT_FAILURE; }Non so voi cosa ne pensate, ma io uso sempre la versione GNU-specific! A voi la scelta...
Ciao, e al prossimo post!
Nessun commento:
Posta un commento