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.

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, quiqui, quiqui 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)...

Prima di tutto devo mantenere la promessa: ecco la versione più efficiente e stringata possibile della atoi(3) (o almeno credo che lo sia...). Vai col codice!
// myatoi - una atoi(3) molto stringata
int 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 0
as 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 in
new programs.

e quindi, per riassumere, i problemi sarebbero questi:

  1. Non imposta errno  in caso di errore, quindi è impossibile sapere se un valore ritornato 0 è un errore o un vero valore 0...
  2. 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.
  3. 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 problemi
int 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'errore
if (endptr == nptr) {
// errore
fprintf(stderr, "%s: non è un numero decimale\n", nptr);
} else if (*endptr != '\0') {
// errore
fprintf(stderr, "%s: un carattere extra alla fine del input: %s\n", nptr, endptr);
} else if ((lresult == LONG_MIN || lresult == LONG_MAX) && errno != 0) {
// errore
fprintf(stderr, "%s: fuori dal range del tipo long\n", nptr);
} else if (lresult > INT_MAX) {
// errore
fprintf(stderr, "%ld maggiore di INT_MAX\n", lresult);
} else if (lresult < INT_MIN) {
// errore
fprintf(stderr, "%ld minore di INT_MIN\n", lresult);
} else {
// success
result = (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-specific
int 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'errore
if (endptr == nptr) {
// errore
if (buf)
snprintf(buf, buflen, "%s: non è un numero decimale", nptr);
} else if (*endptr != '\0') {
// errore
if (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) {
// errore
if (buf)
snprintf(buf, buflen, "%s: fuori dal range del tipo long", nptr);
} else if (lresult > INT_MAX) {
// errore
if (buf)
snprintf(buf, buflen, "%ld maggiore di INT_MAX", lresult);
} else if (lresult < INT_MIN) {
// errore
if (buf)
snprintf(buf, buflen, "%ld minore di INT_MIN", lresult);
} else {
// successo
result = (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'errore
int retval;
if (endptr == nptr) {
// errore: non è un numero decimale
retval = 1;
} else if (*endptr != '\0') {
// errore: un carattere extra alla fine del input
retval = 2;
} else if ((lresult == LONG_MIN || lresult == LONG_MAX) && errno != 0) {
// errore: fuori dal range del tipo long
retval = 3;
} else if (lresult > INT_MAX) {
// errore: maggiore di INT_MAX
retval = 4;
} else if (lresult < INT_MIN) {
// errore: minore di INT_MIN
retval = 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!

Nessun commento:

Posta un commento