Operadores bit a bit
Esta aula aborda os operadores bit a bit em C: AND, OR, XOR, NOT, deslocamento à esquerda e à direita, além do conceito de máscaras e aplicações práticas como otimização de desempenho e manipulação de flags.
Os operadores bit a bit (bitwise) atuam diretamente sobre os bits de inteiros, permitindo manipulações de baixo nível essenciais para programação de sistemas, drivers, criptografia e otimização. Diferentemente dos operadores lógicos (&&, ||, !), que tratam valores booleanos, os operadores bit a bit operam bit a bit, sendo fundamentais para controle de hardware, compactação de dados e algoritmos eficientes.
Nesta aula, exploraremos os operadores AND (&), OR (|), XOR (^), NOT (~), os deslocamentos (<< e >>), o uso de máscaras e casos práticos que demonstram sua utilidade no dia a dia do programador C.
AND, OR, XOR, NOT
Os operadores bit a bit básicos são: AND (&), OR (|), XOR (^) e NOT (~). Eles operam em cada bit dos operandos de forma independente. O operador AND retorna 1 apenas quando ambos os bits são 1; OR retorna 1 se pelo menos um bit for 1; XOR retorna 1 se os bits forem diferentes; NOT inverte todos os bits do operando.
É importante notar que esses operadores funcionam com tipos inteiros (char, short, int, long, etc.) e seus modificadores unsigned. Para tipos com sinal, o comportamento com bits de sinal pode ser problemático; por isso, recomenda-se usar tipos unsigned para operações bit a bit.
#include <stdio.h>
int main() {
unsigned char a = 0b1100; // 12
unsigned char b = 0b1010; // 10
printf("a & b = %d\n", a & b); // 0b1000 = 8
printf("a | b = %d\n", a | b); // 0b1110 = 14
printf("a ^ b = %d\n", a ^ b); // 0b0110 = 6
printf("~a = %d\n", (unsigned char)~a); // 0b0011 = 3 (em 8 bits)
return 0;
}Deslocamento (<< >>)
Os operadores de deslocamento deslocam os bits para a esquerda (<<) ou para a direita (>>). O deslocamento à esquerda preenche com zeros à direita; cada deslocamento à esquerda equivale a multiplicar por 2. O deslocamento à direita para tipos unsigned preenche com zeros (shift lógico); para tipos signed, o comportamento é dependente de implementação (geralmente shift aritmético, preservando o sinal).
Deslocamentos por um número de bits maior ou igual ao tamanho do tipo resultam em comportamento indefinido. Portanto, sempre verifique os limites.
#include <stdio.h>
int main() {
unsigned char x = 0b00010010; // 18
printf("x << 2 = %d\n", x << 2); // 0b01001000 = 72 (18*4)
printf("x >> 1 = %d\n", x >> 1); // 0b00001001 = 9 (18/2)
// Cuidado com estouro
unsigned char y = 0b10000000; // 128
printf("y << 1 = %d\n", (unsigned char)(y << 1)); // 0 (estourou para 8 bits)
return 0;
}Máscaras
Máscaras são valores usados em conjunto com operadores bit a bit para selecionar, limpar, setar ou testar bits específicos. Uma máscara tipicamente tem 1s nas posições de interesse e 0s nas demais. Por exemplo, para testar se o bit 3 (valor 8) de um número está ativo, usamos a máscara 0b00001000 (8) com AND.
As operações comuns são: testar bits (valor & mascara), setar bits (valor | mascara), limpar bits (valor & ~mascara) e alternar bits (valor ^ mascara). O uso de constantes hexadecimais ou binárias (C2x) facilita a leitura.
#include <stdio.h>
int main() {
unsigned char flags = 0b00101100;
unsigned char mask_bit2 = 0b00000100; // bit 2
unsigned char mask_bit5 = 0b00100000; // bit 5
// Testar se bit 2 está ativo
if (flags & mask_bit2)
printf("Bit 2 está ativo\n");
// Setar bit 5
flags |= mask_bit5;
printf("flags após setar bit5: 0x%02X\n", flags);
// Limpar bit 2
flags &= ~mask_bit2;
printf("flags após limpar bit2: 0x%02X\n", flags);
// Alternar bit 0
flags ^= 0b00000001;
printf("flags após alternar bit0: 0x%02X\n", flags);
return 0;
}Casos de uso
Operações bit a bit são amplamente usadas em: (1) flags de status e configuração, onde cada bit representa uma opção; (2) manipulação de hardware (registradores de dispositivos); (3) algoritmos de criptografia e hashing; (4) compactação de dados; (5) jogos e gráficos (máscaras de colisão, cores); (6) otimização de multiplicação/divisão por potências de 2.
Um exemplo clássico é o uso de flags em sistemas operacionais: permissões de arquivo no Unix usam bits para leitura, escrita e execução. Outro caso é a implementação de um conjunto de bits (bitset) para representar um conjunto de inteiros de forma eficiente.
#include <stdio.h>
#include <stdbool.h>
// Exemplo: flags de permissão
#define PERM_READ 0b100
#define PERM_WRITE 0b010
#define PERM_EXEC 0b001
void print_permissions(unsigned char perm) {
printf("Permissões: ");
printf("%s", (perm & PERM_READ) ? "r" : "-");
printf("%s", (perm & PERM_WRITE) ? "w" : "-");
printf("%s\n", (perm & PERM_EXEC) ? "x" : "-");
}
int main() {
unsigned char perm = PERM_READ | PERM_EXEC; // 101
print_permissions(perm);
// Adicionar permissão de escrita
perm |= PERM_WRITE;
print_permissions(perm);
// Remover permissão de execução
perm &= ~PERM_EXEC;
print_permissions(perm);
return 0;
}Boas práticas
Sempre use tipos unsigned para operações bit a bit, a menos que tenha um motivo específico. Prefira constantes nomeadas (defines ou enums) para máscaras, em vez de números mágicos. Evite deslocamentos com valores negativos ou maiores que a largura do tipo. Lembre-se de que operações bit a bit têm precedência menor que operadores relacionais; use parênteses para garantir a ordem desejada.
Referências
- cppreference: Operadores aritméticos (inclui bitwise)
- GNU C Manual: Bit Shifting
- Wikipedia: Bitwise operation
- Microsoft C: Bitwise operators
- Stack Overflow: Bitwise operation and usage
Exercícios
- Escreva uma função em C que receba um unsigned char e retorne o número de bits 1 (popcount) usando operações bit a bit.
- Dado um unsigned char x, use uma máscara para extrair os 4 bits mais significativos (nibble alto) e os 4 bits menos significativos (nibble baixo).
- Implemente uma função que verifique se um número é potência de 2 usando operadores bit a bit.
- Escreva um programa que leia um inteiro e troque os nibbles (4 bits) de um unsigned char.
- Crie uma função que use máscaras para setar, limpar e alternar um bit específico (dado por posição 0-7) de um unsigned char.
int popcount(unsigned char x) {
int count = 0;
while (x) {
count += x & 1;
x >>= 1;
}
return count;
}unsigned char x = 0xAB; // 171
unsigned char nibble_alto = (x >> 4) & 0x0F;
unsigned char nibble_baixo = x & 0x0F;
printf("Alto: %X, Baixo: %X\n", nibble_alto, nibble_baixo);int is_power_of_two(unsigned int n) {
return n && !(n & (n - 1));
}#include <stdio.h>
int main() {
unsigned char val = 0xAB; // exemplo
unsigned char trocado = (val << 4) | (val >> 4);
printf("Original: 0x%X, Trocado: 0x%X\n", val, trocado);
return 0;
}void set_bit(unsigned char *val, int pos) {
*val |= (1 << pos);
}
void clear_bit(unsigned char *val, int pos) {
*val &= ~(1 << pos);
}
void toggle_bit(unsigned char *val, int pos) {
*val ^= (1 << pos);
}