Scrivere Software è un piacere. Un programma non solo deve funzionare bene ed essere efficiente (questo si dà per scontato), ma deve essere anche bello ed elegante da leggere, comprensibile e facile da manutenere, sia per l'autore che per eventuali lettori futuri. Programmare bene in C è un'arte.
Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...
Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.
mercoledì 24 dicembre 2025
venerdì 12 dicembre 2025
Warning Peaks perché i warning sono importanti in C - pt.1
Dale Cooper: [parlando a un registratore] Diane, quando due eventi, due fatti riguardanti la stessa indagine si verificano simultaneamente, dobbiamo sempre considerarli con la massima attenzione.
Nella bellissima (e mitica) serie TV Twin Peaks il Maestro David Lynch (insieme al fido Mark Frost) ci spiegava che nulla è come appare a prima vista, e che in una investigazione bisogna analizzare tutti i dettagli, anche i più insignificanti, per giungere alla verità. Ebbene si, questo è valido anche nella nostra amata programmazione in C: il nostro strumento principale è il compilatore, che oltre a costruire l'eseguibile dell'applicazione su cui abbiamo lavorato ci fornisce un sacco di informazioni utili sul codice che abbiamo scritto, mostrandoci errori, vulnerabilità e avvisi, i famosi warning, che sono molto (ma molto) più importanti di quello che si pensa... Come avrete già intuito, in questo articolo parleremo di warning!
![]() |
| ...ma l'hai letto l'ultimo warning?... |
Nella mia lunga carriera di programmatore ho incontrato, a volte, colleghi che non davano la dovuta attenzione ai warning di compilazione, considerandoli semplici avvisi di scarsa importanza... e questo è un errore fondamentale! Anzi, ora che ci penso, un giorno assistei a un interessante dialogo tra mio cuggino e un suo collega. Si, credo che sia ora di far intervenire (sia pur indirettamente) mio cuggino, che manca da tempo da queste pagine. Il dialogo che intercettai era, più o meno, questo:
mio cuggino: Tu sei un programmatore professionista, vero?
programmatore: Si, è il mio mestiere. Anni e anni di esperienza...
mio cuggino: Ma dimmi una cosa: quando compili che linea di compilazione usi?
programmatore: Mah, qualcosa del tipo "gcc -o programma programma.c".
mio cuggino: Ma lo sai che con quella linea vedi solo i warning di base? E così te ne sfuggono alcuni molto importanti.
programmatore: Beh, ma i warning sono solo avvertenze, non servono a niente, già vedere quelli di base mi confonde abbastanza le idee...
mio cuggino: Guarda, se vuoi sviluppare del buon codice dovresti perlomeno attivare il flag -Wall e magari anche il -Wextra...
programmatore: -Wall? -Wextra? Mai sentiti, a cosa servono?.
mio cuggino: Mai pensato di cambiare mestiere?
Ecco, come sempre mio cuggino fu molto tranciante, ma quella volta io pensai la stessa cosa, ma non lo dissi (tanto l'aveva già detto lui, ah ah ah... si scherza, eh!).
Come dite? Sto esagerando? Ok, allora è il caso di fornire qualche piccolo esempietto... vai col codice!
// uninit.c#include <stdio.h>int main(){int index;char *str = "salve, mondo";printf("%c\n", str[index]);return 0;}
Ecco, questo è un codice semplicissimo che stampa il contenuto di una stringa. Proviamo a compilare ed eseguire questo uninit.c:
aldo@Linux $ gcc uninit.c -o uninitaldo@Linux $ ./uninitErrore di segmentazione (core dump creato)
Ma va! Un bel segmentation fault per un codice così semplice! E il compilatore non mi dice nulla? Proviamo a aggiunger il flag -Wall:
aldo@Linux $ gcc -Wall uninit.c -o uninituninit.c: In function ‘main’:uninit.c:9:23: warning: ‘index’ is used uninitialized [-Wuninitialized]9 | printf("%c\n", str[index]);| ^uninit.c:6:9: note: ‘index’ was declared here6 | int index;| ^~~~~
Oohh... e io che pensavo che i warning non servissero a niente (ah ah ah). Ecco, il codice mostrato è un semplice main di cinque righe, e quindi l'errore si vede facilmente, ma immaginate quando questo pezzetto di codice viene immerso in un grande progetto di centinaia di migliaia di linee: diventerà quasi invisibile, e per risolvere il problema sarà meglio dare un occhiata ai warning, no?
Ed ora un altro esempio con un warning che prima usciva solo attivando il flag -Wformat (che è incluso nel -Wall) e che ora esce anche a livello base (senza nessun flag), ma che molti non notano "perché leggere tutti i warning non serve" (ehm...) :
// format.c#include <stdio.h>int main(){int a = 12;printf("%s\n", a);return 0;}
Ok, anche questo è un codice semplicissimo che stampa nel terminale una stringa. Proviamo a compilare ed eseguire questo format.c:
aldo@Linux $ gcc format.c -o formatformat.c: In function ‘main’:format.c:8:14: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat=]8 | printf("%s\n", a);| ~^ ~| | || | int| char *| %daldo@Linux $ ./formatErrore di segmentazione (core dump creato)
Ma va! Un altro bel segmentation fault per un codice così semplice! E il compilatore me lo dice già di base! Certo che se non dò peso al warning (magari perché ce ne sono così tanti che faccio finta di non vederli...) il problema verrà poi quando eseguo l'applicazione. Male, male, molto male...
E non parliamo dei problemi che non appaiono costantemente a run-time, perché dipendono, sfortunatamente, da come è occupata la memoria al momento dell'esecuzione (quindi, a volte appaiono e a volte no): costerà moltissimo risolvere questi errori, quando invece sarebbe stato sufficiente risolvere tutti i warning di compilazione man mano che si presentavano! Spero di essere stato chiaro: i warning sono importanti, e non solo quelli di base!
Ok, per oggi può bastare. Nella seconda parte dell'articolo vedremo una panoramica dei flag di compilazione consigliati per realizzare una applicazione a prova di bomba. Come sempre vi raccomando di non trattenere il respiro nell'attesa (può nuocere gravemente alla salute...).
Ciao, e al prossimo post!
lunedì 24 novembre 2025
Il Logger come scrivere un logger in C - pt.2
Michael Corleone: Io non penso affatto di dover eliminare tutti Tom, solo i miei nemici, tutto qui.
(...una premessa: questo post potrebbe sembrare un remake di un mio vecchio post. In realtà tratta, incidentalmente, lo stesso argomento, ma amplia notevolmente il discorso è affronta ben altri temi. Leggete e mi direte...)
Visto che nell'ultimo articolo avevo introdotto l'argomento logger usando Il Padrino del Maestro Francis Ford Coppola, per questa seconda parte mi è sembrato il caso di usare un'altro suo capolavoro, Il Padrino - Parte II. E, come già detto nell'altro articolo,"...il logger lavora dietro le quinte e registra tutto, non gli sfugge niente...", proprio come il protagonista del film... Come avrete già capito, in questo articolo parleremo ancora di logger e di syslog!
![]() |
| ...ti spiego: senza il logger non vai da nessuna parte... |
E veniamo al dunque: ci eravamo lasciati con una pseudo-specifica di un logger che usa il syslog (system logger) , con tanto di anticipazione dei log ottenuti. Adesso è venuto il momento della implementazione vera e propria, e spero che qualcuno abbia raccolto il mio invito a scriverne una versione propria aspettando questo nuovo post (non potete certo dire che non vi ho lasciato il tempo...); tra l'altro, come vedremo tra poco, l'implementazione è veramente semplicissima. Nello scorso post avevamo visto l’header file (mylog.h) e un esempio di uso (main.c), e quindi, con grande originalità, il file di implementazione lo chiameremo mylog.c. E vediamolo, vai col codice!
#define _GNU_SOURCE#include <stdio.h>#include <stdarg.h>#include <syslog.h>#include <unistd.h>#include "mylog.h"#define LOGGER_MAXSIZE 2048 // max size di un messaggio di log (in bytes)// openLog - apre il log sul syslogvoid openLog(int level){// set del log level del syslogsetlogmask(LOG_UPTO(level));// aprp il log sul syslogopenlog(NULL, LOG_PID | LOG_NDELAY | LOG_PERROR, LOG_USER);}// closeLog - chiude il log sul syslogvoid closeLog(){// chiudo il syslog e mostro il risultato su stderrcloselog();fprintf(stderr, "%s: syslog chiuso\n", __func__);fflush(stderr);}// traceLog - scrive un messaggio di log sul syslogvoid traceLog(int level, const char *format, ...){// inizializzo la lista variabile di argomenti va_list usando la stringa formatva_list arglist;va_start(arglist, format);// costruisco una nuova stringa di format aggiungendo il TID a formatchar my_format[LOGGER_MAXSIZE];snprintf(my_format, sizeof(my_format), "[%d]: %s", gettid(), format);// chiamo vsyslog(3) (syslog(3) con va_list) con la stringa di format costruitavsyslog(level, my_format, arglist);// chiudo la va_listva_end(arglist);}
Non starò a descrivere gli include-files, che sono, né più né meno, solo quelli che servono. Sembra lapalissiano, ma è normale trovare sorgenti con molti più include del necessario: non fanno danni ma complicano l'interpretazione visiva del codice. Ricordatevi di mettere solo quelli che servono.
Dopodiché passiamo alla vera e propria implementazione, e incontriamo le prime due delle tre funzioni che compongono il nostro logger, e cioè: openLog() e closeLog(). Sono molto semplici: la prima setta il livello di log con il valore passato come argomento (level) e poi esegue la vera e propria apertura usando l'apposita funzione della famiglia syslog(3), e cioè openlog(3); la seconda si limita, semplicemente, a chiudere il logger usando l'apposita funzione closelog(3).
Notare che openlog(3) accetta una notevole serie di opzioni (col suo secondo argomento, option), che si possono mettere in OR per definire il funzionamento richiesto. Vi rimando al manuale di syslog(3) per la lista delle opzioni disponibili, comunque vi faccio notare che nell'esempio ho usato quelle più comuni, e cioè:
- LOG_PID: include il PID (Process ID) del chiamante in ogni messaggio di log.
- LOG_NDELAY: apre immediatamente la connessione (altrimenti la connessione viene aperta quando viene registrato il primo messaggio di log). Si parla di connessione perché per scrivere sul syslog si usa un socket, come già visto qui.
- LOG_PERROR: scrive il messaggio di log anche su stderr.
E adesso veniamo al cuore del nostro logger, la funzione traceLog(), che è quella che si occupa di scrivere le tracce di log nel file del syslog: per farlo usa funzioni della famiglia stdarg(3) (che gestisce le "variable argument lists"), visto che la nostra traceLog() deve usare una sintassi printf-like, quindi con stringa di formattazione e argomenti variabili. L'operazione è divisa in tre parti come indicato nel codice stra-commentato):
- Inizializzo la lista variabile di argomenti va_list usando la stringa format.
- Costruisco una nuova stringa di format aggiungendo il TID (Thread ID).
- Chiamo vsyslog(3) (che sarebbe una syslog(3) con va_list) con la stringa di format costruita precedentemente.
- Chiudo la va_list.
Faccio un appunto sul punto 2 qui sopra: è la fase di personalizzazione del messaggio di log, dove si possono aggiungere (a piacere) dettagli che si sommano a quelli standard presenti di default. In questo esempio ho aggiunto il TID (Thread ID), che in una applicazione multithread può essere utile a tracciare adeguatamente quello che è successo durante l'uso.
Cosa ve ne sembra dell'implementazione? Non è male, no? Ed è semplicissima! Provate a copiare e a compilare il tutto (con il main() del precedente articolo), vi assicuro che funziona bene (come tutti gli esempi che propongo nel blog, eh!).
E con questo credo che possiamo considerare concluso l'argomento logger su syslog. Il codice presentato in questi due articoli è molto simile a quello che uso spesso in produzione, quindi si tratta di un esempio pratico di uso reale, eh! Fatene buon uso!
Ciao, e al prossimo post!
giovedì 30 ottobre 2025
Il Logger come scrivere un logger in C - pt.1
Don Vito Corleone: Gli farò un'offerta che non potrà rifiutare.
(...una premessa: questo post potrebbe sembrare un remake di un mio vecchio post. In realtà tratta, incidentalmente, lo stesso argomento, ma amplia notevolmente il discorso è affronta ben altri temi. Leggete e mi direte...)
Il Logger è un po' come Il Padrino, il protagonista di uno dei capolavori del Maestro Francis Ford Coppola: lui lavora dietro le quinte e registra tutto, non gli sfugge niente. E quando è necessario bisogna ricorrere a lui per risolvere svariati problemi (uff... e, ahimè, in questo caso la finzione cinematografica è molto vicina a cose che succedono nella realtà...). Come avrete già capito, in questo articolo parleremo di logger e di syslog!
![]() |
| ...gli farò un logger che non potrà rifiutare... |
Quando si scrive Software bisogna sempre cercare di essere professionali, qualunque sia l'uso del Software che stiamo scrivendo. Ad esempio non ci si può dimenticare di prevedere un buon sistema di log (un logger, per gli amici). Ci sono, poi, alcuni professionisti della programmazione che non sanno neanche cos'è un buon sistema di log... ma questa è un altra storia...
Per aggiungere un logger al nostro Software possiamo aiutarci col sistema operativo, ad esempio su UNIX e Linux abbiamo il syslog (system logger) che è molto flessibile e ci semplifica la vita. Nel caso di non potere (o non volere) usare i mezzi forniti dal sistema si può pensare di scriversi un proprio sistema di log (l'ho fatto alcune volte, ma erano esigenze particolari di progetto), ma il syslog dei sistemi POSIX è così pratico e flessibile che non si può rinunciare a usarlo. Comunque in un mio vecchio articolo potete trovare un buon esempio di "logger indipendente".
Sia perché non voglio fare un post troppo lungo (diventerebbe noioso), sia per seguire una specie di approccio specifica+implementazione (adattato a un blog di programmazione), ho diviso il post in due puntate: nella prima descriverò, a mo' di specifica funzionale, l'header file (mylog.h) e un esempio di uso (main.c). Nella seconda puntata descriverò la vera e propria implementazione.
Vediamo l'header file, mylog.h:
// prototipi globalivoid openLog(int level);void closeLog();void traceLog(int level, const char* format, ...) __attribute__ ((format(printf, 2, 3)));// NOTA: il __attribute__ serve a mostrare warning (a compile-time) sui parametri di traceLog()
Semplicissimo e auto-esplicativo, no? Il nostro prodotto è formato da tre funzioni canoniche (apri, chiudi, traccia). L'argomento level di openLog() è il livello di log richiesto, e corrisponde a uno degli otto livelli di gravità previsti dal syslog standard (dal più grave al meno grave: LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG). Quasi inutile spiegare che, settando un livello, nel file del syslog appariranno tutti i messaggi di uguale o maggiore gravità del livello selezionato.
Merita invece un po' di attenzione il prototipo della funzione traceLog(): è la funzione per scrivere i messaggi nel syslog, ed è una funzione printf-like, quindi con argomenti variabili che si formattano usando l'argomento format. Notare che ho aggiunto, dopo il prototipo, questo attributo:
__attribute__ ((format(printf, 2, 3)));
A cosa serve? Come ben saprete le funzioni della famiglia printf(3) sono conosciute dal compilatore, e quindi se sbagliamo a formattare un argomento (ad esempio se assegniamo un int a un campo %s che si aspetta una stringa) il compilatore emette un warning che ci aiuta a correggere il problema a compile-time (che, nell'esempio appena descritto, si trasformerebbe in un problemone a run-time).
Il fatto è che la nostra funzione traceLog() è sconosciuta al compilatore, e quindi per generare eventuali warning bisogna presentargliela: e questo lo possiamo fare con l'apposito attributo format(archetype, string-index, first-to-check) che, nel nostro caso, specifica che è una printf (archetype=printf) con la stringa come secondo argomento (string-index=2) e gli argomenti variabili dalla terza posizione (first-to-check=3). Sfortunatamente tutto questo è valido solo per GCC, ma visto che è il compilatore più usato su UNIX e Linux mi sembra una informazione utilissima, no?
Vediamo ora il main dell'esempio d'uso: vai col codice!
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <syslog.h>#include "mylog.h"// prototipi localistatic int getLoglevel(const char* level);// main - funzione mainint main(int argc, char* argv[]){// test del numero di argomentiif (argc != 2) {// errore: numero errato di argomentifprintf(stderr, "%s: numero errato di argomenti\n", argv[0]);fprintf(stderr, "uso: %s loglevel (e.g.: %s debug)\n", argv[0], argv[0]);return EXIT_FAILURE;}// test del argomento loglevelint loglevel;if ((loglevel = getLoglevel(argv[1])) == -1) {// errore: livello di log sconosciutofprintf(stderr, "%s: livello di log sconosciuto (%s)\n", argv[0], argv[1]);fprintf(stderr, "uso: %s loglevel (e.g.: %s debug)\n", argv[0], argv[0]);return EXIT_FAILURE;}// apro il log su syslogopenLog(loglevel);// eseguo un test di traceLog()traceLog(LOG_EMERG, "log di tipo LOG_EMERG (il livello impostato è %s)", argv[1]);traceLog(LOG_ALERT, "log di tipo LOG_ALERT (il livello impostato è %s)", argv[1]);traceLog(LOG_CRIT, "log di tipo LOG_CRIT (il livello impostato è %s)", argv[1]);traceLog(LOG_ERR, "log di tipo LOG_ERR (il livello impostato è %s)", argv[1]);traceLog(LOG_WARNING, "log di tipo LOG_WARNING (il livello impostato è %s)", argv[1]);traceLog(LOG_NOTICE, "log di tipo LOG_NOTICE (il livello impostato è %s)", argv[1]);traceLog(LOG_INFO, "log di tipo LOG_INFO (il livello impostato è %s)", argv[1]);traceLog(LOG_DEBUG, "log di tipo LOG_DEBUG (il livello impostato è %s)", argv[1]);// chiudo il logcloseLog();// exit Okreturn EXIT_SUCCESS;}// getLoglevel - ritorna il loglevel come numero definito in syslog.hstatic int getLoglevel(const char* level) // la stringa corrispondente al numero definito{if (!strcmp(level, "emerg")) // sistema non usabilereturn LOG_EMERG;else if (!strcmp(level, "alert")) // necessità di azione immediatareturn LOG_ALERT;else if (!strcmp(level, "crit")) // condizione criticareturn LOG_CRIT;else if (!strcmp(level, "err")) // condizione di errorereturn LOG_ERR;else if (!strcmp(level, "warning")) // condizione di warningreturn LOG_WARNING;else if (!strcmp(level, "notice")) // condizione normale, ma significativareturn LOG_NOTICE;else if (!strcmp(level, "info")) // messaggio di informazionereturn LOG_INFO;else if (!strcmp(level, "debug")) // messaggio di debugreturn LOG_DEBUG;// livello di log sconosciutoreturn -1;}
Semplice, no? Eseguo un test degli argomenti e, se vanno bene, apro il logger, lo uso, lo chiudo ed esco. Nel caso di una errata linea di comando mostro l'errore e un esempio di lancio corretto: "uso: ./mylog loglevel (e.g.: ./mylog debug)". Il resto del codice è, come sempre, stra-commentato, quindi non credo che ci sia altro da aggiungere.
Ma, in pratica, usando una linea di comando del tipo "./mylog err" quale sara il risultato? Nel file del syslog verranno aggiunte le seguenti linee (delle quali spiegherò il contenuto nel prossimo articolo):
2025-10-27T19:12:48.035208+01:00 mint22 mylog[8030]: [8030]: log di tipo LOG_EMERG (il livello impostato è err)2025-10-27T19:12:48.036163+01:00 mint22 mylog[8030]: [8030]: log di tipo LOG_ALERT (il livello impostato è err)2025-10-27T19:12:48.036390+01:00 mint22 mylog[8030]: [8030]: log di tipo LOG_CRIT (il livello impostato è err)2025-10-27T19:12:48.036625+01:00 mint22 mylog[8030]: [8030]: log di tipo LOG_ERR (il livello impostato è err)
Notare che, essendo il livello scelto nella linea di comando quello di LOG_ERR, non sono apparsi i messaggi di LOG_NOTICE, LOG_INFO e LOG_DEBUG. Aggiungo che le scritte di log possono apparire anche su terminale se (come vedremo nel prossimo articolo) il syslog viene aperto con questa modalità (che è opzionale).
Per oggi abbiamo finito. I più volenterosi potranno, nell'attesa della seconda parte, scrivere una propria implementazione, e poi confrontarla con la mia, ma la mia sarà sicuramente migliore... (si allontana sghignazzando).
Ciao, e al prossimo post!
venerdì 19 settembre 2025
atoi(3)? No, grazie! come sostituire la atoi(3) in C
uomo travestito da Babbo Natale: Per la miseria! Non hanno più ritegno! Fare la multa a Babbo Natale la sera della vigilia! E alla Befana che faranno, le sequestrano la scopa?
Nell'ultimo articolo (che vi invito a rileggere) terminai con questo P.S.: "Nel prossimo articolo, anche se parlerò d'altro, vi proporrò la versione più efficiente e stringata possibile della atoi(3)...". L'idea era, in effetti di parlare d'altro ma, per la serie "battere il ferro finché è caldo", perché lasciare del tutto l'argomento atoi(3)? Solo che, invece di parlare di utili esercizi per rinfrescare le basi del C, oggi vi proporrò un nuovo capitolo della saga dei "No, grazie!" (ah: ne ho scritti altri e vi invito a leggerli o rileggerli, qui, qui, qui, qui, qui e qui) ). E, quindi, benvenuti a: atoi(3)? No, grazie!, dove spiegherò come e perché sostituire la atoi(3). Il film di riferimento è, ancora, il buon Mamma, ho perso l'aereo, un film Natalizio che ora è un po' fuori stagione, ma sarà perché sto già aspettando le vacanze di Natale...
![]() |
| ...e daje con la atoi(3)... |
// myatoi - una atoi(3) molto stringataint myatoi(const char *nptr){int result = 0;while (*nptr)result = result * 10 + *nptr++ - '0';return result;}
Sono tre (3!) linee di codice più il return, credo che sia difficile farla ancora più stringata, no? Ovviamente, come detto nella premessa, questa versione minimale fa solo l'indispensabile, quindi niente controllo della coerenza dell'input, niente gestione degli errori, ecc.
E a voi come è andata? Siete riusciti a scrivere una atoi(3) decente in meno di 10 minuti?
E ora veniamo al dunque, facciamo il punto sulla atoi(3) di libreria e vediamo perché è meglio sostituirla. Il perché è ben descritto già nel manuale della funzione, nel paragrafo "BUGS":
errno is not set on error so there is no way to distinguish between 0as an error and as the converted value. No checks for overflow or un‐derflow are done. Only base-10 input can be converted. It is recom‐mended to instead use the strtol() and strtoul() family of functions innew programs.
e quindi, per riassumere, i problemi sarebbero questi:
- Non imposta errno in caso di errore, quindi è impossibile sapere se un valore ritornato 0 è un errore o un vero valore 0...
- Derivato dal punto 1: restituisce 0 se la stringa non rappresenta un numero intero (o decimale), che è indistinguibile da una stringa di input formattata correttamente che indica lo zero.
- Ha un comportamento indefinito se il valore del risultato non può essere rappresentato.
Il manuale propone di usare, in alternativa, una funzione della famiglia strtol(3), che è una buona idea, anche se, devo dire, la strtol(3) non è propriamente una funzione user-friendly, non ha un uso semplice e immediato come la atoi(3), e non è esente dal problema di identificazione corretta di tutti gli errori da parte del chiamante. Comunque, se volete seguire rigidamente i consigli del manuale usate la strtol(3), che va benissimo; io, dato che ci sono, vi propongo una mia funzione alternativa che si usa (più o meno) come la atoi(3) ma che non ha nessuno dei suoi difetti (e usa internamente la strtol(3)). L'ho chiamata safeatoi(). Vai col codice!
// safeatoi - una atoi(3) senza problemiint safeatoi(const char *nptr){// set risultato di default e set errno a 0 prima di usare strtol(3)int result = -1;errno = 0;// set risultato usando strtol(3)char *endptr;const long lresult = strtol(nptr, &endptr, 10);// check risultato e eventualmente mostro l'erroreif (endptr == nptr) {// errorefprintf(stderr, "%s: non è un numero decimale\n", nptr);} else if (*endptr != '\0') {// errorefprintf(stderr, "%s: un carattere extra alla fine del input: %s\n", nptr, endptr);} else if ((lresult == LONG_MIN || lresult == LONG_MAX) && errno != 0) {// errorefprintf(stderr, "%s: fuori dal range del tipo long\n", nptr);} else if (lresult > INT_MAX) {// errorefprintf(stderr, "%ld maggiore di INT_MAX\n", lresult);} else if (lresult < INT_MIN) {// errorefprintf(stderr, "%ld minore di INT_MIN\n", lresult);} else {// successresult = (int)lresult;}return result;}
Che ne dite? Non è male, no? Questa versione scrive (su stderr) gli (eventuali) errori di esecuzione, pertanto si può sapere se qualcosa è andata male e se il valore ottenuto è corretto o no. Notare che nei test interni è presente anche il test di overflow su long, che essendo questa una atoi(3) potrebbe sembrare superfluo, ma non lo è, perché è comunque un errore che è necessario trattare, ed è anche una buona base per la codifica di una eventuale safeatol() alternativa alla atol(3).
Questa versione "sicura" è utile ed è adatta ad essere inserita direttamente in un progetto, possibilmente redirigendo le scritte al syslog invece che a stderr. Ma se l'obbiettivo è scrivere una vera funzione di libreria non possiamo basare il funzionamento su scritte di errore, bisogna seguire un altro approccio. E quindi? Ebbene si, sto per proporvi ben due (2! crepi l'avarizia!) versioni di una atoi(3) sicura, usabili come funzioni di libreria. La differenza tra le due versioni è che una usa una interfaccia GNU-specific, mentre l'altra ha una interfaccia XSI-compliant (ossia: POSIX). In realtà nessuna delle due realizza l'interfaccia in modo completamente canonico, ma visto che si tratta di funzioni aggiuntive esterne alla libc non si possono fare miracoli...
E cominciamo con la versione GNU-specific, che ho chiamato gsafeatoi(). Vai col codice!
// gsafeatoi - una atoi(3) senza problemi e in stile (quasi) GNU-specificint gsafeatoi(const char *nptr, char *buf, size_t buflen){// set risultato di default e set errno a 0 prima di usare strtol(3)int result = -1;errno = 0;// set risultato usando strtol(3)char *endptr;const long lresult = strtol(nptr, &endptr, 10);// check risultato e eventualmente mostro l'erroreif (endptr == nptr) {// erroreif (buf)snprintf(buf, buflen, "%s: non è un numero decimale", nptr);} else if (*endptr != '\0') {// erroreif (buf)snprintf(buf, buflen, "%s: un carattere extra alla fine del input: %s", nptr, endptr);} else if ((lresult == LONG_MIN || lresult == LONG_MAX) && errno != 0) {// erroreif (buf)snprintf(buf, buflen, "%s: fuori dal range del tipo long", nptr);} else if (lresult > INT_MAX) {// erroreif (buf)snprintf(buf, buflen, "%ld maggiore di INT_MAX", lresult);} else if (lresult < INT_MIN) {// erroreif (buf)snprintf(buf, buflen, "%ld minore di INT_MIN", lresult);} else {// successoresult = (int)lresult;if (buf)snprintf(buf, buflen, "success");}return result;}
Come sempre il codice è stra-commentato quindi non credo che ci sia da dilungarsi troppo in dettagli. Grazie a questo tipo di interfaccia è possibile ricevere la stringa di errore e così verificare se il risultato è valido. Ovviamente il chiamante deve fornire un buffer (e la sua lunghezza) per permettere la scrittura del messaggio.
E adesso possiamo passare alla versione XSI-compliant, che ho chiamato psafeatoi(). Vai col codice!
// psafeatoi - una atoi(3) senza problemi e in stile (quasi) XSI-compliant (POSIX)int psafeatoi(const char *nptr, int *result){// set risultato di default e set errno a 0 prima di usare strtol(3)*result = -1;errno = 0;// set risultato usando strtol(3)char *endptr;const long lresult = strtol(nptr, &endptr, 10);// check risultato e eventualmente mostro l'erroreint retval;if (endptr == nptr) {// errore: non è un numero decimaleretval = 1;} else if (*endptr != '\0') {// errore: un carattere extra alla fine del inputretval = 2;} else if ((lresult == LONG_MIN || lresult == LONG_MAX) && errno != 0) {// errore: fuori dal range del tipo longretval = 3;} else if (lresult > INT_MAX) {// errore: maggiore di INT_MAXretval = 4;} else if (lresult < INT_MIN) {// errore: minore di INT_MINretval = 5;} else {// successo*result = (int)lresult;retval = 0;}return retval;}
Come è evidente, grazie a questo tipo di interfaccia, è possibile ricevere un codice di successo (0) o di errore (1, 2, ecc.) per verificare se il risultato è valido o che tipo di errore si è verificato. In realtà la interfaccia XSI-compliant canonica dovrebbe ritornare l'errno gestito dalla libc, ma nel nostro caso dovremo accontentarci del nostro codice user-defined che potremo, ad esempio definire in un header apposito del progetto.
Che ne dite? Spero che questo prolungamento dell'argomento atoi(3) vi sia piaciuto e, soprattutto, vi sia risultato utile. E se no, pazienza, me ne farò una ragione...
Ciao, e al prossimo post!




