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.)
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:
- El ciclo
for no es usado para simplemente contar.
x>>=1 es equivalente a x = x >> 1.
- El ciclo
for iterativamente desplaza a la derecha x hasta que x se hace 0.
x & 01 enmascara el primer bit de x, y si este es 1 entonces incrementa count.
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:
- Solamente los n bits más bajos son asignados a un número de n bits. Por lo tanto, el campo
tipo no puede tomar valores mayores que 15, ya que es de 4 bits de largo.
- Los campos de bit son siempre convertidos a un tipo entero cuando se hace algún cálculo con ellos.
- Se permite mezclar los tipos ``normales'' con los campos de bit.
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:
- Los enteros podrían ser con o sin signo.
- Muchos compiladores limitan el número máximo de bits en el campo de bit al tamaño de un integer, el cual podría ser de 16 bits o de 32 bits.
- Algunos miembros campos de bit son guardados de izquierda a derecha, otros son guardados de derecha a izquierda en memoria.
- Si los campos de bits son muy largos, el siguiente campo de bit podría ser guardado consecutivamente en memoria (traslapando los límites entre las localidades de memoria) o en la siguiente palabra de memoria.
- Escribir una función que muestre un número de 8 bits (unsigned char) en formato binario.
- 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)
- 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)
- 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)
|