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

jueves, 23 de agosto de 2012

No Comment
cómo escribir los comentarios en C

Si sois de los "no escribo comentarios. Son una pérdida de tiempo", paraos aquí. Ya se que no os voy a poder convencer (ni me importa).

Para todos los demás: el comentario es vuestro mejor amigo, nunca os abandonará. El comentario es la última barrera entre el entender y el no entender "¿que tenía en mente cuando escribí ese programa hace un año?". El comentario es vuestra demostración de respeto para vuestros compañeros de trabajo: tarde o temprano, alguno de ellos pondrá sus manos sobre vuestro software y el número de maldiciones que os envíe será inversamente proporcional a la cantidad (y calidad) de los comentarios que habéis escrito.

Claro, hay varias maneras de comentar el código, que van desde "Absolute No Comment" al "auto-explicativo con la adición de comentarios" (bueno, esto es un poco exagerado...), con todos los casos intermedios posibles ...

Vamos a hacer una pequeña demostración: vamos a fijarnos en tres maneras de escribir una función para leer un dispositivo de control industrial (se trata, en el Caso 3, de un código real que escribí hace unos años: Para este ejemplo he cambiado solo algunos nombres). Que se tenga en cuenta que los tres códigos son prácticamente idénticos, aun que la lectura de una impresión completamente diferente ...

Caso 1: código Absolute No Comment

int smsg(S_dev *p)
{
    if (p->msg) {
        p->btx[0]=p->msg;
        if (!sndch(p->ch,(char *)p->btx,(size_t)1))
            return(-1);
        p->msg=0;
    }
    return(0);
}


como se puede ver no hay ningún comentario, el código es (por decirlo suavemente) críptico, y se ahorra en todo: nombres, espacios, líneas: que nunca pase que alguien pueda entender lo que está escrito. Para los maníacos del secretismo.

Caso 2: código auto-explicativo

int sendMessageToDevDummy(
    S_dev_dummy *p_devdum)
{
    if (p_devdum->msg_to_send != 0) {
        p_devdum->buffer_tx[0] = p_devdum->msg_to_send;

        if (! send_char_to_dev(p_devdum->channel_tx, (char*)p_devdum->buffer_tx, (size_t)1))
            return(-1);

        p_devdum->msg_to_send = 0;
    }

    return(0);
}


como se puede ver no hay ningún comentario, pero el código es auto-explicativo y no se ahorra en nada. Los nombres son elegidos para obtener la máxima claridad. Para quien no ama los comentarios, pero quiere que quede claro a toda costa.

Caso 3: código claro y comentado

/* sendMessage()
 * envia un mensaje al device dummy
 */
int sendMessage(
    S_dev_dummy *dev)
{
    // espero los mensajes a enviar
    if (dev->msg_2snd) {
        // copio el mensaje en el buffer de transmision
        dev->buf_tx[0] = dev->msg_2snd;

        // envio el mensaje
        if (! send_char_2dev(dev->chan_tx, (char *)dev->buf_tx, (size_t)1))
            return(-1);

        // libero el semaforo de transmision
        dev->msg_2snd = 0;
    }

    return(0);
}


como se puede ver, hay una cabecera (que debería ampliarse para las funciones importantes) y muchos comentarios breves. El código no es auto-explicativo, pero es lo suficientemente claro. Para comment-lovers.

Es inútil decir que pertenezco a la escuela comment-lovers. Pero también respeto los seguidores del código auto-explicativo. Sobre los señores del Absolute No Comment lo único que puedo decir es ... No Comment. Sin embargo, si os interesa, el Caso 1 no es una exageración, y he visto hasta cosas peores, mejor me callo...

Hasta el próximo post.

lunes, 20 de agosto de 2012

¿ Estás seguro de tus strings ?
cómo tratar las cadenas en C

Vivimos en un mundo real. En un mundo ideal, no habría necesidad de probar el retorno de una malloc(), antes de utilizar la memoria que amablemente nos concedió. Nisiquiera habría necesidad de usar la malloc(). En un mundo ideal, no habría necesidad de probar la seguridad de una aplicación, porque nadie intentaría atacar nunca, con múltiples métodos, nuestro software para buscar fallos extraños.

Sin embargo, vivimos en un mundo real. Y entonces, como el ataque cracker más común se basa en la conocida técnica del buffer overflow, abría que mantener bajo control todas las strings de entrada de una aplicación, ya sea que provengan de otro programa, ya se trate directamente de actividades interactivas. Vale, parece muy complicado y caro, pero si se tiene la costumbre de utilizar algunas funciones simples (escritas ad hoc) de nuestra personal libc, el resultado final no será, entonces, tan antiestético y complicado de implementar.

Un pequeño ejemplo: todas las strings que vienen del mundo exterior las filtramos con una función que podríamos llamar strSecure():

/* strSecure()
 * tratamiento de seguridad (para buffer-overflow) del string <src>: devuelve un string 

 * de max <len> char con '\0' final
 */
static char *strSecure(
    char         *dest,
    const char *src,
    size_t        len)
{
    // tratamiento de seguridad (para buffer-overflow) del string <src>
    if (! memccpy(dest, src, '\0', len))
        dest[len - 1] = '\0';

    // devuelve un string de max <len> char con '\0' final
    return(dest);
}


Es decir, si memccpy() no puede encontrar el terminador lo forzamos nosotros. En práctica se podría usar, más o menos, así (será un ejemplo un poco sintético, lo admito...):

main()
{
    ...
    // lee input string
    char *inputstr = ...
    ...

    // usa input string
    char my_inputstr[80];
    sprintf(buf, "input string is: %s", strSecure(my_inputstr, inputstr, sizeof(my_inputstr));
    ...
}


Ah, se me olvidaba: si queréis vivir bien y seguros, abrigaros en invierno, id ligeros en verano, y, sobre todo, no uséis nunca la gets().

Hasta el próximo post.

¿ Donde está mi atoi() ?

Os voy a proponer una pequeña prueba: abrid un editor y, sin consultar Google, manuales o programas archivados, escribid una función atoi(). No os explico lo que es, porque si el nombre no os suena ya habéis fallado la prueba. Si la memoria no os ha traicionado, deberíais escribir un pequeño main () que llame a vuestra función, pasándole un parámetro fijo y, por ejemplo, escribir el resultado en la pantalla. No perdáis tiempo en escribir un programa interactivo que se encargue del input: el objetivo es sólo escribir una atoi() funcionante.

Bueno, si habéis necesitado más de 5 minutos (bueno, voy a ser generoso: 10 minutos), entonces vais a necesitar un sano repaso de los fundamentos del C. No os sorprendáis, tal vez uno usa C a menudo, quizás también escribe cosas complicadas, pero, gracias al cut&paste de código escrito previamente, o ejemplos encontrados en línea (¡gracias Google!) es posible que uno, a la larga, se olvide de cómo se hacen las cosas simples.

Que ninguno de los que no han podido con la prueba se ofenda: estáis en buena compañía. ¿Por qué creéis que os estoy proponiendo esta prueba? Bueno, hace unos años, me dieron un papel y un lápiz (¡aún más difícil! Sin ni un PC con un compilador para probar) y me dijeron: "Escribe un atoi()". La escribí y me equivoqué descaradamente. Esa misma noche saqué de la librería el K&R y empecé a releerlo, desde la primera hasta la ultima pagina (hacía muchos, muchos, muchos años que no volvía a leerlo entero): fue una gran sorpresa, había argumentos que nisiquiera me acordaba que venían tratados, y descubrí partes olvidadas del C que por (mala) costumbre no había usado en años.

Ahora que tengo la costumbre de actualizar periódicamente mi memoria, la prueba ya no la fallo. Bueno, por supuesto, antes de escribir este post me he obligado en hacer una prueba sorpresa del atoi(), y, creedme, esta vez lo conseguí (después de todo, si no lo lograba, no habría tenido el coraje de escribir este post...).

Hasta el próximo post.