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...

viernes, 3 de junio de 2016

Sprintf Driver
por qué no hay que usar la sprintf en C

"¿Hablas conmigo? ¿Me lo dices a mí?... Dime, ¿Es a mí?". Sí, al igual que el gran De Niro en Taxi Driver, esa fue mi reacción (incrédula) cuando descubrí (hace muchos años, ahora) que, después de años y años de uso honrado, tendría que dejar de utilizar la sprintf() .
...Y tu me dices de no utilizar la sprintf? A mí?...
Bueno, en realidad, si la usas bien, y tienes el 100% de control sobre el código escrito, también puede utilizarla sin grandes problemas, pero, como dicen los ingleses, la sprintf() es error prone, fácilmente te lleva a cometer errores, incluso graves. El problema más grave y evidente con la sprintf() se llama buffer overflow, y no creo que sea necesario gastar muchas palabras en eso: si el búfer que pasamos como primer argumento no es del tamaño correcto el desastre está detrás de la esquina.

Afortunadamente viene en nuestro auxilio la snprintf(), que es de la misma familia, pero más segura. Vemos los dos prototipos en comparación:
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
La snprintf() nos obliga a poner el size del buffer como segundo argumento, entonces es muy fácil coger la costumbre de escribir de una manera error-free como esta:
char buffer[32];
snprintf(buffer, sizeof(buffer), "Hello world!");
Si en lugar de "Hello world!" hubiéramos escrito una cadena de más de 32 chars, ningún problema: la snprintf() trunca la cadena de manera adecuada y estamos a salvo.
Y ahora os propongo un pequeño ejemplo real: tomamos una nuestra vieja conocida escrita para un viejo post, la getDateUsec() y la vamos a escribir en dos versiones, una buena y otra mala (bad). Veamos:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>

// prototipos locales
char *getDateUsec(char *dest, size_t size);
char *badGetDateUsec(char *dest);

// función main
int main(int argc, char* argv[])
{
    // llama getDateUsec (o badGetDateUsec) y escribe el resultado
    char dest[12];
    printf("fecha con usec: %s\n", getDateUsec(dest, sizeof(dest)));
    //printf("fecha con usec: %s\n", badGetDateUsec(dest));

    return EXIT_SUCCESS;
}

// getDateUsec() - Genera una cadena con fecha y hora (usa los microsegundos)
char *getDateUsec(char *dest, size_t size)
{
    // get time (con gettimeofday()+localtime() en lugar de time()+localtime() para obtener los usec)
    struct timeval tv;
    gettimeofday(&tv, NULL);
    struct tm *tmp = localtime(&tv.tv_sec);

    // format cadena destinación dest(debe ser alocada por el llamante) y añade los usec
    char fmt[128];
    strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp);
    snprintf(dest, size, fmt, tv.tv_usec);

    // return cadena destinacion dest
    return dest;
}

// badGetDateUsec() - Genera una cadena con fecha y hora (usa los microsegundos) (versione bad)
char *badGetDateUsec(char *dest)
{
    /// get time (con gettimeofday()+localtime() en lugar de time()+localtime() para obtener los usec)
    struct timeval tv;
    gettimeofday(&tv, NULL);
    struct tm *tmp = localtime(&tv.tv_sec);

    // format cadena destinación dest(debe ser alocada por el llamante) y añade los usec
    char fmt[128];
    strftime(fmt, sizeof(fmt), "%Y-%m-%d %H:%M:%S.%%06u", tmp);
    sprintf(dest, fmt, tv.tv_usec);

    // return cadena destinacion dest
    return dest;
}
Vale, en aquel entonces, para simplificar, había escrito una getDateUsec() que era, de hecho, una badGetDateUsec() (y más tarde, por la precisión, procedí en modificarla en el post). Esa versión funcionaba, pero podía crear problemas, mientras que la nueva versión es mucho más segura. Intentad compilar el ejemplo, adonde, deliberadamente, he subdimensionado el buffer de destino: comentando badGetDateUsec() y usando la getDateUsec(), funciona perfectamente, truncando el output a 12 chars. Si, sin embargo, se comenta la getDateUsec() y se utiliza la badGetDateUsec() el programa peta mientras se ejecuta. !Inténtelo!

Y ya que estamos en el argumento sprintf() un pequeño consejo un poco OT: si necesitáis agregar secuencialmente una cadena (en un bucle, por ejemplo) sobre una cadena base (para redactar un texto, por ejemplo) no hacerlo nunca así:
char buf[256] = "";
for (int i = 0; i < 5; i++)
    sprintf(buf, "%s añadido a la cadena %d\n", buf, i);
el método aquí arriba parece funcionar, pero, en realidad, funciona cuando le da la gana. Hacerlo, en cambio, así:
char buf[256] = "";
for (int i = 0; i < 5; i++) {
    char tmpbuf[256];
    sprintf(tmpbuf, "%s añadido a la cadena %d\n", buf, i);
    sprintf(buf, "%s", tmpbuf);
}
Y si no me creéis probad a verificar el código con un lint como cppchek (que siempre es una buena idea) o consultad el manual de la 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).
Y, por supuesto, también en este último ejemplo (hecho, por simplicidad, con la sprintf ()) seria recomendable usar la snprintf ().

¡Hasta el próximo post!

viernes, 13 de mayo de 2016

Strerror Simple
cómo escribir una pseudo-strerror en C

Este es un post fácil. Tal vez un poco más de el Blood del debut de los grandes Coen brothers (um... la sangre nunca es fácil). Sí, un post fácil fácil, pero más útil de lo que parece, sólo para demostrar que a problema simple solución simple: si para hacer una tontería habéis escrito 1000000 de líneas incomprensibles de código os habéis equivocado en algo: mejor borrar y empezar de nuevo, sin duda os saldrá mejor.

...¿y esto le llaman simple? ¿¿¿SIMPLE???
Entonces, supongamos de tener unos códigos que representan algo (errores, tipos de mensajes, warning de sistema, zapatillas, quesos... lo que sea), y queréis, por ejemplo, mostrarlos en un bonito sistema de log (tal vez como el que se ha visto aquí, aquí y aquí), pero no como números (bueno, somos seres humanos, no Sheldon Cooper), estamos acostumbrados a ver cosas, no números. Queremos ver las cadenas mnemónicas correspondientes a los números.
...y desde hoy os voy a hablar solamente de forma binaria...
Vale, escribir el código para una cosa así es realmente muy simple... ¡Vamos con el codigo!
#include<stdio.h>

// códigos cadenas
#define CODE_SPIDERMAN  0
#define CODE_THOR       1
#define CODE_DAREDEVIL  2
#define CODE_PUNISHER   3

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

// obtiene una cadena
const char* getString(
    int code)
{
    // calcula size
    int size = sizeof(strings) / sizeof(char*);

    // test si el codigo está en overflow
    if (code >= 0 && code < size)
        return(strings[code]);
    else
        return("error: código no valido");
}

// main() para test
void main()
{
    // loop de input
    for (;;) {
        // pide el codigo
        int code;
        printf("get cadena numero? ");
        scanf("%d", &code);

        // enseña la cadena correspondiente al codigo
        printf("cadena = %s\n", getString(code));
    }
}
Entonces, a fin de no repetirme: el código anterior es, por supuesto,  auto-explicativo, ampliamente comentado y los comentarios hablan por sí mismos. 


Como os habréis dado cuenta es suficiente (por lo menos en una implementación básica) definir los códigos y escribir las cadenas correspondientes (¡en el mismo orden!) en un array. A continuación, se escribe la función para extraerlas que ejecuta, simplemente, un return de array[codigo]. He añadido un mínimo (esencial) de controles, para evitar el uso de códigos negativos o no existentes (overflow). Notar que el size se calcula de forma dinámica, por lo que podemos añadir, con el tiempo, códigos (y cadenas), sin necesidad de cambiar la getString() (¡muy bien!). Obviamente el código presentado (que también incluye un sencillo main() de test interactivo) se puede complicar y sofisticar como se desee, pero respetando la idea básica, que es simple.

En un proyecto real, el file anterior hay que dividirlo en tres: los códigos (y un prototipo) irán a un header-file que será incluido por todos los source-files de la aplicación que necesitan utilizar la getString(). La getString() y el array (estático) de cadenas irá en un source-file específico (que llamaremos getstring.c) que habrá que compilar y linkar con el resto de los archivos del proyecto.

¿Porque en el título he mencionado la strerror()? Porque la nuestra getString() hace, más o menos, el mismo trabajo que la strerror() de sistema, o sea devuelve la cadena correspondiente al código de error errno. Obviamente, la strerror() es un poco más sofisticada: utiliza un buffer estático interno sobre el cual ejecuta strcpy(), y otras cositas... y es por causa de el buffer interno que no es thread safe y, en algunos casos, nos obliga a utilizar la strerror_r(): pero eso es otra historia, y esto es un post simple, no hay que divagar.

Por cierto, si alguien de trabajo hace el que complica la cosas sencillas (que, por desgracia, es un trabajo bastante común) todo lo dicho hasta ahora le resultará indiferente, tal vez incluso molesto. Pero tal vez no debería leer este blog, será pasado por aquí por error.

¡Hasta el próximo post!

sábado, 16 de abril de 2016

The (Duel)lists
cómo usar las Linked Lists en C

Si alguien habla mal de las Linked Lists lo reto a un duelo. Sí, lo haré igual que Harvey Keitel en una de las obras maestras de Ridley Scott. Tener cuidado.
¿Qué decías de las Linked Lists?
Por aquí ya habíamos abordado (indirectamente) el tema de las Linked Lists, en un post prehistórico sobre la malloc(): tendríais que ir a releerlo, pero, si realmente no tenéis ganas de hacerlo, os resumo una parte:

"Para aquellos que nunca la han utilizado, las linked list no son una frivolidad: son una de las herramientas más poderosas de la programación en general (y del C en particular). Los que trabajan intensamente en proyectos grandes y profesional, más pronto o más tarde, acabaran por usarlas: una razón más, por lo tanto, para familiarizarse con la asignación de memoria dinámica."

Premisa: si alguien no tiene la más mínima idea de lo que una lista enlazada le aconsejo la lectura (antes de continuar) de una de las muchas descripciones (algunas realmente buenas) que se encuentran en la red. Esta, por ejemplo. Para este nuevo post he decidido retomar el código que había escrito para el viejo y simplificarlo/mejorarlo de manera de obtener:
  1. condensar en una sola función las dos que en el viejo código creaban un nuevo nodo en la lista (¡simplificar!).
  2. añadir una función que permite colgar un nuevo nodo en lugar de ponerlo en la cabezas de la lista (¡mejorar!).
¡Vamos con el codigo!
#include<stdlib.h>
#include<stdio.h>

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

// alloc en la cabeza de una lista un node con datos y un pointer al próximo elemento
void addNode(
    node_t **head,
    int    data)
{
    // alloc de un nuevo node
    node_t *node = malloc(sizeof(node_t));
    node->data = data;
    node->next = *head;

    // asigna head lista al nuevo node
    *head = node;
}

// alloc al final de una lista un node con datos y un pointer al próximo elemento
void appendNode(
    node_t **head,
    int    data)
{
    // alloc un nuevo node
    node_t *node = malloc(sizeof(node_t));
    node->data = data;
    node->next = NULL;

    // append el nuevo node
    node_t *current = *head;
    if (current == NULL) {
        // caso especial para lista vacia: asigna head lista al nuevo node
        *head = node;
    }
    else {
        // recurre la lista para encontrar el ultimo node
        while (current->next != NULL)
            current = current->next;

        // asigna al ultimo node el nuevo node
        current->next = node;
    }
}

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

    // init lista vacia 1 y inserta con addNode() 3 nodes con data = índice loop
    node_t *head1 = NULL;
    for (i = 1; i <= 3; i++)
        addNode(&head1, i);

    // recurre la lista y enseña los valores
    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 vacia 2 y inserta con addNode() 3 nodes con data = índice loop
    node_t *head2 = NULL;
    for (i = 1; i <= 3; i++)
        appendNode(&head2, i);

    // recurre la lista y enseña los valores
    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 ;
    }
}
Como se puede ver es bastante simple y conciso, y, como siempre, el código es auto-explicativo, ampliamente comentado y los comentarios hablan por sí mismos.

En el main() he añadido un poco de trazas de log, que nos ayudan, mientras se ejecuta, a entender lo que ocurre cuando añadimos un nodo y cuando, en alternativa, lo colgamos. El resultado final es el mismo (en el ejemplo se muestra una lista con tres nodos), pero el diseño cambia: en el caso append tenemos una disposicion más lógica de nodos: la lista crece hacia adelante, y cada nuevo nodo es el último. En el caso insert, sin embargo, es la cabeza de la lista que cambia en cada inserción, y enyonces, cuando leemos los datos, los encontramos invertidos respeto  la orden de inserción.

Ambas listas tienen usos válidos (por ejemplo una es mejor para crear colas FIFO y la otra es mejor para crear colas LIFO), y, para aplicaciones más sofisticadas, donde se necesita añadir/leer/borrar nodos en posiciones determinadas se puede decidir (casi) sin distinción por una estructura o la otra. Normalmente yo uso el método de append, que me parece más lógico, aunque, lo admito, la función de append es un poco más complicada y menos inmediata. Pero la lista ordenada hacia adelante me parece más consistente y fácil de manejar.

Después de todo el programa nos enseña este 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))
que creo que es inútil explicar (¡habla por sí mismo!). Acabo con una ultima consideración: evidentemente lo que se ha enseñado es solo un programa de test: en una aplicación real habrá que añadir unas cuantas funciones accesorias para buscar y borrar nodos, etc. Y, visto che usamos unas malloc(), habrá que acordarse de usar las correspondientes free()...

Bueno, os deseo un buen trabajo con las Linked Lists, las cuales, sin duda, os darán unas satisfacciones. Y si no las dan, paciencia: si estos fuesen los problemas de la vida...

¡Hasta el próximo post!