Subsecciones

11. Operadores de bajo nivel y campos de bit

Se ha visto como los apuntadores nos dan control sobre las operaciones de bajo nivel de la memoria.

Muchos programas (por ejemplo, aplicaciones del tipo sistemas) operan actualmente a bajo nivel donde bits individuales deben ser manipulados.

La combinación de apuntadores y operadores a nivel bit hacen de C útil para muchas aplicaciones de bajo nivel y pueden casi reemplazar al código ensamblador. (Solamente un 10% aproximadamente de UNIX esta en código ensamblador el resto es C.)

11.1 Operadores sobre bits

Los operadores sobre bits de C se resumen en la siguiente tabla:

Operador Acción
& Y
| O
^ O exclusiva (XOR)
~ Complemento a uno
- Negación
<< Desplazamiento a la izquierda
>> Desplazamiento a la derecha

No se debe confundir el operador & con el operador &&: & es el operador Y sobre bits, && es el operador lógico Y. Similarmente los operadores | y ||.

El operador unario ~ sólo requiere un argumento a la derecha del operador.

Los operadores de desplazamiento, >> y <<, mueven todos los bits en una posición hacia la derecha o la izquierda un determinado número de posiciones. El formato general de la sentencia de desplazamiento a la derecha es:

variable >> num_pos_de_bit

y el formato general de desplazamiento a la izquierda es

variable << num_pos_de_bit

Como los operadores desplazan bits en un sentido, la computadora trae ceros en el otro extremo. Se debe recordar que un desplazamiento no es una rotación: los bits desplazados en un extremo no vuelven al otro. Se pierden y los ceros traídos los reemplazan.

Una aplicación que tienen los operadores de desplazamiento de bits es para realizar multiplicaciones y divisiones rápidas con enteros. Como se ve en la siguiente tabla, donde un desplazamiento a la izquierda es multiplicar por 2 y uno a la derecha dividir por 2.

char x Ejecución Valor de x
x = 7; 0 0 0 0 0 1 1 1 7
x << 1; 0 0 0 0 1 1 1 0 14
x << 3; 0 1 1 1 0 0 0 0 112
x << 2; 1 1 0 0 0 0 0 0 192
x >> 1; 0 1 1 0 0 0 0 0 96
x >> 2; 0 0 0 1 1 0 0 0 24

Los desplazamientos son mucho más rápidos que la multiplicación actual (*) o la división (/) por dos. Por lo tanto, si se quieren multiplicaciones o divisiones rápidas por 2 use desplazamientos.

Con la finalidad de ilustrar algunos puntos de los operadores sobre bits, se muestra la siguiente función contbit, la cual cuenta los bits puestos a 1 en un número de 8 bits (unsigned char) pasado como un argumento a la función.

int contbit(unsigned char x)
{
	int count;
	for (count=0; x!=0; x>>=1)
		if ( x & 1)
			count++;
	return count;
}

En esta función se ilustran varias características de C:

11.2 Campos de bit

Al contrario de la mayoría de los lenguajes de programación, C tiene un método predefinido para acceder a un único bit en un byte. Este método puede ser utilísimo por una serie de razones:

-
Si el almacenamiento es limitado, se pueden almacenar varias variables booleanas en un byte;
-
ciertas interfaces de dispositivo transmiten información que se codifica en bits dentro de un byte;
-
ciertas rutinas de encriptación necesitan acceder a los bits en un byte.

El método que C usa para acceder a los bits se basa en la estructura. Un campo de bit es un tipo especial de estructura que define la longitud en bits que tendrá cada elemento. El formato general de una definición de campo de bit es:

struct nomb_estruct {
tipo nombre_1 : longitud;
tipo nombre_2 : longitud;
.
.
.
tipo nombre_n : longitud;
}

Se debe declarar un campo de bit como int, unsigned o signed. Se debe declarar los campos de bits de longitud 1 como unsigned, ya que un bit único no puede tener signo.

Por ejemplo, considerar esta definición de estructura:

struct empaquetado {
    unsigned int b1:1;
    unsigned int b2:1;
    unsigned int b3:1;
    unsigned int b4:1;
    unsigned int tipo:4;
    unsigned int ent_raro:9;
} paquete;

La estructura empaquetado contiene 6 miembros: 4 banderas de 1 bit (b1, b2, b3 y b4), uno de 4 bits (tipo) y otro de 9 bits (ent_raro).

C automáticamente empaca los campos de bit anteriores tan compactamente como sea posible, donde la longitud máxima del campo es menor que o igual a la longitud de la palabra entera de la computadora. Si no fuera el caso, entonces algunos compiladores podrían permitir traslape en memoria para los campos, mientras otros podrían guardar el siguiente campo en la siguiente palabra.

La forma de accesar los miembros es en la forma usual:

paquete.tipo = 7;

Con estructura anterior se tiene que:

11.2.1 Portabilidad

Los campos de bit son una forma conveniente de expresar muchas operaciones dificiles. Sin embargo, los campos de bit carecen de portabilidad entre plataformas, por alguna de las siguientes razones:

11.3 Ejercicios

  1. Escribir una función que muestre un número de 8 bits (unsigned char) en formato binario.

  2. Escribir una función ponerbits(x,p,n,y) que regrese x, con n bits que empiezan en la posición p y estan a la derecha de una variable y unsigned char, dejándolos en x en la posición p y a la izquierda dejando los otros bits sin cambio.

    Por ejemplo, si x = 10101010 (170 decimal), y = 10100111 (167 decimal), n = 3 y p = 6, entonces se necesita tomar 3 bits de de y (111) y ponerlos en x en la posición 10xxx010 para obtener la respuesta 10111010.

    La respuesta deberá mostrarse en forma binaria (ver primer ejercicio), sin embargo la entrada puede ser en forma decimal.

    La salida podría ser como la siguiente:

    x = 10101010 (binario)
    y = 10100111 (binario)
    ponerbits n = 3, p = 6 da x = 10111010 (binario)
    

  3. Escribir una función que invierta los bits de x (unsigned char) y guarde la respuesta en y.

    La respuesta deberá mostrarse en forma binaria (ver primer ejercicio), sin embargo la entrada puede estar en forma decimal.

    La salida podría ser como la siguiente:

    x = 10101010 (binario)
    x invertida = 01010101 (binario)
    

  4. Escribir una función que rote (no desplaze) a la derecha n posiciones de bits de x del tipo unsigned char. La respuesta deberá mostrarse en forma binaria (ver primer ejercicio) y la entrada puede ser en forma decimal.

    La salida podría ser como la siguiente:

    x = 10100111 (binario)
    x rotada por 3 = 11110100 (binario)