Operadores a nivel de bit - Bitwise operators
¿Qué son?:
Son operadores que no trabajan con valores "numéricos" en el sentido aritmético, sino bit por bit sobre la representación binaria de un dato entero.
Se aplican sobre:
- char
- short
- int
- long
- Sus versiones unsigned
NO APLICA a float, double, struct, etc.
Representación binaria:
Supongamos un unsigned char(8 bits):
- Valor decimal:
13 - Binario:
0000 1101
Cada operador va a actuar bit a bit, comparando o modificando esos 0 y 1.
Operador AND bit a bit ( & )
No confundir con el AND lógico (&&)
Sintaxis:
resultado = a & b;
Regla:
Un bit vale 1 sólo si ambos bits son 1.
| a | b | a & b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
Ejemplo:
unsigned char a = 12; // 0000 1100
unsigned char b = 10; // 0000 1010
unsigned char c = a & b; // 0000 1000 -> 8
¿Para qué se usa?:
- Enmascarar bits.
- Ver si un bit específico está activo.
Ejemplo típico:
if (estado & 0x01) {
// el bit 0 esta en 1
}
Operador OR bit a bit (|)
No confundir con el OR lógico (||)
Sintaxis:
resultado = a | b;
Regla:
Un bit vale 1 solo si al menos uno es 1.
| a | b | a | b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
Ejemplo:
unsigned char a = 12; // 0000 1100
unsigned char b = 10; // 0000 1010
unsigned char c = a | b; // 0000 1110 -> 24
¿Para qué se usa?:
- Prender bits (Setear flags).
Ejemplo típico:
estado = estado | 0x04; // pone en 1 el bit 2
Operador XOR bit a bit (^)
Este es el más "raro" al principio.
Sintaxis:
resultado = a ^ b;
Regla:
Un bit vale 1 solo si son distintos.
| a | b | a ^ b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Ejemplo:
unsigned char a = 12; // 0000 1100
unsigned char b = 10; // 0000 1010
unsigned char c = a ^ b; // 0000 0110 -> 6
¿Para qué se usa?:
- Invertir bits específicos.
- Truco histórico, hacer swap sin variable auxiliar, hoy en día solo didáctico.
Hoy en día este método no se recomienda: es menos legible y los compiladores modernos optimizan mejor el swap tradicional con variable auxiliar.
Ejemplo típico:
estado ^= 0x01; // invierte el bit 0
Swap sin variable auxiliar:
a = a ^ b;
b = a ^ b;
a = a ^ b;
Operador NOT o Complemento a uno (~)
Sintaxis:
resultado = ~a;
Regla:
Invierte todos los bits:
0 => 11 => 0
| a | ~a |
|---|---|
| 0 | 1 |
| 1 | 0 |
Ejemplo:
unsigned char a = 5; // 0000 0101
unsigned char b = ~a; // 1111 1010 -> 250
IMPORTANTE:
En tipos con signo, el resultado depende de la representación (en C usualmente Complemento a dos).
Por eso para bitwise - bit a bit conviene usar unsigned.
¿Para qué se usa?:
- Apagar bits con máscara.
Ejemplo típico:
estado &= ~0x04; // pone en 0 el bit 2
Desplazamiento a la izquierda - Shift left (<<)
Sintaxis:
resultado = a << n;
¿Qué hace?:
Desplaza los bits n posiciones a la izquierda, rellenando con ceros a la derecha.
Ejemplo:
unsigned char a = 3; // 0000 0011
unsigned char b = a << 2; // 0000 1100 => 12
Interpretación:
Equivale para unsigned a multiplicar por 2n.
a << 1 // Si a vale 1: 1 * 2^1 => 2
a << 2 // Si a vale 1: 1 * 2^2 => 4
Esta equivalencia es válida siempre que no se pierdan bits. Si el desplazamiento provoca overflow, el comportamiento puede no coincidir con la multiplicación/división aritmética.
Desplazamiento a la derecha - Shift right (>>)
Sintaxis:
resultado = a >> n;
¿Qué hace?:
Desplaza los bits n posiciones a la derecha, rellenando con ceros a la izquierda en tipos unsigned.
Ejemplo:
unsigned char a = 8; // 0000 1000
unsigned char b = a >> 2; // 0000 0010 => 2
Interpretación:
Equivale para unsigned a dividir por 2n.
a >> 1 // Si a vale 1: 1 / 2^1 => 0 (truncado)
a >> 2 // Si a vale 1: 1 / 2^2 => 0
En C, los operadores de desplazamiento trabajan sobre enteros. Cualquier división descarta la parte fraccionaria (truncamiento).
Shift right en tipos signed:
Dependiente de la implementación.
- Puede rellenar con ceros.
- O repetir el bit de signo (Shift aritmético). Por eso para bit a bit(bitwise) serio, se usa
unsigned.
Bitwise - bit a bit vs Lógicos:
| Operador | Tipo | Resultado |
|---|---|---|
| & | bitwise | Opera bit a bit |
| && | lógico | 0 o 1 |
| | | bitwise | Opera bit a bit |
| || | lógico | 0 o 1 |
| ^ | bitwise | Opera bit a bit |
| ! | lógico | Negación lógica |
| ˜ | bitwise | Complemento a 1 |
Resumen:
- Los operadores bitwise trabajan a nivel de bits.
- Son ideales para flags, máscaras y control fino.
- Para evitar sorpresas, se recomienda usar
unsigned.
Teoría aplicada
Makefile:
CC = gcc
CFLAGS = -std=c11 -Wall -Wextra -pedantic
PROG = bitwise
OBJ = main.o
$(PROG): $(OBJ)
$(CC) $(OBJ) -o $(PROG)
$(OBJ): main.c
$(CC) $(CFLAGS) -c main.c
.PHONY: clean
clean:
del $(OBJ) $(PROG).exe
Código:
// gcc -std=c11 -Wall -Wextra -pedantic main.c -o bitwise
#include <stdio.h>
/* Funcion auxiliar para mostrar unsigned char en binario */
void binprint(unsigned char x)
{
for (int i = 7; i >= 0; i--)
{
printf("%d", (x >> i) & 1);
if (i == 4)
printf(" ");
}
}
int main(void)
{
unsigned char a = 12; // 0000 1100
unsigned char b = 10; // 0000 1010
unsigned char r;
printf("Valores iniciales:\n");
printf("a = %3u = ", a); binprint(a); printf("\n");
printf("b = %3u = ", b); binprint(b); printf("\n\n");
/* ---- AND bitwise ---- */
r = a & b;
printf("AND bitwise (a & b):\n");
printf("%3u = ", r); binprint(r); printf("\n\n");
/* ---- OR bitwise ---- */
r = a | b;
printf("OR bitwise (a | b):\n");
printf("%3u = ", r); binprint(r); printf("\n\n");
/* ---- XOR bitwise ---- */
r = a ^ b;
printf("XOR bitwise (a ^ b):\n");
printf("%3u = ", r); binprint(r); printf("\n\n");
/* ---- NOT bitwise ---- */
r = ~a;
printf("NOT bitwise (~a):\n");
printf("%3u = ", r); binprint(r); printf("\n\n");
/* ---- SHIFT LEFT ---- */
r = a << 2;
printf("Shift left (a << 2):\n");
printf("%3u = ", r); binprint(r); printf("\n\n");
/* ---- SHIFT RIGHT ---- */
r = a >> 2;
printf("Shift right (a >> 2):\n");
printf("%3u = ", r); binprint(r); printf("\n\n");
return 0;
}
Ejercicios de Operadores a nivel de bit