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.

jueves, 16 de agosto de 2012

Oh my, my, strncpy()
cómo usar la strncpy() en C

Esperando alguna idea para un post más interesante (y, seguramente, más largo de preparar) me centraré un poco en la libc y, en particular, en la strncpy(), aunque, en realidad, la elección de la función es sólo un pretexto para dar algunas sugerencias de estilo, como veréis (si tenéis un lapsus de memoria con la libc, haced clic en el nombre de la función).

Escribir en C sin utilizar directamente las funciones de la biblioteca libc es bastante inusual, pero hay casos en que, entre las miles y miles de funciones disponibles, falta solo la que necesitas (o está ahí y no te has dado cuenta). Ejemplo: me ha pasado (es un caso real, lo juro) de tener que leer líneas repetitivas de un file y de tener que extraer sólo una parte de las líneas para copiarlo en un array, o sea, resumiendo:

- tenemos x líneas del tipo "... id = 123456;" (el número cambia en cada línea y en lugar de los puntos hay otras informaciones).
- el número puede ser más o menos largo: de 1 a 8 caracteres.
- hay que extraer el numero, transformarlo en int y copiarlo en un array de int.

Bueno, obviamente hay muchas maneras de hacerlo, y uno podria ser empezar con identificar donde empieza el número y luego usar la strncpy()... pero el número no tiene longitud fija: entonces hay que obtener la longitud y luego usar la strncpy(). O, quizás, usar la strtok(), y así sucesivamente. Y el código se hace siempre más largo y ilegible: que bonito seria solucionarlo todo con una simple llamada a la strcnpy()!

Pero yo digo: quien dijo que si no hay ninguna función en libc que haga lo que yo necesito no puedo reescribirla yo? Estas funciones no son necesariamente misteriosas y indescriptibles. Por ejemplo, la strncpy() es una función muy sencilla, y volver a escribirla para mi propio uso es muy sencillo:

/* myStrncpy()
 * versión especial de la strncpy() que se para al primer ";" y copia max (n-1) char
 * asegurando un "\0" final
 */
char *myStrncpy(
    char         *dest,
    const char *src,
    size_t       n)
{
    size_t i;

    // copia src en dest (con stop a <n - 1> chars o al primer "\0" o al primer ";")
    for (i = 0; i < (n - 1) && src[i] != '\0' && src[i] != ';'; i++)
        dest[i] = src[i];

    // llena con "\0" los chars que quedan
    for ( ; i < n; i++)
        dest[i] = '\0';

    // devuelve un pointer a dest
    return dest;
}


Así, el flujo principal del programa hace una sola llamada a myStrncpy(), sin cosas raras, salvando elegancia y legibilidad. En otro file podemos poner la myStrncpy(), lista para su reutilización en otros proyectos (será parte de nuestra, personal, libc).

Hasta el próximo post.

hello, world

Bueno, el primer post de un blog sobre el arte de la programación en C sólo se podía llamar "hello, world" (ehm. .. "hola, mundo" en Español, pero prefiero la versión original). Como bien saben todos los que, como yo, se han acercado al mundo del C a través del legendario K&R, en el capítulo 1 se muestra el primer ejemplo de programa en C:

int main()
{
     printf("hello, world\n");
}


es obvio que solamente se podía comenzar desde aquí. Vale, el título que veis ahí arriba "El arte de la programación en C" es, quizás, un poco retumbante, pero este blog está dirigido a todos aquellos que consideran la programación un arte, y no simplemente un trabajo, un hobby o un mal necesario (os aseguro que conozco gente que lo considera así). Escribir software es un placer. Un programa no sólo debe funcionar bien y ser eficiente (esto se da por supuesto), sino que también debe ser bello y elegante de leer, comprensible y fácil de mantener, tanto para el autor como para eventuales futuros lectores.

Este blog no es para aquellos que tienen que aprender a programar en C (en la web hay muchos sitios más adecuados), si no para los que ya lo conocen y que les gusta escribir en C. A medida que me vengan a la mente argumentos interesantes, publicare posibles implementaciones (artísticas, espero), y los que quieran hacer comentarios sobre mejoras o diferentes realizaciones, serán bienvenidos.

Hasta el próximo post.