En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

lunes, 4 de enero de 2016

Mad Sleep
cómo escribir un wrapper para la nanosleep() en C

La sleep() tiene personalidades múltiples, tal vez está un poco loca, al igual que nuestro amigo Max. Si queremos enviar a dormir por un tiempo la nuestra aplicación (normalmente un thread de una aplicación, pero vamos a tratar este tema en otra ocasión...) tenemos muchas (¿demasiadas?) opciones: sleep(), usleep(), nanosleep(), ¡y más todavia!
...¿a quién has llamado loco?
Vamos a centrarnos en las tres mencionadas anteriormente: cada una tiene sus pros y sus contras:
  • sleep(): está bien, pero tiene una resolución demasiado baja: un segundo es una eternidad para algunas aplicaciones.
  • usleep(): es obsoleta: fue eliminada por el estándar Posix, ya que tiene una respuesta a las señales indefinida.
  • nanosleep(): es OK (alta resolución, trata las señales), pero tiene una interfaz un poco complicada...
Para el uso normal (se podría decir  usos no hard-realtime, pero vamos a tratar este tema en otra ocasión...) nos necesitaría una función con el interfaz de la sleep() y una resolución media, yo diría en milisegundos. Pues, entonces, ¡hacemos un wrapper para la usleep()!

¡Vamos con el código!
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// prototipos locales
static void mySleep(long msec);
static char *getDateUsec(char *date);

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

    // enseña la hora, sleep y enseña otra vez la hora
    char date[32];
    printf("hora antes de la sleep:  %s\n", getDateUsec(date));
    mySleep(1500);
    printf("hora después de la sleep:%s\n", getDateUsec(date));

    // esce con Ok
    return EXIT_SUCCESS;
}

// mySleep - sleep para un numero especifico de milisegundos
void mySleep(
    long msec)              // sleep time en milisegundos
{
    // split argumento en segundos y milisegundos (msec está en milisegundos pero podría ser > 1000)
    unsigned int seconds      = msec / 1000;
    unsigned int milliseconds = msec % 1000;

    // set datos para nanosleeep() y los usa
    struct timespec t;
    t.tv_sec  = seconds;
    t.tv_nsec = milliseconds * 1000000; // i.e.: tv_nsec=500000000; 500 millones de ns es 1/2 sec
    nanosleep(&t, NULL);
}

// getDateUsec - obtiene date-time con los microsegundos
char *getDateUsec(
    char* date)             // string destinación
{
    ...
}
Y, como siempre:
  1. Codigo que se auto-explica, ampliamente comentado y... los comentarios hablan por sí mismos.
  2. La función main() no hace más que arrancar la mySleep() y a enseñar (con los datos de tiempo antes y después) que la función trabaja.
    La mySleep() es el nuestro wrapper, y se puede integrar fácilmente en cualquier aplicación o en una librería. Internamente utiliza la nanosleep(), formateando oportunamente los datos a pasar con el único argumento (en milisegundos) que le ofrecemos. El main() muestra (con una precisión de microsegundos) el tiempo antes y después de la mySleep(), utilizando otra función escrita ad-hoc, la getDateUsec(): esta es una nuestra vieja conocida descrita aqui, y por eso no he reportado su código (bueno, tal vez asi vais a leer el antiguo puesto donde fue descrita).

    Una última nota para (eventuales) súper-perfeccionistas que habrán pensado, al leer el código: "pero el split de los argumentos se podría hacer directamente en las asignaciones a struct timespec t, ahorrando las dos instrucciones de asignación precedentes...". Bueno, no voy a perder tiempo discutiendo y los invito sólo en reflexionar sobre los siguientes tres puntos:
    1. mySleep() sirve para retrasar: sabéis lo que nos importa la eficiencia en este caso...
    2. Leer esto.
    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).
    Os recuerdo una vez más que, para probar el programa en Linux (lo cual hice, por supuesto...) es suficiente compilar el programa con:
    gcc mysleep.c -o mysleep
    ...uhmmm... tengo que admitir que la mención del mítico D.Knuth es la parte más interesante de todo el post. Meditar gente, meditar...

    ¡Hasta el próximo post!

    lunes, 7 de diciembre de 2015

    Mission: Impossible - XML Deserializer
    cómo escribir un XML Deserializer en C

    Según lo prometido, volvemos con una nueva misión (no mucho) imposible para proponer una Deserializer complementario al Serializer del último post. Obviamente, si todavía no habéis leído el post anterior, deberíais:

    1) avergonzaros
    2) ir a leerlo. ¡Inmediatamente!


    Ahora, si estáis leyendo esto, puede ser que ya habéis pasado los dos puntos anteriores, entonces podemos continuar. Vamos a utilizar también esta vez la libXML2 para escribir una función que consigue extraer todas las parejas key-value que componen un documento XML.
    Siempre más fácil...
    ¡Vamos con el código!
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <time.h>
    #include <libxml/parser.h>
    
    // prototipos locales
    void deserialize(const char* document);
    void recurDoc(xmlDocPtr doc, xmlNodePtr cur);
    
    // main del programa de test
    int main(int argc, char **argv)
    {
        // test argumentos
        if (argc <= 1) {
            printf("Usage: %s docname\n", argv[0]);
            return(0);
        }
    
        /* libxml2: inicializa la librería y testea potenciales ABI mismatches
           entre la versión compilada y la actual shared library usada */
        LIBXML_TEST_VERSION;
    
        // deserializa XML
        deserialize(argv[1]);
    
        // sale con Ok
        return EXIT_SUCCESS;
    }
    
    // función deserialize()
    void deserialize(
        const char* document)
    {
        // elimina los blank nodes
        xmlKeepBlanksDefault(0);
    
        // extrae el documento del file
        xmlDocPtr doc;
        if ((doc = xmlParseFile(document)) == NULL ) {
            fprintf(stderr,"Document not parsed successfully. \n");
            return;
        }
    
        // test si el documento non está vacio
        xmlNodePtr cur;
        if ((cur = xmlDocGetRootElement(doc)) == NULL) {
            fprintf(stderr,"empty document\n");
            xmlFreeDoc(doc);
            return;
        }
    
        // recurre y libera el documento
        recurDoc(doc, cur);
        xmlFreeDoc(doc);
    }
    
    // función recursiva de lectura del documento
    void recurDoc(
        xmlDocPtr  doc,
        xmlNodePtr cur)
    {
        // loop de lectura
        xmlChar *value;
        cur = cur->xmlChildrenNode;
        while (cur != NULL) {
            // extrae y libera un elemento
            if ((value = xmlNodeListGetString(doc, cur->xmlChildrenNode, 1)) != NULL) {
                printf("key: %-10s - value: %s\n", cur->name, value);
                xmlFree(value);
            }
    
            // llamada recursiva
            recurDoc(doc, cur);
    
            // pasa al próximo elemento
            cur = cur->next;
        }
    
        // sale
        return;
    }
    Y, como siempre:

    1) codigo que se auto-explica, ampliamente comentado y... los comentarios hablan por sí mismos.
    2) la función main() no hace más que arrancar la función deserialize(): concéntrese en esa.


    La función, en realidad, está dividida en dos partes: deserialize() prepara la lectura del documento y recurDoc() (que es una función recursiva que se llama a si misma) recurre el documento n-veces hasta que haya campos para leer.

    Este ejemplo es apto para leer cualquier archivo XML que se le pasa como argumento, y se puede especializar fácilmente, por ejemplo si les pasais un file de estructura conocida (por ejemplo, se puede intentar con el catálogo XML de películas producidas por nuestro anterior Serializer) podeis rellenar los campos con lo campos key-value de la estructura de datos correspondiente (en nuestro caso la struct Catalog). En pocas palabras, con un poco de imaginación, la pareja Serializer/Deserializer que os he propuesto, permite una gran cantidad de actividades interesantes en programas que utilizan datos XML. Buen trabajo...

    Os recuerdo una vez más que, para probar el programa en Linux (lo cual hice, por supuesto...) se debe instalar antes la libXML2 (desde el repository de la distribución), y luego compilar el programa con:
    gcc desxml.c -I/usr/include/libxml2 -o desxml -lxml2
    ¡Hasta el próximo post!