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.

domenica 3 gennaio 2016

Mad Sleep
come scrivere un wrapper per la nanosleep() in C

La sleep() ha personalità multiple, forse è un po' pazza, proprio come il nostro amico Max. Se vogliamo mandare a dormire per un po' la nostra applicazione (tipicamente un thread di una applicazione, ma questo argomento lo tratteremo un altra volta...) abbiamo varie (troppe?) opzioni: sleep(), usleep(), nanosleep() e altre ancora!
...a chi hai detto pazzo?
Concentriamoci sulle tre elencate sopra: ognuna ha i suoi pro e contro:
  • sleep(): è Ok ma ha una risoluzione troppo bassa: un secondo per alcune applicazioni è una eternità.
  • usleep(): è obsoleta: è stata bannata dagli standard Posix perché ha una risposta ai segnali indefinita.
  • nanosleep() è Ok (alta risoluzione, tratta i segnali) ma ha una interfaccia un poco ostica...
Per gli usi normali (si potrebbe dire usi non hard-realtime, ma anche questo argomento lo tratteremo un altra volta...) ci servirebbe una funzione con l'interfaccia tipo la sleep() e una risoluzione media, direi in millisecondi. Ma allora facciamo un wrapper per la usleep()!

Vai col codice!
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// prototipi locali
static void mySleep(long msec);
static char *getDateUsec(char *date);

// main del programma di test
int main(int argc, char **argv)
{
    // test argomenti
    if (argc != 1) {
        // error
        printf("Usage: %s\n", argv[0]);
        return EXIT_FAILURE;
    }

    // mostra l'ora, sleep e mostra di nuovo l'ora
    char date[32];
    printf("ora prima della sleep: %s\n", getDateUsec(date));
    mySleep(1500);
    printf("ora dopo la sleep:     %s\n", getDateUsec(date));

    // esce con Ok
    return EXIT_SUCCESS;
}

// mySleep - sleep per un numero specifico di millisecondi
void mySleep(
    long msec)              // sleep time in millisecondi
{
    // split argomento in secondi e millisecondi (msec è in millisecondi ma potrebbe essere > 1000)
    unsigned int seconds      = msec / 1000;
    unsigned int milliseconds = msec % 1000;

    // set dati per nanosleeep() e li usa
    struct timespec t;
    t.tv_sec  = seconds;
    t.tv_nsec = milliseconds * 1000000; // i.e.: tv_nsec=500000000; 500 milioni di ns è 1/2 sec
    nanosleep(&t, NULL);
}

// getDateUsec - ottiene date-time con i microsecondi
char *getDateUsec(
    char* date)             // stringa destinazione
{
    ...
}
E, come sempre:
  1. Codice auto-esplicativo, ampiamente commentato e... i commenti parlano da soli.
  2. Il main() serve solo a lanciare la mySleep() e a mostrarci (con i dati del tempo prima e dopo) che la funzione funziona.
La mySleep() è il nostro wrapper, e può essere facilmente integrato in qualsiasi applicazione o in una libreria. Usa internamente la nanosleep(), formattando opportunamente i dati da passare usando il solo argomento (in millisecondi) che gli forniamo. Il main() ci mostra (con la precisione del microsecondo) il tempo prima e dopo la mySleep(), usando un'altra funzione scritta ad-hoc, la getDateUsec(): questa è una nostra vecchia conoscenza descritta qui, per cui non ho riportato il codice (così, magari vi viene voglia di rileggervi il vecchio post dove era descritta).

Una ultima nota per gli (eventuali) precisini che leggendo il codice avranno pensato "ma lo split degli argomenti si poteva fare direttamente nelle assegnazioni a struct timespec t, risparmiando le (ben) due istruzioni di assegnazione precedenti...". Beh non perdo neanche tempo a discutere e li invito solo a riflettere sui seguenti tre punti:
  1. mySleep() serve a ritardare: sai cosa ce ne importa dell'efficienza in questo caso...
  2. Leggetevi questo.
  3. "The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming" (D.Knuth, "Computer Programming as an Art", 1974).
Vi ricordo nuovamente che, per testare l'esempio su Linux (cosa che ho fatto, ovviamente...) basta compilare il programma con:
gcc mysleep.c -o mysleep
...uhmmm... devo ammettere che la citazione del mitico D.Knuth è la parte più interessante di tutto il post. Meditate gente, meditate...

Ciao e al prossimo post!

domenica 6 dicembre 2015

Mission: Impossible - XML Deserializer
come scrivere un XML Deserializer in C

Come promesso torniamo con una nuova missione (non tanto) impossibile per proporre un Deserializer complementare al Serializer dell'ultimo post. Ovviamente, se non avete ancora letto il post precedente, dovreste:

1) vergognarvi
2) andare a leggerlo. Subito!


Ecco, se state leggendo queste righe può darsi che avete già passato i due punti precedenti, quindi possiamo continuare. Useremo anche questa volta la libXML2 per scrivere una funzione che riesce a estrarre tutte le coppie key-value che compongono un documento XML.
Sempre più facile...
Vai col codice!
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <libxml/parser.h>

// prototipi locali
void deserialize(const char* document);
void recurDoc(xmlDocPtr doc, xmlNodePtr cur);

// main del programma di test
int main(int argc, char **argv)
{
    // test argomenti
    if (argc <= 1) {
        printf("Usage: %s docname\n", argv[0]);
        return(0);
    }

    /* libxml2: initializza la libreria e testa potenziali ABI mismatches
       tra la versione compilata e la attuale shared library usata */
    LIBXML_TEST_VERSION;

    // deserializza XML
    deserialize(argv[1]);

    // esce con Ok
    return EXIT_SUCCESS;
}

// funzione deserialize()
void deserialize(
    const char* document)
{
    // salta i blank nodes
    xmlKeepBlanksDefault(0);

    // estrae il documento dal file
    xmlDocPtr doc;
    if ((doc = xmlParseFile(document)) == NULL ) {
        fprintf(stderr,"Document not parsed successfully. \n");
        return;
    }

    // test se il documento non è vuoto
    xmlNodePtr cur;
    if ((cur = xmlDocGetRootElement(doc)) == NULL) {
        fprintf(stderr,"empty document\n");
        xmlFreeDoc(doc);
        return;
    }

    // ricorre e libera il documento
    recurDoc(doc, cur);
    xmlFreeDoc(doc);
}

// funzione ricorsiva di lettura del documento
void recurDoc(
    xmlDocPtr  doc,
    xmlNodePtr cur)
{
    // loop di lettura
    xmlChar *value;
    cur = cur->xmlChildrenNode;
    while (cur != NULL) {
        // estrae e libera un elemento
        if ((value = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
            printf("key: %-10s - value: %s\n", cur->name, value);
            xmlFree(value);
        }

        // chiamata ricorsiva
        recurDoc(doc, cur);

        // passa al prossimo elemento
        cur = cur->next;
    }

    // esce
    return;
}
E, come sempre:

1) codice auto-esplicativo, ampiamente commentato e... i commenti parlano da soli.
2) il main() serve solo a lanciare la funzione deserialize(): concentratevi su quella.


La funzione, in realtà è divisa in due parti: deserialize() prepara la lettura del documento e recurDoc() (che è una funzione ricorsiva che richiama se stessa) ricorre il documento n-volte fino a quando ci sono campi da leggere.

Questo esempio è adatto a leggere qualsiasi file XML che gli si passa come argomento, e si può specializzare facilmente: ad esempio se gli passate un file di struttura nota (per esempio si può provare con il catalogo XML di film prodotto dal nostro precedente Serializer) potete riempire con i campi key-value letti i campi della struttura dati corrispondente (nel nostro caso la struct Catalog). Insomma, con un po' di immaginazione la coppia Serializer/Deserializer che vi ho proposto, permette un sacco di interessanti attività in programmi che usano dati in formato XML. Buon lavoro...

Vi ricordo nuovamente che, per testare l'esempio su Linux (cosa che ho fatto, ovviamente...) bisogna installare prima la libXML2 (dal repository della distribuzione), e poi compilare il programma con:
gcc desxml.c -I/usr/include/libxml2 -o desxml -lxml2
Ciao e al prossimo post!