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ì 3 giugno 2016

Sprintf Driver
perché non bisogna usare la sprintf in C

"Ma dici a me? Ma dici a me?... Ma dici a me?...". Si, proprio come il grande De Niro in Taxi Driver, questa è stata la mia reazione (incredula) quando ho scoperto (molti anni fa, oramai) che, dopo anni e anni di onorato uso, avrei dovuto smettere di usare la sprintf().
...e tu dici a me di non usare più la sprintf? A me?...
Beh, in effetti se la usi bene e hai il 100% di controllo sul codice scritto puoi anche usarla senza grossi problemi ma, come dicono gli inglesi, la sprintf() è error prone, induce facilmente a errori, anche gravi. Il problema più grave ed evidente con la sprintf() si chiama buffer overflow, e non credo che sia necessario spenderci molte parole: se il buffer che passiamo come primo argomento non è correttamente dimensionato il disastro è dietro l'angolo.

Fortunatamente ci viene in aiuto la snprintf(), che è della stessa famiglia ma molto più sicura. Vediamo i due prototipi a confronto:
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
La snprintf() ci obbliga a mettere il size del buffer come secondo argomento, per cui è facilissimo prendere l'abitudine a scrivere in un modo error-free come questo:
char buffer[32];
snprintf(buffer, sizeof(buffer), "Hello world!");
Se al posto di "Hello world!" avessimo scritto una stringa di più di 32 chars, nessun problema: la snprintf() la tronca opportunamente e siamo salvi.

E adesso vi propongo un piccolo esempio reale: prendiamo una nostra vecchia conoscenza scritta per un vecchio post, la getDateUsec() e la scriviamo in due versioni, una buona e una cattiva (bad). Vediamo:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>

// prototipi locali
char *getDateUsec(char *dest, size_t size);
char *badGetDateUsec(char *dest);

// funzione main
int main(int argc, char* argv[])
{
    // chiama getDateUsec (o badGetDateUsec) e scrive il risultato
    char dest[12];
    printf("date con usec: %s\n", getDateUsec(dest, sizeof(dest)));
    //printf("date con usec: %s\n", badGetDateUsec(dest));

    return EXIT_SUCCESS;
}

// getDateUsec() - Genera una stringa con data e ora (usa i microsecondi)
char *getDateUsec(char *dest, size_t size)
{
    // get time (con gettimeofday()+localtime() invece di time()+localtime() per ottenere i usec)
    struct timeval tv;
    gettimeofday(&tv, NULL);
    struct tm *tmp = localtime(&tv.tv_sec);

    // format stringa destinazione dest(deve essere allocata dal chiamante) e aggiunge i usec
    char fmt[128];
    strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp);
    snprintf(dest, size, fmt, tv.tv_usec);

    // return stringa destinazione dest
    return dest;
}

// badGetDateUsec() - Genera una stringa con data e ora (usa i microsecondi) (versione bad)
char *badGetDateUsec(char *dest)
{
    // get time (con gettimeofday()+localtime() invece di time()+localtime() per ottenere i usec)
    struct timeval tv;
    gettimeofday(&tv, NULL);
    struct tm *tmp = localtime(&tv.tv_sec);

    // format stringa destinazione dest(deve essere allocata dal chiamante) e aggiunge i usec
    char fmt[128];
    strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp);
    sprintf(dest, fmt, tv.tv_usec);

    // return stringa destinazione dest
    return dest;
}
Ecco, a suo tempo, per semplificare, avevo scritto una getDateUsec() che era, in realtà, una badGetDateUsec() (e in seguito, per precisione, ho provveduto a modificarla anche sul post). Quella versione funzionava ma poteva creare problemi, mentre la nuova versione è molto più sicura. Provate a compilare l'esempio, dove, volutamente, ho sottodimensionato il buffer di destinazione: commentando la badGetDateUsec() e usando la getDateUsec(), funziona perfettamente, troncando l'output a 12 chars. Se, invece, commentate la getDateUsec() e usate la badGetDateUsec() il programma si schianta durante l'esecuzione. Provare per credere!

E già che siamo in argomento sprintf() un piccolo consiglio un po' OT: se dovete aggiungere sequenzialmente delle stringhe (in un loop, ad esempio) su una stringa base (per comporre un testo, ad esempio) non fate mai cosi:
char buf[256] = "";
for (int i = 0; i < 5; i++)
    sprintf(buf, "%s aggiunto alla stringa %d\n", buf, i);
il metodo qui sopra sembra funzionare, ma, in realtà, funziona quando c'ha voglia lui. Fate invece così:
char buf[256] = "";
for (int i = 0; i < 5; i++) {
    char tmpbuf[256];
    sprintf(tmpbuf, "%s aggiunto alla stringa %d\n", buf, i);
    sprintf(buf, "%s", tmpbuf);
}
E se non ci credete provate a passare il codice con un lint tipo cppchek (che è sempre una buona idea) o consultate il manuale della sprintf():
C99 and POSIX.1-2001 specify that the results are undefined if  a  call
to  sprintf(), snprintf(), vsprintf(), or vsnprintf() would cause copy‐
ing to take place between objects that overlap  (e.g.,  if  the  target
string  array and one of the supplied input arguments refer to the same
buffer).
E, ovviamente, anche in quest'ultimo esempio (fatto per semplicità con la sprintf()) sarebbe raccomandabile usare la snprintf().

Ciao e al prossimo post!

sabato 7 maggio 2016

Strerror Simple
come scrivere una pseudo-strerror in C

Questo è un post facile. Magari un po' più del Blood dell'esordio dei grandi Coen brothers (uhm... il sangue non è mai facile). Si, un post facile facile, ma più utile di quel che sembra, tanto per dimostrare che a problema semplice soluzione semplice: se per fare una scemata avete scritto 1000000 di incomprensibili righe di codice avete sbagliato qualcosa: meglio cancellare e ricominciare, sicuramente vi verrà meglio.
...e questo tu lo chiami semplice? SEMPLICE???
Allora, supponiamo di avere dei codici che rappresentano qualcosa (errori, tipi di messaggi, warning di sistema, ciabatte, formaggi... qualsiasi cosa), e volete, per esempio, mostrarli in un bel sistema di log (magari come quello visto qui, qui e qui), ma non come numeri (ehi, siamo esseri umani, mica Sheldon Cooper): siamo abituati a visualizzare cose, non numeri. Vogliamo vedere le stringhe mnemoniche corrispondenti ai numeri.
...e da oggi vi parlerò usando solo codice binario...
Ecco, scrivere il codice per una cosa del genere è veramente molto semplice... vai col codice!
#include<stdio.h>

// codici stringhe
#define CODE_SPIDERMAN  0
#define CODE_THOR       1
#define CODE_DAREDEVIL  2
#define CODE_PUNISHER   3

// array stringhe
static const char* const strings[] = {
    "Spiderman",
    "Thor",
    "Daredevil",
    "Punisher"
};

// ottieni una stringa
const char* getString(
    int code)
{
    // calcola size
    int size = sizeof(strings) / sizeof(char*);

    // test se il codice va in overflow
    if (code >= 0 && code < size)
        return(strings[code]);
    else
        return("errore: codice non valido");
}

// main() per test
void main()
{
    // loop di input
    for (;;) {
        // chiede il codice
        int code;
        printf("get stringa numero? ");
        scanf("%d", &code);

        // mostra la stringa corrispondente al codice
        printf("stringa = %s\n", getString(code));
    }
}
Allora, tanto per non ripetermi: il codice qui sopra è, evidentemente, auto-esplicativo, ampiamente commentato e con commenti che parlano da soli.

Come avrete notato è sufficiente (almeno in una implementazione base) definire i codici e scrivere le stringe corrispondenti (nello stesso ordine!) in un array. Poi si scrive la funzione per estrarle che esegue, semplicemente, un return di array[codice]. Ho aggiunto un minimo (indispensabile) di controlli, per evitare l'uso di codici negativi o non esistenti (overflow). Notare che il size è calcolato dinamicamente, quindi possiamo aggiungere, col tempo, codici (e stringhe) senza bisogno di modificare la getString() (molto bene!). Ovviamente il codice presentato (che include anche un semplice main() di test interattivo) si può complicare e sofisticare quanto si vuole, ma rispettando l'idea base, che è semplice.

In un progetto reale il file qui sopra bisogna spezzarlo in tre: i codici (e un prototipo) andranno in un header-file che verrà incluso da tutti i source-file della applicazione che necessitano usare la getString(). La getString() e l'array (statico) di stringhe andranno in un source-file apposito (che chiameremo getstring.c) che verrà compilato e linkato insieme agli altri file del progetto.

Perché nel titolo ho citato la strerror()? Perché la nostra getString() fa, più o meno, lo stesso lavoro della strerror() di sistema, ossia restituisce la stringa corrispondente al codice di errore errno. Ovviamente la strerror() è un po' piu sofisticata: usa un buffer statico interno su cui esegue strcpy() e altre cosette... tra l'altro proprio per colpa del buffer interno non è thread safe, e, in alcuni casi ci costringe a usare la strerror_r(): ma questa è un altra storia, e questo è un post semplice, non divaghiamo.

Certo, se poi qualcuno di mestiere fa il complicatore di cose facili (che, sfortunatamente, è un lavoro abbastanza diffuso) tutto quello detto finora gli risulterà indifferente, magari addirittura fastidioso. Ma magari non dovrebbe leggere questo blog, sarà capitato qui per sbaglio.

Ciao e al prossimo post!

sabato 16 aprile 2016

The (Duel)lists
come usare le Linked Lists in C

Se qualcuno parla male delle Linked Lists lo sfido a duello. Si, farò proprio come Harvey Keitel in uno dei capolavori di Ridley Scott. State attenti.
Cosa dicevi delle Linked Lists?
Da queste parti avevamo già affrontato (di striscio) il tema delle Linked Lists, in un post preistorico sulla malloc(): dovreste andare a rileggerlo, ma, se proprio non ne avete voglia, vi riporto una parte:

"Per chi non le avesse mai usate, le linked-list non sono una frivolezza: sono una delle costruzioni più potenti della programmazione in generale (e del C in particolare). Chi lavora intensamente in C su progetti grandi e professionali finirà, prima o poi, con usarle: un motivo in più, quindi, per familiarizzare con la allocazione dinamica della memoria."

Premessa: se qualcuno non ha la minima idea di cosa sia una lista concatenata gli consiglio di leggersi (prima di continuare) una delle mille descrizioni (alcune veramente ottime) che si trovano in rete. Questa, per esempio. Per questo nuovo post ho deciso di riprendere il codice che avevo scritto per quello vecchio e semplificarlo/migliorarlo in maniera di ottenere:
  1. condensare in una sola funzione le due che nel vecchio codice creavano un nuovo nodo nella lista (semplificare!).
  2. aggiungere una funzione che permette di appendere un nuovo nodo invece di metterlo in testa alla lista (migliorare!).
Vai col codice!
#include<stdlib.h>
#include<stdio.h>

// nodo di una linked list con campo dati
typedef struct snode {
    int data;
    struct snode *next;
} node_t;

// alloca in testa a una lista un node con i dati e un pointer al prossimo elemento
void addNode(
    node_t **head,
    int    data)
{
    // alloca un nuovo node
    node_t *node = malloc(sizeof(node_t));
    node->data = data;
    node->next = *head;

    // assegna head lista al nuovo node
    *head = node;
}

// alloca in fondo a una lista un node con i dati e un pointer al prossimo elemento
void appendNode(
    node_t **head,
    int    data)
{
    // alloca un nuovo node
    node_t *node = malloc(sizeof(node_t));
    node->data = data;
    node->next = NULL;

    // appende il nuovo node
    node_t *current = *head;
    if (current == NULL) {
        // caso speciale per lista vuota: assegna head lista al nuovo node
        *head = node;
    }
    else {
        // scorre la lista per trovare l'ultimo node
        while (current->next != NULL)
            current = current->next;

        // assegna all'ultimo node il nuovo node
        current->next = node;
    }
}

// main() per test
void main()
{
    int i;

    // init lista vuota 1 e inserisce con addNode() 3 nodi con data = indice loop
    node_t *head1 = NULL;
    for (i = 1; i <= 3; i++)
        addNode(&head1, i);

    // scorre la lista e stampa i valori
    node_t *myhead1 = head1;
    printf("myhead1=%p - metodo ADD\n", myhead1);
    while (myhead1) {
        printf("data=%d (myhead1=%p next=%p)\n", myhead1->data, myhead1, myhead1->next);
        myhead1 = myhead1->next ;
    }

    // init lista vuota 2 inserisce con appendNode() 3 nodi con data = indice loop
    node_t *head2 = NULL;
    for (i = 1; i <= 3; i++)
        appendNode(&head2, i);

    // scorre la lista e stampa i valori
    node_t *myhead2 = head2;
    printf("myhead2=%p - metodo APPEND\n", myhead2);
    while (myhead2) {
        printf("data=%d (myhead2=%p next=%p)\n", myhead2->data, myhead2, myhead2->next);
        myhead2 = myhead2->next ;
    }
}
Come si nota è abbastanza semplice e conciso, e, come sempre, il codice è auto-esplicativo, ampiamente commentato e con commenti che parlano da soli.

Nel main() ho aggiunto un po' di tracce di log, che ci aiutano, durante l'esecuzione, a capire cosa succede quando aggiungiamo un nodo e quando, in alternativa, lo appendiamo. Il risultato finale è lo stesso (nell'esempio è una lista con tre nodi), ma la disposizione cambia: nel caso append abbiamo una disposizione più logica dei nodi: la lista cresce in avanti, e ogni nuovo nodo è l'ultimo. Nel caso insert, invece, è la testa della lista che cambia ad ogni inserzione, e quindi, quando leggiamo i dati, li troviamo invertiti rispetto all'ordine di inserimento.

Entrambe le liste hanno usi validi (ad esempio una va meglio per creare code FIFO e l'altra va meglio per creare code LIFO), e, per applicazioni più sofisticate, dove bisogna aggiungere/leggere/cancellare nodi in posizioni determinate si può decidere (quasi) indistintamente per una struttura o l'altra. Io normalmente uso il metodo append, che mi sembra più logico, anche se, lo riconosco, la funzione di append è un po' più complicata e meno immediata. Però la lista ordinata in avanti mi sembra più coerente e facile da maneggiare.

Alla fin fine il programma ci mostra questo log:
myhead1=0x11b5050 - metodo ADD
data=3 (myhead1=0x11b5050 next=0x11b5030)
data=2 (myhead1=0x11b5030 next=0x11b5010)
data=1 (myhead1=0x11b5010 next=(nil))
myhead2=0x11b5070 - metodo APPEND
data=1 (myhead2=0x11b5070 next=0x11b5090)
data=2 (myhead2=0x11b5090 next=0x11b50b0)
data=3 (myhead2=0x11b50b0 next=(nil))
che mi sembra inutile spiegare (parla da solo!). Finisco con una ultima, doverosa, precisazione: evidentemente quello presentato è solo un programma di test: in una applicazione reale bisognerà aggiungere un po' di funzioni accessorie per cercare e cancellare nodi, ecc. E, visto che usiamo delle malloc(), bisognerà ricordarsi di usare le corrispondenti free()...

Beh, vi auguro un buon lavoro con le Linked Lists, che, sicuramente, vi daranno delle soddisfazioni. E se non ve le danno, pazienza: fossero questi i problemi della vita...

Ciao e al prossimo post!