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!

miércoles, 13 de noviembre de 2013

¡Bitwise operations!
cómo usar los operadores Bitwise en C - pt.1

Aunque éste es un blog de estilo (por si alguien todavía no se había dado cuenta...), he decidido, durante un tiempo, continuar con mi trabajo meritorio (¡que modestia!) de actualizar algunos temas que me imagino que todo el mundo ha leído y/o estudiado (tal vez en el K&R), y luego han rápidamente olvidado, porque seamos sinceros, el C contiene partes bastantes especializadas y de uso marginal, que en algunos entornos de programación no se utilizan nunca (¡nunca nunca!).

Repetimos el juego del post anterior: que levante la mano quien ha escrito recientemente código que utiliza las operaciones bit a bit. O bien, levante la mano, sin antes ir a volver a leer un manual de C, aquellos de vosotros que saben cómo usar y/o describir perfectamente las operaciones bit a bit. Uhmm ... Veo pocas manos levantadas. El hecho es que las bitwise opeartions son una de las partes del C menos conocida, de uso dudoso y poco frecuente, en definitiva, una de las partes que, por falta de práctica, se olvida (y he usado exactamente las mismas palabras del post anterior: soy ecologista, cuando puedo reciclo).

En comparación con las union, por otra parte, hay que decir que sobre las operaciones bit a bit el fantástico K&R no es particularmente claro y detallado, y las trata (como son) como un tema secundario, y no te involucra con decenas de ejemplos entretenidos que te ayudan en memorizar perfectamente el argumento, de hecho, que yo recuerde de la última vez que lo leí, hay solo un par de páginas que te se escapaban de las manos y que ya has olvidado cuando pasas al siguiente capítulo. Bueno, la biblia K&R (que considero sagrada) tiene algunos puntos no tan atractivos.

Refrescamos: los operadores de bitwise (que operan en los bits individuales) son:
"&"  AND
"|"  OR
"^"  XOR
"~"  NOT (complemento a 1)
"<<" SHIFT hacia la izquierda
">>" SHIFT hacia la derecha
  
NOTA: 
- el NOT es un operador unario: funciona con un solo argumento indicado 
  a la derecha. 
- los operadores de shift son operadores unarios: operan con un solo 
  argumento indicado a la izquierda.  
Ahora bien, sin entrar en discursos aburridos, nos dirigimos a una pequeña tabla y a algunos ejemplos sencillos.

Aquí está la tabla:
"& ":  el resultado es 1 si los dos operandos son 1. En caso contrario 0. 
"|" :  el resultado es 0 si los dos operandos son 0. En caso contrario 1. 
"^ ":  el resultado es 1 si los dos operandos son diferentes. En caso contrario 0. 
"~ ":  el resultado es 1 si el operando es 0. En caso contrario 0. 
"<<n": el resultado es el operando con todos los bits desplazados a la 
       izquierda por n posiciones. 
">>n": el resultado es el operando con todos los bits desplazados a la 
       derecha por n posiciones.
Y aquí están los simples ejemplos prácticos:
AND
int a = 74;       // 0 1 0 0 1 0 1 0
int b = 174;      // 1 0 1 0 1 1 1 0
int c = a & b;    // 0 0 0 0 1 0 1 0 resultado c=10

OR
int a = 74;       // 0 1 0 0 1 0 1 0
int b = 174;      // 1 0 1 0 1 1 1 0
int c = a | b;    // 1 1 1 0 1 1 1 0 resultado c=238

XOR
int a = 74;       // 0 1 0 0 1 0 1 0
int b = 174;      // 1 0 1 0 1 1 1 0
int c = a & b;    // 1 1 1 0 0 1 0 0 resultado c=228

NOT
int a = 74;       // 0 1 0 0 1 0 1 0
int b = ~b;       // 1 0 1 1 0 1 0 1 resultado c=181

SHIFT hacia la izquierda
int a = 74;       // 0 1 0 0 1 0 1 0
int b = a<<2;     // 0 0 1 0 1 0 0 0 resultado c=296

SHIFT hacia la derecha
int a = 74;       // 0 1 0 0 1 0 1 0
int b = a>>2;     // 0 0 0 1 0 0 1 0 resultado c=18
Tener en cuenta que en las operaciones de shift los bits nuevos que entran por la derecha (en el shift hacia la izquierda) valen 0, y los bit nuevos que entran por la izquierda (en el shift hacia la derecha) valen 0.

Tener en cuenta también que el shift hacia la derecha es equivalente a una división por múltiplos de 2 (>>1 es una división por 2, >>2 es una división por 4, etc.) , Mientras que el shift hacia la izquierda es equivalente a una multiplicación por múltiplos de 2 (<<1 es una multiplicación por 2, <<2 es una multiplicación por 4, etc.). Estas operaciones de multiplicación y división son muy rápidas, y se puede tener la tentación de utilizarlas para acelerar el código: bueno, antes de hacerlo, leer de nuevo (o leer) esto.

Y me gustaría añadir una advertencia: en función del tamaño del tipo del operando, y la presencia o ausencia del bit de signo, las multiplicaciones y divisiones con shift pueden dar resultados inesperados. Hablaremos de esto en el próxima episodio, donde vamos a hacer unos cuantos ejemplos prácticos de código que utiliza las bitwise operations (y no contengáis la respiración, mientras tanto...).

Hasta el próximo post.