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?

  1. Flags e configurações: Armazenar múltiplas opções em uma única variável
  2. Registradores de hardware: Configurar periféricos em embarcados
  3. Protocolos: Empacotar/desempacotar dados binários
  4. 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
Progresso do Tópico