Operações Bitwise
Manipulando bits diretamente — o nível mais baixo que você pode chegar em C.
Operações bitwise manipulam dados no nível de bits individuais. Enquanto operadores aritméticos (+, -) trabalham com números como valores, operadores bitwise trabalham com a representação binária desses números.
Por que você usaria isso?
- Flags e configurações: Armazenar múltiplas opções em uma única variável
- Registradores de hardware: Configurar periféricos em embarcados
- Protocolos: Empacotar/desempacotar dados binários
- Otimização: Certas operações são mais rápidas com bits
Os Operadores
AND (&)
Retorna 1 apenas onde ambos os bits são 1:
0101 (5)
& 0011 (3)
------
0001 (1)
int result = 5 & 3; // 1
Uso típico: Verificar se um bit está ligado (máscara).
OR (|)
Retorna 1 onde qualquer bit é 1:
0101 (5)
| 0011 (3)
------
0111 (7)
int result = 5 | 3; // 7
Uso típico: Ligar bits (combinar flags).
XOR (^)
Retorna 1 onde os bits são diferentes:
0101 (5)
^ 0011 (3)
------
0110 (6)
int result = 5 ^ 3; // 6
Uso típico: Alternar bits (toggle), criptografia simples.
NOT (~)
Inverte todos os bits:
~ 0101
------
1010 (considerando apenas 4 bits)
uint8_t x = 5;
uint8_t result = ~x; // 250 (11111010 em 8 bits)
Uso típico: Criar máscaras de desligamento.
Left Shift (<<)
Desloca bits para a esquerda, preenchendo com zeros:
0001 (1)
<< 3
------
1000 (8)
int result = 1 << 3; // 8 (equivale a 1 * 2³)
Cada shift para esquerda multiplica por 2.
Right Shift (>>)
Desloca bits para a direita:
1000 (8)
>> 2
------
0010 (2)
int result = 8 >> 2; // 2 (equivale a 8 / 2²)
Cada shift para direita divide por 2 (truncando).
Atenção: Para signed ints, o comportamento do bit de sinal pode variar. Use unsigned para evitar surpresas.
Padrões Práticos
1. Verificar se um Bit Está Ligado
#define BIT_3 (1 << 3) // 0000 1000
uint8_t flags = 0b00001010;
if (flags & BIT_3) {
printf("Bit 3 está ligado\n");
} else {
printf("Bit 3 está desligado\n"); // Este é o caso
}
2. Ligar um Bit
flags = flags | BIT_3;
// Forma resumida:
flags |= BIT_3;
// flags agora é 0b00011010
3. Desligar um Bit
flags = flags & ~BIT_3;
// Forma resumida:
flags &= ~BIT_3;
// ~BIT_3 = 11110111, faz AND para desligar apenas o bit 3
4. Alternar um Bit (Toggle)
flags = flags ^ BIT_3;
// Forma resumida:
flags ^= BIT_3;
// Se estava ligado, desliga. Se estava desligado, liga.
5. Extrair um Campo de Bits
// Extrair bits 4-7 de um byte
uint8_t valor = 0b11010110;
uint8_t campo = (valor >> 4) & 0x0F; // resultado: 0b1101 = 13
6. Inserir um Campo de Bits
// Inserir valor 5 nos bits 4-7
uint8_t resultado = 0;
resultado |= (5 << 4); // resultado: 0b01010000
Aplicação: Registradores de Hardware
Em sistemas embarcados, periféricos são controlados por registradores mapeados em memória. Cada bit tem uma função específica.
// Endereços fictícios de registradores
volatile uint32_t *GPIO_ENABLE = (volatile uint32_t *)0x40020000;
volatile uint32_t *GPIO_OUTPUT = (volatile uint32_t *)0x40020004;
#define PIN_LED (1 << 5) // Bit 5
#define PIN_BUZZER (1 << 6) // Bit 6
void setup_gpio() {
// Habilitar pinos 5 e 6 como saída
*GPIO_ENABLE |= PIN_LED | PIN_BUZZER;
}
void led_on() {
*GPIO_OUTPUT |= PIN_LED; // Liga LED
}
void led_off() {
*GPIO_OUTPUT &= ~PIN_LED; // Desliga LED
}
void led_toggle() {
*GPIO_OUTPUT ^= PIN_LED; // Alterna LED
}
A palavra-chave volatile informa ao compilador que o valor pode mudar externamente (pelo hardware) — não otimize leituras.
Aplicação: Flags de Configuração
#include <stdint.h>
// Definir flags como potências de 2
#define OPT_VERBOSE (1 << 0) // 0001
#define OPT_DEBUG (1 << 1) // 0010
#define OPT_FORCE (1 << 2) // 0100
#define OPT_RECURSIVE (1 << 3) // 1000
void processar(uint8_t opcoes) {
if (opcoes & OPT_VERBOSE) {
printf("Modo verbose ativado\n");
}
if (opcoes & OPT_DEBUG) {
printf("Debug ativado\n");
}
// ...
}
int main() {
// Combinar múltiplas opções
processar(OPT_VERBOSE | OPT_DEBUG);
return 0;
}
Uma única variável de 8 bits armazena 8 flags booleanas independentes.
Cuidados
Shifts com Tipos Signed
int x = -1;
x >> 1; // Resultado é implementation-defined!
Use unsigned para operações de shift previsíveis.
Shift Além do Tamanho do Tipo
uint8_t x = 1;
x << 10; // Undefined behavior! uint8_t tem só 8 bits
Precedência de Operadores
Bitwise têm precedência menor que comparação:
// ERRADO: compara primeiro, depois faz AND
if (flags & BIT_3 == BIT_3) // Lido como: flags & (BIT_3 == BIT_3)
// CORRETO: parênteses explícitos
if ((flags & BIT_3) == BIT_3)
Use parênteses sempre que misturar bitwise com outros operadores.
Referências:
- Kernighan, B. W., & Ritchie, D. M. (1988). The C Programming Language, Chapter 2.9
- Warren, H. S. (2012). Hacker’s Delight, 2nd Edition