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, 20 de enero de 2014

Syslog Royale
cómo escribir un Syslog en C - pt.1

Al escribir software siempre se debe intentar ser profesionales, independientemente del uso del software que estamos escribiendo. Por ejemplo, no se puede olvidar de prever un buen sistema de log (hay, también, algunos profesionales de la programación que ni siquiera saben lo que es un buen sistema de log... pero esta es otra historia.)

Para agregar un log al nuestro software podemos ayudarnos con el sistema operativo, por ejemplo en UNIX y Linux tenemos Syslog que es muy flexible y que nos hace la vida más fácil. En el caso de no poder (o no querer) utilizar los recursos proporcionados por el OS podemos pensar de escribir nuestro propio sistema de log, como veremos más adelante en este post. Vamos a tratar de emular , más o menos, la sintaxis y la función de Syslog, tratando de hacer un producto sencillo pero de calidad, o sea un producto profesional, porque siempre tratamos de ser profesionales, tal vez sin llegar a los excesos de profesionalidad del protagonista del título.
Me llamo Log, James Log
Sea porque no quiero hacer un post muy largo (se convertiría en aburrido), que por seguir una especie de enfoque específica+implementación (adaptado a un C -Blog ), he dividido el post en dos partes: en la primera describiré, a modo de especifica funcional, el header file (mylog.h) y un ejemplo de uso (main.c). En la segunda entrega describiré la implementación real .

Vemos el header file, mylog.h:
// niveles de mylog
#define MYLOG_NOLOG   -1 // ningún mensaje de log
#define MYLOG_ERROR   0  // condición de errore
#define MYLOG_WARNING 1  // condición de warning
#define MYLOG_DEBUG   2  // mensaje de debug

// prototipos globales
void myOpenLog(const char *fname, int level);
void myLog(int level, const char *format, ...);
void myCloseLog();
Simple y que se explica por sí mismo, ¿no? Nuestro producto se compone de tres funciones canónicas (abre, usa, cierra), y trabaja en niveles de gravedad (actualmente cuatro). Casi no hace falta explicar que, estableciendo un nivel, en el logfile aparecerán todos los mensajes de nivel de gravedad igual o mayor del nivel seleccionado. El nivel de NOLOG, luego, es básicamente una flag de inhabilitación que nos permite iniciar la aplicación sin logfile, para aligerar la ejecución: se supone que debe ser una inhabilitación real, es decir, la CPU y el sistema de I/O deben realimente trabajar menos sin el log habilitado.

Veamos ahora el ejemplo de uso, main.c:
#include <stdio.h>
#include <stdlib.h>
#include "mylog.h"

int main(int argc, char* argv[])
{
    // test argumentos
    if (argc != 3) {
        printf("mylog: wrong arguments counts\n");
        printf("usage: mylog logfile loglevel [e.g.: mylog log.txt 1]\n");
        return EXIT_FAILURE;
    }

    // open log: set logfname y loglevel (0=error/1=warning/2=debug; -1=nolog)
    myOpenLog(argv[1], atoi(argv[2]));

    // test mylog()
    myLog(MYLOG_ERROR, "esto es un msg de tipo %d (el nivel impostado es %d)",
        MYLOG_ERROR, atoi(argv[2]));
    myLog(MYLOG_WARNING, "esto es un msg de tipo %d (el nivel impostado es %d)",
        MYLOG_WARNING, atoi(argv[2]));
    myLog(MYLOG_DEBUG, "esto es un msg de tipo %d (el nivel impostado es %d)",
       MYLOG_DEBUG, atoi(argv[2]));

    // close log
    myCloseLog();

    // exit
    return EXIT_SUCCESS;
}
Sencillo, ¿no? Ejecuto un test de argumentos con ejemplo de uso (usage...), y luego abro, uso, cierro, salgo. Mediante una línea de comandos como "mylog log.log 1", ¿como será el resultado? Creará un file "log.log" que contendrá las siguientes dos líneas:
2014-01-18 18:39:33.890271 - esto es un msg de tipo 0 (el nivel impostado es 1)
2014-01-18 18:39:33.890407 - esto es un msg de tipo 1 (el nivel impostado es 1)
Añadimos detalles a la específica: usos sucesivos de la aplicación deben colgar líneas al logfile, ya que una clave fundamental de un log es mantener información de lo que pasa (y si cada vez reiniciamos el file la vamos a perder). Y si queremos empezar desde cero, podemos utilizar un otro filename, o borrar el viejo logfile antes de ejecutar. Otro detalle que llama la atención es  que necesitamos, además de nuestros textos de traza, también informaciones horarias, tal vez muy precisas (en el ejemplo hay los microsegundos: en el próximo episodio explicaré por qué).

Para hoy terminamos. Los mas trabajadores podrán, esperando la segunda parte, escribir su propia implementación, y luego compararla con la mía, pero la mía será seguramente mejor... (se aleja riendo).

¡Hasta el próximo post!

martes, 3 de diciembre de 2013

Bitwise operations: ep.II - El ataque de los shift
cómo usar los operadores Bitwise en C - pt.2

Ok, como había prometido aquí, volvemos al tema del bitwise para describir algunos resultados inesperados y proporcionar algunos ejemplos sencillos.

Vamos a empezar con las dolientes notas: Como se mencionó en el anterior post, las multiplicación y división con shift, dependiendo del tamaño del tipo del operando y la presencia o ausencia del bit de signo, pueden dar resultados inesperados. Vamos a ver un ejemplo:
void main()
{
    // 1) SHIFT hacia la izquierda con unsigned int
    unsigned int a, b;
    a = 74;           // 0 0 1 0 0 1 0 1 0
    b = a << 2;       // 1 0 0 1 0 1 0 0 0 resultado b=296
    printf("var = %d; var << 2 = %d\n", a, b);

    // 2) SHIFT hacia la izquierda con unsigned char
    unsigned char c, d;
    c = 74;           // 0 1 0 0 1 0 1 0
    d = c << 2;       // 0 0 1 0 1 0 0 0 resultado d=40
    printf("var = %d; var << 2 = %d\n", c, d);

    // 3) SHIFT hacia la derecha con signed char (o int)
    char e, f;
    e = -4;           // 1 1 1 1 1 1 0 0
    f = e >> 2;       // 1 1 1 1 1 1 1 1 resultado f=-1
    printf("var = %d: var >> 2 = %d\n", e, f);
    // N.B.: sin extensión de signo sería:
    // e = -4;        // 1 1 1 1 1 1 0 0
    // f = e >> 2;    // 0 0 1 1 1 1 1 1 resultado f=63
}
El caso 1 es claramente el caso funcionante: un int es mucho más grande que los 8 bits utilizados para representar el número inicial (74), por lo que el shift no pierde ningún 1 en el lado izquierdo (este es el posible problema) y el resultado es correcto (74x4 = 296). Sin embargo, si utilizamos (caso 2) un char (8 bits) durante el shift perdemos un 1 y el resultado va en overflow (¿¿ 74x4 = 40 ??). Así que ¡tengan cuidado!

El caso 3, además, es aún más insidioso: hacer operaciones con signo (en los casos 1 y 2 utilicé variables unsigned en preparación del paso 3) y usando valores negativos, pueden pasar cosas extrañas: para la representación misma de los números negativos en binario (complemento a 2) el bit más a la izquierda (MSB) es el bit de signo, y, en este caso la operación de shift es machine-dependent: en función del tipo de CPU puede tener o no la extensión de signo (por defecto, por ejemplo, en las máquinas de Intel), por eso el shift del ejemplo puede dar el resultado esperado (-4/4 = -1) o un resultado completamente diferente (¿¿ -4/4 = 63 ??). Una vez más, ¡tener cuidado!

Y ahora es el momento de mostrar algunos ejemplos prácticos de uso de lo anterior, que de otro modo seria know-how sin sentido: vamos a ver cómo leer el estado de cada bits de una word utilizando una máscara:
void main()
{
    // uso de una mascara para leer los bit de una word
    unsigned char mask = 1;     // 0 0 0 0 0 0 0 1
    unsigned char word = 74;    // 0 1 0 0 1 0 1 0

    // loop de lectura
    int i;
    for (i = 0; i < 8; i++)
        printf("el bit %d de la word es %s\n",
                i, (word & mask<<i) ? "ON" : "OFF");
}
sencillo, ¿no? Y lo mismo se puede hacer con una macro:
#define INPUT(w, i)    (w & 0x01<<i)

void main()
{
    // uso de una macro para leer los bit de una word
    unsigned char i_word = 74;    // 0 1 0 0 1 0 1 0

    // loop de lectura
    int i;
    for (i = 0; i < 8; i++)
        printf("el bit %d de la word es %s\n",
                i, INPUT(i_word, i) ? "ON" : "OFF");
}
Y luego, ya que esto es un blog de estilo, vemos una manera con una buena estética para leer lo input de un dispositivo, por ejemplo los finales de carrera de un sistema electromecánico que tenemos que controlar con nuestro querido C:
#define INPUT_FC1    (in_word & 0x01<<0)
#define INPUT_FC2    (in_word & 0x01<<1)

void main()
{
    // uso de una define para leer un bit en una word
    unsigned char in_word = 74;    // 0 1 0 0 1 0 1 0

    // lectura
    printf("el bit FC1 de la word es %s\n", INPUT_FC1 ? "ON" : "OFF");
    printf("el bit FC2 de la word es %s\n", INPUT_FC2 ? "ON" : "OFF");
}
Aquí, el ejemplo que acabamos de enseñar indica una manera, simple y elegante para describir los input (utilizando unos mnemónicos auto-explicativos) que pueden ser útiles para escribir el Software de control de dispositivos Hardware, fácil de leer y mantener.

Fácil de leer y mantener: ya he escuchado esto... ¡ah! sí: ​​es como debe ser todo el S/W que escribe un Analista Programador (y lástima que muchos analistas programadores con minúscula se olvidan de este imperativo cuando se sientan delante de un ordenador...).

¡Hasta el próximo post!