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, 24 de diciembre de 2012

La maldición de la Callback de jade
cómo escribir una Callback en C

Hoy vamos a hablar de un tema un poco especial, las funciones callback. ¿Porqué especiales? Bueno, para empezar, en la biblia del C (el K&R) nunca se habla del argumento, así que un radical C-ista también podría afirmar que "¡las callback no existen!". En realidad, en el K&R se habla ampliamente de los tíos de las callback, es decir, los punteros a función (de los cuales las callback son un caso especial): entonces las callback existen.

No voy a escribir un tratado sobre el uso de las callback (ya puedo escuchar vuestros suspiros de alivio), ni voy a explicar cómo, cuándo, por qué, y (especialmente) si usarlas: hay muchas fuentes en la red interesantes y bien escritas. Eventualmente, sería bueno escribir un post sobre sus tíos, pero dado el tema muy complicado y el probable malestar estomacal que me vendría escribiéndolo, lo aplazo a una fecha futura (para ser claros: incluso Kernighan y Ritchie hablando de Pointers to Functions han titulado el capítulo 5.12 del K&R "Complicated Declarations", y si eran complicadas para ellos...).

¿De qué vamos a hablar entonces? Bueno, yo (como muchos otros, supongo) he usado muchas veces las callback, y he leído código que las usaba (código que era, en la mayoría de los casos, ilegible y difícil de interpretar en relación directamente proporcional a la frecuencia de utilización de las callback, sigh). Y bien, hasta el día en el que escribí una aplicación completa (o sea he escrito, además de la callback, la función a la que pasarla) no me he dado cuenta de algunos detalles ocultos. Por supuesto, si para vosotros las callback no tienen detalles ocultos, podéis dejar de leer lo siguiente y nos vemos en el próximo post.

¿Todavía estáis aquí? Vale, antes de empezar vamos a ver una definición de las callback tomada textualmente de Wikipedia: "una función que se usa como argumento de otra función", y yo agregaría: "y, con frecuencia y/o normalmente, la función llamante es una función de librería". El ejemplo más clásico y conocido citado en literatura es relativo al uso de qsort():
// funzione callback de comparación para la qsort()
static int cbCmpFunc(const void *elem_a, const void *elem_b)
{
    return *(int*)elem_a > *(int*)elem_b;
}

// main
int main(void)
{
    int array[] = {34,12,32,9,10,72,82,23,14,7,94};
    int nelems = sizeof(array) / sizeof(int);

    // ejecuto sort array con qsort()
    qsort(array, nelems, sizeof(int), cbCmpFunc);

    // enseño resultados
    int i;
    for (i = 0; i < nelems; i++)
        printf("%d - ", array[i]);
    printf("\n");

    // salgo
    return 0;
}
La qsort() es una función de libc que implementa el algoritmo de ordenación quicksort, y requiere una función de callback que especifique el tipo de ordenación deseado. Estamos, por tanto, en un caso clásico: qsort() es una función de libreria, y nosotros, a nivel local, la usamos pasándole una callback que, en nuestro ejemplo, se utiliza para ordenar en orden ascendente los números del array.

Y ahora vamos al grano: en otros tiempos, cuando no era fácil como ahora encontrar documentación y ejemplos, se me ocurría leer y escribir código como el que se muestra arriba y me preguntaba (quizás sólo inconscientemente): "pero de donde salen los parámetros elem_a y elem_b de cbCmpFunc()? " y otra vez: "Si llamo a la callback y no le paso explícitamente parámetros, cómo funciona todo?" Bueno, como descubrí más tarde, yo estaba pensando invirtiendo la relación causa-efecto: no era yo quien llamaba la callaback, ¡era la qsort() que la llamaba! Vale, me avergüenza un poco tener que contar una conclusión tan perogrullada, pero, efectivamente, lo comprendí a fondo solo el día en que necesité escribir una función de librería que utilizaba una callback. Claro, ahora con toda la información en la red es mucho más fácil ...

Así que para aclarar, vemos un ejemplo completo (NOTA: el ejemplo se podría escribir todo en un file, pero, como se muestra en los comentarios apropiados debería ser dividido en tres file en un proyecto real):
/* parte que tendría que estar en el file mysort.h
*/
// prototipos para mySort()
typedef int (*mycallback)(int, int);
void mySort(int *array, int nelems, mycallback cmpFunc);

/* parte que tendría que estar en el file mysort.c
 */
// mySort() - función de sort que usa el algoritmo bubblesort
void mySort(int *array, int nelems, mycallback cmpFunc)
{
    // loop sobre todos los elementos de array
    while (nelems > 0) {
        // loop interno con decremento longitud
        int i;
        for (i = 0; i < (nelems -1); i++) {
            // ejecuto callback de comparación
            if (cmpFunc(array[i], array[i + 1])) {
                // eseguo swap di array[i] e array[i+1]
                int temp = array[i];
                array[i] = array[i + 1];
                array[i + 1] = temp;
            }
        }

        // decremento nelems
        nelems--;
    }
}

/* parte que tendría que estar en el file mymain.c
*/
// cbCmpFunc() - función de comparación
static int cbCmpFunc(int elem_a, int elem_b)
{
    return elem_a > elem_b;
}

// main
int main(void)
{
    int array[] = {34,12,32,9,10,72,82,23,14,7,94};
    int nelems = sizeof(array) / sizeof(int);

    // ejecuto sort array
    mySort(array, nelems, cbCmpFunc);

    // enseño resultados
    int i;
    for (i = 0; i < nelems; i++)
        printf("%d - ", array[i]);
    printf("\n");

    // salgo
    return 0;
}
Como podéis ver es muy similar al ejemplo de la qsort(), pero, en lugar de utilizar una función de la libc, se utiliza una función de ordenación escrita ad-hoc, la mySort(). Para este ejemplo he utilizado, para no complicar demasiado el código, un algoritmo de tipo bubblesort, que es (lo sé) un asco, pero, para hacer una prueba sencilla es mas que suficiente. Como notaréis es la mySort(), la que se encarga de escribir los argumentos para la callback, procesando correctamente los otros parámetros que se pasan (array y nelems), y así, mágicamente, aparecen los valores en elem_a y elem_b.

¿Qué os parece? Sí, un poco ingenuo (tal vez), pero que levante la mano quien nunca tuvo dudas en su historia de programador (uhmm... veo pocas manos levantadas). Y si este post ha servido para ampliar el conocimiento sólo a uno de mis lectores estoy super-feliz. Y para los otros, aquellos que ya lo sabían todo de las callback: porque habéis llegado igualmente al final del post?

Hasta el próximo post.

viernes, 7 de diciembre de 2012

Érase una vez la Optimización
cómo optimizar el codigo en C

To be, or not to be, that is the question: Whether 'tis Nobler in the mind to suffer... oops, tal vez me he equivocado de blog. Este no es el blog de literatura Inglesa? Este es un blog sobre programación en C? De acuerdo, ya que estoy, solo hay que cambiar un poco la pregunta: Optimizar, o no optimizar, esa es la pregunta...

La pregunta realmente es: "Cuando escribo código tengo que hacerlo de una manera natural, o más bien instintiva (bueno, siempre y cuando se posee el instinto del programador...) o debo seguir líneas un poco abstrusas y herméticas para que el programa sea más eficiente?" Y, por supuesto, no estoy hablando de algoritmos: en este caso, está claro que un buen algoritmo es mejor que uno malo. Estoy hablando en términos de instrucciones y grupos de instrucciones.

Hagamos una premisa: ya no estamos en el año de la pera. Y considerando esto tenemos que comportarnos/actuar. Antiguamente, los ordenadores eran (respecto a los de ahora) lentos y con pocos recursos, y los compiladores eran mucho más sencillos de los actuales. Y, entonces, utilizando la palabra mágica register se podía acelerar un loop y, consiguiendo escribir un código reducido al máximo omitiendo cualquiera operación (presuntamente) superflua, se obtenían programas mucho más eficientes. ¿Y ahora? ¿Aún tiene sentido escribir código de esta manera? El compilador realmente necesita nuestra ayuda?

El asunto es complejo, y creo que necesitaremos más de un post. En este, para empezar, analizaremos un ejemplo sencillo (?), La optimización de un loop, que es, por supuesto, una parte (de hecho, La Parte) critica para el rendimiento de un programa. Comencemos:
// myFunc1()
// versión con multiplicación, array e índice
int myFunc1(int array[], int nelems)
{
    int i;
    for (i = 0; i < nelems; i++)
        array[i] = i * 2;
}
La función es muy simple, pero contiene un bucle, y si nelems es muy grande podría llegar a ser costoso. Probemos a optimizarla usando dos técnicas: la Strength Reduction y ​​la Induction Variable: Vamos a ver lo que podemos lograr a través de varios pasos:
// myFunc2()
// versión con incremento en lugar de multiplicación
int myFunc2(int array[], int nelems)
{
    int i, incr;
    for (i = 0, incr = 0; i < nelems; i++) {
        array[i] = incr;
        incr += 2;
    }
}

// myFunc3()
// versión con incremento y pointer en lugar de array
int myFunc3(int array[], int nelems)
{
    int *ptr = array;
    int i, incr;
    for (i = 0, incr = 0; i < nelems; i++, ptr++) {
        *ptr = incr;
        incr += 2 ;
    }
}

// myFunc4()
// versión con incremento, pointer y sin indice
int myFunc4(int array[], int nelems)
{
    int *ptr = array;
    int limit = nelems * 2;
    int incr;
    for (incr = 0; incr < limit; incr += 2, ptr++)
        *ptr = incr;
}
Según lo prometido y dicho, en este post no quiero limitarme a proponer técnicas de optimización sin proporcionar datos, así que he escrito un programa de prueba y, en mi ordenador, con un valor de nelems lo suficientemente grande (0xFFFFFFF) los resultados son los siguientes:
- compilación sin optimización
myFunc1 - Tiempo transcurrido: 1.720000 segundos.
myFunc2 - Tiempo transcurrido: 1.340000 segundos.
myFunc3 - Tiempo transcurrido: 1.160000 segundos.
myFunc4 - Tiempo transcurrido: 0.920000 segundos.
Por lo tanto, parece que las técnicas de optimización funcionan. Vamos a probar de compilar optimizando, o sea, dejamos que se ocupe el compilador: usaremos la opción -O2 de GCC. Veamos los nuevos resultados:
- compilación con optimización (-O2)
myFunc1 - Tiempo transcurrido: 0.940000 segundos.
myFunc2 - Tiempo transcurrido: 0.540000 segundos.
myFunc3 - Tiempo transcurrido: 0.540000 segundos.
myFunc4 - Tiempo transcurrido: 0.540000 segundos.
Ohhh, qué sorpresa! Parece que el compilador es mejor que nosotros! Con la opción-O2, la versión sin optimizar (myFunc1) se comporta más o menos como la que hemos optimizado nosotros (myFunc4) sin utilizar -O2. No es una sorpresa: simplemente significa que el compilador ha hecho (probablemente) nuestras optimizaciones manuales. También notamos que, utilizando cualquiera de las versiones optimizadas manualmente, el compilador es capaz de añadir un plus adicional de mejora.

Sólo para confundirnos más las ideas, vamos a hacer otro test, sustituyendo la multiplicación por dos con un Shift Lógico:
/ myFunc2Bis()
// versión con shift lógico en lugar de multiplicación
int myFunc2Bis(int array[], int nelems)
{
    int i;
    for (i = 0; i < nelems; i++)
        array[i] = i << 1;
}
Y vemos la velocidad sin la optimización del compilador:
- compilación sin optimización
myFunc2Bis - Tiempo transcurrido: 0.950000 segundos.
Mira, parece que con usando solo el shift logico se consigue el mismo resultado que se obtiene aplicando todas las técnicas ilustradas (myFunc4). Y si además utilizamos la opción -O2, ¿qué pasa?
- compilación con optimización (-O2)
myFunc2Bis - Tiempo transcurrido: 0.540000 segundos.
obtenemos el mismo resultado optimizado que hemos visto arriba.. Tal vez hemos descubierto el secreto del compilador...

La primera conclusión (pero seguirán otros capítulos): merece la pena transformar un código legible y claro como el de myFunc1() en uno mucho más críptico, como el de myFunc4(), simplemente porque no confiamos en el compilador? O bien, podría añadir, porque somos tan presuntuosos como para pensar que podemos hacerlo mejor que el compilador? Repito: ya no estamos en el año de la pera, y considerando esto tenemos que actuar.

Por lo visto hasta ahora, parece que la única ayudita manual que podemos dar es usar (cuando sea posible, y sólo en los puntos realmente críticos) shift logicos en lugar de multiplicaciones. En este caso, recomiendo añadir un comentario, para evitar las maldiciones de los que leen nuestro código. Y si estáis impacientes de descubrir otras informaciones útiles en la optimización, y no queréis esperar mis próximos episodios (traidores!), os sugiero la habitual ronda en Google, o leer directamente un excelente artículo (en Italiano, lo siento) del igualmente excelente Carlo Pescio.

Volveré sobre el tema, intentando agregar datos reales y trabajo de pruebas (¡habéis visto que he cumplido mi promesa!) a los estudios sólo teóricos disponibles en la red. Y si no, ¿qué hago aquí?

Hasta el próximo post.

miércoles, 21 de noviembre de 2012

El último Apache II - El regreso
cómo escribir un módulo Apache en C - pt.2

Ok, lo admito, el título es preocupante, ya que se parece mucho a esto, pero, al ser la segunda parte de un discurso abierto, no se me ha ocurrido nada mejor. Tal vez me estoy haciendo viejo. Y si en un futuro post me plagio a mi mismo en el texto (así como en el título) avisadme, así cierro el blog antes de que sea demasiado tarde.

Sin embargo, como prometí, hoy voy a explicar cómo escribir, instalar y probar un módulo de Apache elemental. Así que si a alguien le apasiona el argumento, con esta base (¡que funciona!), podrá divertirse en escribir otros más complejos en la cara de aquellos que dicen "C no sirve para el desarrollo Web".

Nuestro modulo básico lo llamaremos, por ejemplo, myapmod. Vamos a empezar con el código, por supuesto:
#include <httpd.h>
#include <http_protocol.h>
#include <http_config.h>

// handler del modulo
static int myapmodHandler(
    request_rec *reqrec)
{
    // test handler
    if (! reqrec->handler || strcmp(reqrec->handler, "myapmod"))
        return DECLINED;

    // test metodo http
    if (reqrec->method_number != M_GET)
        return HTTP_METHOD_NOT_ALLOWED;

    // html output
    ap_set_content_type(reqrec, "text/html;charset=ascii");
    ap_rputs("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n", 
                        reqrec);
    ap_rputs("<html>",  reqrec);
    ap_rputs("<head>",  reqrec);
    ap_rputs("<title>Modulo Apache Elementare</title>", reqrec);
    ap_rputs("</head>", reqrec);
    ap_rputs("<body>",  reqrec);
    ap_rputs("<h1>myapmod: il mio Modulo Apache elementare</h1>", 
                        reqrec);
    ap_rputs("</body>", reqrec);
    ap_rputs("</html>", reqrec);

    // salgo con OK
    return OK;
}

// register hooks del modulo
static void myapmodHooks(
    apr_pool_t *pool)
{
    // set hook handler
    ap_hook_handler(myapmodHandler, NULL, NULL, APR_HOOK_MIDDLE);
}

// estructura global del modulo
module AP_MODULE_DECLARE_DATA myapmod_module = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    myapmodHooks
};
¿Sencillo no? Y he añadido los oportunos comentarios en el código (un poco concisos, debo admitir), así que ni siquiera tengo que explicar cómo funciona. Y, como podréis ver cuando lo uséis por primera vez, el código HTML contenido en este documento se muestra correctamente por el navegador (ohhh... milagro).

Ahora pasamos a la compilación e instalación. Para mayor comodidad y gusto personal propongo una guía para Linux, pero os aseguro que el módulo básico se puede hacer también (si de verdad queréis sufrir) en Windows.

El requisito previo es, por supuesto, tener Apache instalado en el sistema. Buscad, con nuestro amigo Google, una de las millones de guías para convertir tu ordenador en un servidor LAMP (o WAMP) (no voy a describir el procedimiento para no alargar demasiado el discurso, pero, os lo garantizo, es bastante simple). Una vez instalado el servidor Apache hay que asegurarse de que el sistema también está preparado para compilar módulos de Apache: debe estar disponible el paquete apache2-prefork-dev, así que, para verificarlo, ejecutad:
dpkg -l | grep apache2
Y, si en la lista de paquetes que se muestra allí no está el paquete indicado, instaladlo con:
sudo apt-get install apache2-prefork-dev
Y ahora, por fin, podemos compilar e instalar con:
sudo apxs2 -c mod_myapmod.c 
sudo apxs2 -i mod_myapmod.la
Luego, necesitamos crear y editar dos archivos nuevos en el directory /etc/apache2/mods-available. Creamos/abrimos el primero con:
sudo gedit /etc/apache2/mods-available/myapmod.load
y dentro escribimos:
LoadModule myapmod_module /usr/lib/apache2/modules/mod_myapmod.so
A continuación, creamos/abrimos el segundo con:
sudo gedit /etc/apache2/mods-available/myapmod.conf
y dentro escribimos:
<Location /myapmod>
SetHandler myapmod
</Location>
A este punto no nos queda nada más que habilitar nuestro nuevo módulo y reiniciar el servidor Apache (lo que hará que arranquen todos los módulos habilitados, incluido nuestro myapmod):
sudo a2enmod myapmod
sudo /etc/init.d/apache2 restart
Si todo se hace correctamente, navegando con un browser a la URL http://localhost/myapmod se mostrará el código HTML insertado en nuestro módulo. Y si no funciona, entonces probad todos los pasos desde el principio, asegurándoos de que no os habéis perdido algunos errores reportados por el sistema. Buena suerte... (Yo, por supuesto, he probado todo el procedimiento antes de publicar el post, y estoy seguro de que funciona).

Es evidente que con este post no pretendo reinventar la rueda, ya que, si buscais en Google cómo construir un módulo elemental de Apache, vais a descubrir que muchos otros han publicado mucho antes que yo guías similares. ¿Pero queréis compararlo con leer la misma cosa escrita por vuestro C-blogger favorito?

Hasta el próximo post. 

jueves, 15 de noviembre de 2012

El último Apache
cómo escribir un módulo Apache en C - pt.1

De acuerdo, con un título como el de arriba, esto no puede ser, por supuesto, el post (prometido) sobre la optimización de código. Todavía estoy trabajando en ello, y yo no sé cuándo voy a terminar, así que mientras tanto, voy a publicar unos posts mas sencillos. Reitero el consejo de no contener la respiración esperando ese post (puede tardar mucho tiempo ...).

Vayamos al grano: esta vez voy a tratar de disipar un mito (aquí vamos otra vez): aquel del C en la programación Web.

He escuchado decir "C es un lenguaje universal, permite hacer casi cualquier cosa (bla, bla, bla ...), pero no se puede utilizar en el desarrollo Web, hacen falta otros lenguajes." Risa (controlada). Otra risa (larga, esta vez).

En mi opinión, de hecho, un (buen) programador C puede entrar en el mundo Web por la puerta grande: para empezar, la mayoría de los lenguajes utilizados en el desarrollo web son C-like (es decir, sus creadores se inspiraron en la sintaxis de C cuando los diseñaron): PHP, Java, C#, JavaScript, por nombrar sólo algunos. Y si son C-like son fácilmente asimilables por un experto en C. Y si nuestro experto sabe (al menos un poco) también de C++ (algo común entre los buenos programadores C) también el lado OOP de los lenguajes indicados será fácil de interpretar.

Entonces­, un (buen) programador C puede entrar en el mundo de la web muy rápidamente (aunque sólo sea por la forma mentís que posee), y os lo digo por experiencia personal, ya que he pasado por eso (¿entonces estoy diciendo que soy un buen programador de C?). Y, para señalar y completar el discurso (y sin querer ofender a nadie), no creo que el paso inverso sea tan simple (un programador Web puro que rápidamente se convierte en un buen desarrollador C). Sobre esta última afirmación, estoy, sin embargo, muy abierto a comentarios, objeciones e impresiones diferentes de las mias. Tal vez me equivoque.

Dejando, pues, el discurso del lenguaje puro y duro, paso al argumento aplicaciones (y, así, explicaremos el misterioso título de este post): la Web vive de Apache y Apache significa C. Se sabe que alrededor del 70% de los servidores web están basados en Apache (es un hecho), y, si bien es cierto que se puede trabajar durante mucho tiempo en el mundo Web sin tener que escribir un módulo de Apache, también es cierto que alguien tiene que haber escrito los módulos existentes y, tarde o temprano (y por diversas razones), alguien necesitará un modulo ad-hoc para una actividad particular o tendrá que tocar el código de un módulo existente: en este caso se necesitan buenos programadores C. También en el mundo Web. Y ya no hace falta decir nada más.

En mi próximo post voy a presentar un módulo elemental de Apache (que ya lo tengo casi listo), así veremos en práctica que significa lo que he dicho aquí arriba.

Hasta el próximo post.

viernes, 2 de noviembre de 2012

Still Alive

En el caso que alguna alma buena se haya preocupado por mi (relativamente) larga ausencia de estas páginas, quiero asegurarle: I'm still alive (en Inglés suena bien, ¿verdad?).

El hecho es que escribo este blog en mi tiempo libre, y, a veces, no lo tengo.

Otro hecho, más importante aún, es que siempre me gustaría escribir cosas interesantes (de escribir y, sobre todo, de leer) y, a veces, no me viene la inspiración.

El último hecho (sí, el último, así no se aburre nadie) es que, a veces, encuentras un buen argumento, pero te das cuenta de que, para describirlo bien, se necesita una gran cantidad de trabajo preparatorio (como escribir un programa de prueba complejo) y, debido al hecho numero 1 (véase más arriba), los tiempos se alargan...

OK: estoy escribiendo un post (para mí) interesante, del que (spoiler alert) os anticipo el tema: la optimización del código. Existe una amplia literatura sobre esto, pero cuantos se la han leído atentamente? Y cuántos ejemplos reales (con resultados de test) se describen en la literatura? Bueno, ya que hay muchas leyendas urbanas sobre el tema, y a menudo sazonadas con consejos de dudoso valor, he pensado presentar unos cuantos datos reales (espero) indiscutibles.

Y, ya que estoy, os voy a contar (escuchar, escuchar!) un pequeño percance que tuve, personalmente, sobre el tema.

Quedaros a la espera con confianza, pero, mientras tanto, no contengáis la respiración, porque todavía me falta un poco...


Hasta el próximo post.

miércoles, 10 de octubre de 2012

Érase una vez la malloc()
cómo usar la malloc() en C

Hoy vamos a hablar de la asignación dinámica de memoria. Si hacemos una búsqueda rápida en Google del mal reputado malloc() y su uso (probar con "why use malloc", por ejemplo), veremos que hay un montón de preguntas sobre el tema que van desde preguntas sencillas ("¿cómo se usa?") a las dudas existenciales ("¿porqué se usa?"). Bueno, estamos en un tema pseudo-filosófico debido al hecho de que, en realidad, se puede programar en C por un largo tiempo sin tener que utilizar malloc()... pero, en este caso, ¿estamos usando bien el lenguaje? Lo siento, pero la respuesta es ¡NO!

Sin embargo, hagamos primero un poco de claridad. Cualquier persona (incluyendo yo) que se encuentre con la necesidad de escribir (muy) rápido un pequeño programa para probar algo rápidamente - quizás para una emergencia repentina durante algún mantenimiento crítico de software muy urgente (listo para ayer) - ¿como lo escribe? Por supuesto, con miles de variables automáticas, arrays sobredimensionados ("hay mucha memoria"), tal vez algunas variables estáticas y (horror, horror) variables globales. O sea, en fin, uno de esos programas que incluso mientras lo escribes lo bautizas "temp_halgo.c" porque ya sabes que vas a escribirlo de nuevo tan pronto como sea posible o tendras que borrarlo por la vergüenza (nunca se sabe que alguien lo lee por accidente).

Pero aquí, por lo general, hablamos de estilos y de cosas bien hechas: el C tiene una gestión de memoria potente y flexible (es un lenguaje con punteros!), y escribir en C, fingiendo no saberlo es un error. Pero tenga cuidado: esto no significa que tienes que utilizar siempre punteros y malloc() ("de lo contrario no eres un buen programador"), en realidad es todo lo contrario. Dentro de una función las variables automáticas siempre serán (y con razón) la mayoría, también porque (cuando sea posible y correcto) usar el stack en lugar del heap permite mejorar el rendimiento del programa y, entre otras cosas, hace que sea más fácil de escribir, leer y mantener. La asignación dinámica de memoria es una operación costosa para el sistema operativo, añade (obviamente) complicación en el código y aumenta la posibilidad de errores (levante la mano quien nunca se olvidó de utilizar la free() correspondiente de una malloc()) .

Pero, ¿cuándo y por qué utilizar malloc()? Lo siento por la respuesta un poco "perogrullada", pero yo diría:

    1) cuando es indispensable
    2) cuando es mejor

Vemos el primer punto de Pedro Grullo:

¿Cuando es indispensable? La asignación dinámica es esencial en al menos dos casos: el primero es lo de las funciones que devuelven una dirección. Vamos a ver un ejemplo sencillo de una función que duplica una cadena (hay uno casi idéntico en K&R):
char *myStrdup(
    char *src)
{
    // ejecuta un duplicado de src (con sitio para el '\0')
    char *dest = malloc(strlen(src) + 1);
    if (dest != NULL)
        strcpy(dest, src);

    return dest;
}
Simple, ¿no? No hace falta decir (pero lo diré de todos modos), que en una función como esta, hay que usar malloc() y no se puede optar por utilizar la dirección de un array automático en el stack, el cual, debido a que está en el stack, se pierde después del return.

El segundo caso con elección forzada es el de las linked list. Veamos un ejemplo muy simplificado (pero perfectamente compilable y comprobable):
// nodo de una single linked list con campo datos
typedef struct snode {
    int data;
    struct snode *next;
} node_t;

// alloca un nodo con datos y un puntero al próximo elemento
node_t *addNode(
    node_t *next,
    int    data)
{
    node_t *node = malloc(sizeof *node);
    node->data = data;
    node->next = next;
    return node;
}

// inserta un nodo en la cabeza de una lista
void insertNode(
    node_t **head,
    int    data)
{
    node_t *node = addNode(*head, data);
    *head = node;
}

// main() para test
void main() 
{
    // lista vacia
    node_t *head = NULL;

    // inserta 10 nodos con data=indice loop
    int i;
    for (i = 1; i <= 10; i++)
        insertNode(&head, i);

    // recorre la lista y enseña los valores
    while (head) {
        printf("%d\n", head->data);
        head = head->next ;
    }
}
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.

Pasando al segundo punto de Pedro Grullo:

¿Cuándo es mejor? Sin duda, cada vez que se necesite manejar datos (normalmente arrays de tipos simples o de estructuras), de los cuales no se conoce la dimensión en compile-time: si no usamos malloc() se debería asignar arrays de gran tamaño (para evitar falta de espacio en run-time). Y este detalle nos muestra otro punto: si vamos a manejar grandes cantidades de datos, no podemos confiar demasiado en el stack, donde el espacio no es infinito, incluso si trabajamos con modernos sistemas operativos en máquinas llenas de memoria (y si programamos aplicaciones embebidas con grandes limitaciones Hardware... incluso peor).

Con lo que se ha dicho hasta ahora espero, por lo menos, haber contribuído en hacer un poco de claridad sobre esta cuestión. ¡Ah!, en el post he mencionado siempre, para simplificar, malloc(), pero, como todos saben, la asignación dinámica de memoria es una familia de funciones (malloc (), calloc (), realloc () y free()) que permiten una gran flexibilidad y variedad de soluciones.

Bueno, esto es todo, y recordad: por cada malloc() hay un free(): si las cuentas no te salen empieza a preocuparte...

Hasta el próximo post.

jueves, 27 de septiembre de 2012

¿Variables globales? No, Gracias
cómo usar las variables globales en C

Hoy vamos a tratar de destruir otro de los pilares de la no-programación: la variable global. Ojo: las variables globales (así como el goto, por ejemplo) son parte del lenguaje, entonces existen: a veces es posible y/o necesario usarlas. Pero, al igual que el goto, casi siempre es posible prescindir de éllas, con grandes beneficios de estilo (legibilidad y facilidad de mantenimiento, sobre todo) y funcionalidad (menos bugs): Llámalo nada.

Los puntos críticos son muchos, pero ya que no quiero escribir ni un poema ni un libro sobre el tema, he aislado unos pocos. Vamos a ver:

1) Las variables globales no son thread-safe: en la programación multihilo el uso de variables globales puede generar unos mal funcionamientos sutiles y difíciles de encontrar. El caso clásico es la instrucción de lectura/escritura de una global fuera de un área crítica, o sea (por ejemplo) sin la protección de un mutex: en un proyecto de gran envergadura es suficiente un olvido (sólo uno!) de este tipo para darte tantos dolores de cabeza que dejaras de usar las globales para el resto de tu vida como programador. Ver para creer. En este punto, se podría objetar: "Pero yo estoy escribiendo programas normales, no multihilo". Muy bien, declarando previamente (y matizando en la siguiente sección) que un programa "normal" se puede considerar un programa multi-hilo con un solo hilo, doy las gracias por la objeción, que me permite de lanzar el punto 2:

2) las variables globales violan el principio de mantenimiento/reutilización del Software: cuando se escribe profesionalmente Software siempre se debe pensar en el trabajo en equipo, y por lo tanto, en las operaciones de mantenimiento que podrían ser realizadas por otras personas (y, a veces) después de un largo tiempo. Evidentemente, un código, vasto y lleno de globales, es difícil de mantener como un código No Comment (¿recordáis?), porque el historial de una global es poco comprensible, podría verse afectado en muchos lugares diferentes por muchas funciones diferentes, y si para entender un trozo de código tienes que abrir decenas de archivos... os habeis ganado otro dolor de cabeza! Y no hablamos de reutilizar código lleno de globales para otro proyecto: si no lo habéis hecho nunca por lo menos tratar de imaginar la dificultad. Y no sólo: volvemos al punto 1 (thread-safe): ¿quién me dice que el codigo "normal" no tiene que volverse a utilizar (un día) en un proyecto multihilo? Si el código es thread-safe se puede hacer fácilmente, pero si hay globales de por medio... Bueno, buen trabajo (y buena suerte).

3) las variables globales aumentan la dificultad de depuración y multiplican la probabilidad de errores de programación: para la depuración, remito al punto 2: si el valor de una variable es difícil de seguir en términos de mantenimiento también lo será a nivel de depurar. Y habrá más mal funcionamientos para depurar (fantástico!), porque, además de todos los posibles errores de codificación. hay que añadir los de scope: probar este código:
int my_var = 0;

void incrementaMyVar()
{
    my_var += 5;
}

int main(int argc, char **argv)
{
    // hago mil cosas...
    // ...

    // incremento my_var
    incrementaMyVar();

    // hago otras mil cosas...
    // ...

    // declaro una "nueva" variable my var y la uso
    int my_var = 2;        // ah, ah, ah: redeclaración!
    // ...

    // hago todavía mil cosas...
    // ...

    // incremento y test my_var (oops! cual my_var?)
    incrementaMyVar();
    if (my_var == 2)
        formatMyHardDisk();    // uh, uh, uh: era la equivocada!
    // ...

    return 0;
}
El que se ha enseñado arriba era un problema de scope con redefinición local (accidental) de una global. El código que sigue es aún más simple, muestra un descuido de un detalle importante:
int my_var = 0;

void hagoMilCosas()
{
    // hago mil cosas...
    // ...

    // incremento my_var (escondido entre mil instrucciones!)
    my_var += 5;

    // hago otras mil cosas...
    // ...
}

int main(int argc, char **argv)
{
    // hago unas cuantas cosas...
    // ...

    // llamo hagoMilCosas()
    hagoMilCosas();    // oops! he incrementado my_var sin quererlo

    // hago otras cosas...
    // ...

    // test my_var
    if (my_var == 5)
        formatMyHardDisk();    // uh, uh, uh: me he equivocado?
    // ...

    return 0;
}
buena historia, eh?

4) las variables globales violan el principio de encapsulación de variables: bien, esto ni siquiera es necesario que lo explique, una global es cualquier cosa menos que encapsulada... oops, pero esto es OOP, entonces un poco fuera del tema del blog: perdón, quería exagerar. Bueno, ya que estamos hablando de OOP, y entonces de C++, cito con gran placer el grande M.Cline que en su C++FAQ dice (traduzco, vale);
El nombre de una variable global debe empezar con //.

Esta es la forma ideal de declarar una variable global:

// int xyz; <-lo que hace ideal esta global es la inicial //

Esta es la manera ideal de utilizar una variable global:

void mycode()
{
  ...
  // hace_algo_con(xyz);  <-ídem
  ...
}

OK, esto es un juego. Casi. La verdad es que hay casos en que las
variables globales son menos peores que la alternativa - cuando las
globales son el menor de los males. Pero ellas son siempre malvadas.
Así que lavarse las manos después del uso. Dos veces.
Palabras santas.

Sin embargo, si pensáis que todo esto es sólo harina de mi bolsa, intentar pedirle a nuestro amigo Google: global variable are evil? y vais a ver que avalancha de resultados sale. Si estáis interesados en una de las páginas más interesantes la podéis encontrar aquí.

Creo que lo que se ha dicho es suficiente. Obviamente estoy un poco sesgado porque he trabajado mucho sobre software multihilo (y todavía tengo un poco de dolor de cabeza ...), pero os pido que confíeis y difundáis el mensaje lo más posible. Sí, lo sé, decirle a un programador sin experiencia (o a un programador cansado) "no usar variables globales" es como decirle a un niño "no comer demasiados dulces": bueno, hay que hacerlo. La salud en primer lugar.

Hasta el próximo post.

domingo, 9 de septiembre de 2012

No Comment 2 - El regreso
cómo escribir los comentarios en C - pt.2

Hoy ha llegado el momento de completar el discurso del No Comment del que hablamos aquí. Se habló, por así decirlo, del nivel micro (los comentarios sobre los bloques de código), pero nos perdimos el nivel macro, es decir, el propio contenedor (el file) y los grandes bloques de código que son las funciones. Decidme la verdad: si trabajáis (o habéis trabajado) en un equipo, ¿cuántas veces habéis abierto un file escrito por un compañero (que pertenece a la religión No Comment, devoto y practicante) y habéis descubierto que la única pista sobre el código contenido era el nombre del fichero mismo? Y, ya que por la Ley de Murphy, el distinguido compañero estaba de vacaciones (justas y merecidas), habéis pasado el día entero sólo para entender de qué iba el file, y sólo en ese momento, habéis empezado a descifrar el código gracias a la Piedra de Rosetta.

Un file necesita un header y las funciones (al menos las más importantes, que suelen ser las globales) necesitan una cabecera hecha como dios manda (pero también las funciones estáticas se merecen un mínimo de presentación). Espero que el mensaje sea claro.

Bien, y ahora ¿cómo podemos hacer una cabecera? Aunque las posibles variaciones son infinitas, diría que, para empezar, algo muy sencillo como esto, podría funcionar:
/*!
 *  FILE
 *     getComments.c - extracción comentarios de un file C
 *  PROJECT
 *     myDoc - generación automática documentación
 *  FUNCTIONS
 *     globales:
 *        getComments()
 *     estáticas:
 *        formatLine()
 *  AUTHOR
 *     A.Abate
 *  OPERATING SYSTEM
 *     Linux (all versions)
 *  COMPILER
 *     GNU gcc
 *  RELEASE
 *     1.0 (Septiembre 2012)
 */ 
Obviamente se puede abundar más o menos en descripciones. Por ejemplo, si el archivo contiene el código de un protocolo de comunicación, se puede agregar un bloque de descripción (un dibujo en texto) con las entidades involucradas y el flujo de mensajes de comunicación.

En cuanto a las funciones importantes, se puede utilizar la sintaxis clásica de las Man Pages de Unix (y Linux), o sea, hacer algo como esto:
/*!
 *  NAME
 *     getComments - examina un file C para extraer los comentarios
 *  SYNOPSIS
 *     int getComments(
 *        char *inpath,
 *        char *oupath)
 *  DESCRIPTION
 *     Extrae los comentarios de un file C con pathname <inpath> y los
 *     escribe en un file de documentación con pathname <outpath>.
 *  RETURN VALUE
 *     0 a operación efectuada. Un valor negativo en caso de error
 */ 
Y, también aquí, uno puede abundar más o menos con las descripciones, añadiendo, por ejemplo, los detalles de todos los códigos de retorno, y, si es necesario, mediante la inserción de un apartado EXAMPLES adicional, con breves ejemplos de uso.

Como los mas observadores habrán notado, la primera línea de los dos ejemplos anteriores comienza con "/*!", que contiene un símbolo de reconocimiento para una posible herramienta para la auto-generación de la documentación (como Doxygen). Y aún los mas observadores habrán notado que el contenido de los dos ejemplos es acerca de la auto-documentación. El hecho es que he decidido ofrecer un caso real, y que podría ser mejor que escribir, ad-hoc, un instrumento de documentación simple que he escrito y probado en un muy poco tiempo (es realmente muy simple, pero puede ser mejorado) .

Pues bien, la mini-herramienta estaría (casi) toda aquí:
int getComments(
    char *inpath,
    char *oupath)
{
    char *line = NULL;
    size_t len = 0;
    int retcode;
    bool start_comment = false;

    // abro el file de input
    FILE *fpin;
    if (fpin = fopen(inpath, "r")) {
        // abro el file de output
        FILE *fpout;
        if (fpout = fopen(oupath, "w")) {
            // leo el file de input
            while (getline(&line, &len, fpin) != -1) {
                // test final comentarios para reset flag start_comment
                if (strstr(line, "*/") && start_comment) {
                    start_comment = false;
                    fprintf (fpout, "\n\n\n");
                }

                // test flag start_comment
                if (start_comment) {
                    // formateo y escribo la linea en el file output
                    fprintf(fpout, "%s", formatLine(line));
                }

                // test inicio comentarios para set flag start_comment
                if (strstr(line, "/*!"))
                    start_comment = true;
            }

            // libera recursos y set retcode=OK
            fclose(fpout);
            free(line);
            retcode = 0;
        }
        else
            retcode = -2;    // fopen err: set retcode=err

        fclose(fpin);    // libera recursos
    }
    else
        retcode = -1;    // fopen err: set retcode=err

    // sale con el retcode seteado
    return retcode;
}
Yo diría que el código es tan intuitivo (y comentado) que no hay mucho más que añadir: se busca el código de referencia (/ *!) Y se copia en un archivo de documentación todo lo que sigue hasta el final del comentario. La función formatLine() (que he omitido) puede no hacer nada (es decir, devolver directamente la línea pasada), o hacer algo de procesamiento más o menos sofisticado (como mínimo recomiendo quitar el inicio de linea como " *"). Yo, para probar la aplicación, he escrito un main() que lee todos los files fuentes de una directory y escribe los resultados en otra directory (y, yo diría que funciona bien).

Lo que se ha presentado es una alternativa rápida a las herramientas del tipo de Doxigen, que están, por supuesto, recomendadas para grandes proyectos (aunque la alternativa artesanal descrita no esta mal).

Termino con un mensaje para los que piensan (como ya se ha dicho aquí) que escribir comentarios (y, aun mas, los headers y las descripciones de funciones) sea una pérdida de tiempo que alarga el tiempo de desarrollo. Hay que tener la mente abierta, pensar a largo plazo: aunque el tiempo de desarrollo aumente un 10% (y documentar no necesita mucho tiempo, sobre todo si lo haces mientras escribes el código), habrá un día en que se recuperará con intereses el tiempo perdido inicialmente, por ejemplo, en la primera ocasión de mantenimiento o modificación (tratad de hacerlo en un código No Comment, y luego me contáis...). Y el tiempo es oro.

Hasta el próximo post.

domingo, 2 de septiembre de 2012

return, o no return, ésa es la pregunta
cómo estructurar el código en C

Él tenía razón, a veces es difícil tomar decisiones existenciales, y hoy yo os voy a proponer una: ¿salida única o múltiple? Bueno, vosotros diríais: "hay cosas más importantes en que pensar". Por supuesto que si, pero aquí se trata de estilo, y como ya se ha señalado aquí, el estilo es importante. Además esta decisión a tomar no es una invención mía, de esto habló mucho antes que yo uno mucho mejor que yo, aunque ya entonces había alguien que no estaba completamente de acuerdo.

Creo que el dilema es claro: estamos hablando de Programación Estructurada y no de tonterías. Y si alguien está pensando "pero la programación estructurada esta superada", o bien "con la OOP el enfoque es completamente diferente", lo paro de inmediato. Aquí se habla del C, el padre (o al menos el tío) de todas los lenguajes modernos, y C es un lenguaje estructurado. Y punto. Y si pensáis que C y Programación Estructurada están superados, mejor que os toméis una pausa para la reflexión ...

¿Y entonces? Bueno, vamos a ver un ejemplo para ver si así nos aclaramos las ideas. Escribimos una función que abre un archivo, extrae la primera línea, procesa la línea de datos y sale. Como siempre, se podría escribir en varios miles de maneras diferentes, pero aislaremos las dos buenas para el ejemplo (bueno, aislamos también una tercera manera, pero solo para cerrar bonito el post). Consideremos en primer lugar el tipo Single-Exit:
int readFile(
    char *path)
{
    char *line = NULL;
    size_t len = 0;
    int retcode;

    // abro el file en path
    FILE *fp;
    if (fp = fopen(path, "r")) {
        // leo el file
        if (getline(&line, &len, fp) != -1) {
            // test contenido file
            if (!strncmp(line, "output=", 7)) {
                // procesa datos
                // ...

                retcode = 0;    // proc. OK: set retcode=OK
            }
            else
                retcode = -3;   // str err: set retcode=err

            free(line);     // libera recursos
        }
        else
            retcode = -2;   // getline err: set retcode=err

        fclose(fp);     // libera recursos
    }
    else
        retcode = -1;   // fopen err: set retcode=err

    // salgo con el retcode seteado
    return retcode;
}
Y ahora pasamos a analizar su perfecto equivalente del tipo multiple-exit, o sea:
int readFile(
    char *path)
{
    char *line = NULL;
    size_t len = 0;

    // abro el file en path
    FILE *fp;
    if ((fp = fopen(path, "r")) == NULL) {
        // salgo con error
        return -1;
    }

    // leo el file
    if (getline(&line, &len, fp) == -1) {
        // libero recursos y salgo con error
        fclose(fp);
        return -2;
    }

    // test contenido file
    if (strncmp(line, "output=", 7)) {
        // libero recursos y salgo con error
        fclose(fp);
        free(line);
        return -3;
    }

    // procesa datos
    // ...

    // proc. OK: libero recursos y salgo con OK
    fclose(fp);
    free(line);
    return 0;
}
¿Cuál es el mejor tipo? Bueno, para mí (y él) es el tipo Single-Exit, pero hay que decir que el partido del Multiple-Exit es grande y está formado (también) por gente muy preparada.

Entonces, ¿dónde está la verdad, tal vez en el medio? No, decir siempre que la verdad está en el medio es la excusa de los indecisos. Yo elijo la solución con el mejor estilo, la más bella y fácil de leer, y, añado, la que te da más control mientras la escribes: con el tipo Single-Exit es más difícil que te olvides de liberar recursos en el momento adecuado (pensar en un programa más complejo, no en el ejemplo). Y también el tipo Single-Exit es más fácil de leer y depurar (probarlo, pero siempre con un caso más complejo).

Se podría hacer una excepción: cuando con el tipo Single-Exit el nivel de anidamiento es demasiado alto, tal vez se podría hacer un híbrido: empezar con los test básicos (tipo "¿el parámetro pasado es nulo?") y sus respectivos return, y, luego, seguir en Single-Exit (pero, si al final el código resultante no os convence, tal vez es mejor si dividáis en dos la función).

Sin embargo, soy menos radical de lo que parece: entre el código que he escrito en mi pasado hay (basta con ver aquí) código de Multiple-Exit o híbrido. El hecho es que hay que ser un poco flexibles: programar (bien) es un arte, y demasiadas restricciones no ayudan a la expresividad.

Como preanunciado cerramos con el tercer tipo: la siguiente función también funciona, y es un equivalente perfecto de las anteriores:
int readFile(
    char *path)
{
    char *line = NULL;
    size_t len = 0;

    // lo hago todo de una vez
    FILE *fp;
    if ((fp = fopen(path, "r")) && (getline(&line, &len, fp) != -1) &&
            !strncmp(line, "output=", 7)) {

        // procesa datos
        // ...

        // datos procesados: libero recursos y salgo con OK
        fclose(fp);
        free(line);
        return 0;
    }
    else {
        // libero recursos y salgo con error
        if (fp)
            fclose(fp);

        if (line)
            free(line);

        return -1;
    }
}
Bueno, si os estáis planeando escribir una función en este modo (tal vez poniendo aun más test en el mismo if) relajaros, tomaros un descanso, leeros un buen libro y, a continuación, intentarlo nuevamente con la cabeza finalmente libre de malos pensamientos: tal vez (espero) que ya no se os ocurrirá de escribirla de esa manera.

Hasta el próximo post.