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:
for
no es usado para simplemente contar.
x>>=1
es equivalente a x = x >> 1
.
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:
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:
tipo
no puede tomar valores mayores que 15, ya que es de 4 bits de largo.
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:
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)
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)
La salida podría ser como la siguiente:
x = 10100111 (binario) x rotada por 3 = 11110100 (binario)