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

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!