Operações Bit a Bit

Operações bit a bit são como controlar uma fileira de interruptores de luz — cada luz corresponde a um bit, e você pode ligar, desligar ou inverter qualquer luz individual sem afetar as outras.

Revisão de Binário

Os computadores representam todos os dados internamente usando binário. Um byte = 8 bits, cada bit é 0 ou 1.

Decimal 42  =  Binário 00101010
Decimal 255 =  Binário 11111111

Inteiros com sinal usam representação em complemento de dois: o bit mais alto é o bit de sinal, 0 para positivo, 1 para negativo.

Decimal  5  =  00000101
Decimal -5  =  11111011  (complemento de dois)

Seis Operadores Bit a Bit

Operador Nome Regra
& AND bit a bit Resultado é 1 apenas quando ambos os bits são 1
| OR bit a bit Resultado é 1 quando pelo menos um bit é 1
^ XOR bit a bit Resultado é 1 quando os dois bits são diferentes
~ NOT bit a bit 0 vira 1, 1 vira 0
<< Deslocamento à esquerda Desloca bits à esquerda, preenche bits baixos com 0
>> Deslocamento à direita Desloca bits à direita, preenche bits altos com bit de sinal (com sinal) ou 0 (sem sinal)

AND Bit a Bit (&)

  1010  (10)
& 1100  (12)
------
  1000  (8)

Usos: limpar certos bits, verificar se certos bits são 1.

OR Bit a Bit (|)

  1010  (10)
| 1100  (12)
------
  1110  (14)

Usos: definir certos bits como 1.

XOR Bit a Bit (^)

  1010  (10)
^ 1100  (12)
------
  0110  (6)

Propriedades: a ^ a = 0, a ^ 0 = a. XOR é reversível — aplicar XOR duas vezes com o mesmo valor restaura o original.

NOT Bit a Bit (~)

~ 00101010  (42)
= 11010101  (-43, complemento de dois)

Para números sem sinal: ~0 = 255 (8 bits), ~0 = 0xFFFFFFFF (32 bits).

Deslocamento à Esquerda (<<)

5 << 2
= 00000101 << 2
= 00010100
= 20

Deslocar à esquerda por n é equivalente a multiplicar por 2^n. 5 << 2 = 5 * 4 = 20.

Deslocamento à Direita (>>)

20 >> 2
= 00010100 >> 2
= 00000101
= 5

Deslocar à direita por n é equivalente a dividir por 2^n (arredondado para zero).

⚠️ Atenção: Deslocar à direita números negativos com sinal preenche os bits altos com 1 (deslocamento aritmético), o que pode produzir resultados inesperados. Recomenda-se deslocar apenas valores sem sinal.

Quatro Operações Bit Essenciais

Definir um Bit (como 1)

C
flags |= (1 << n);

Define o enésimo bit como 1, deixando todos os outros bits inalterados.

Limpar um Bit (como 0)

C
flags &= ~(1 << n);

~(1 << n) produz uma máscara que é toda 1s exceto por um 0 na posição n. A operação AND limpa apenas aquele bit.

Inverter um Bit

C
flags ^= (1 << n);

Operação XOR: 0^1=1, 1^1=0, o que implementa perfeitamente a inversão.

Verificar um Bit

C
if (flags & (1 << n)) {
}

Se o enésimo bit for 1, o resultado é não zero; se for 0, o resultado é zero.

Exemplo

C
#include <stdio.h>

void print_bits(unsigned char val) {
    for (int i = 7; i >= 0; i--) {
        printf("%d", (val >> i) & 1);
    }
    printf("\n");
}

int main(void) {
    unsigned char flags = 0;

    flags |= (1 << 3);
    printf("Definir bit 3: ");
    print_bits(flags);

    flags |= (1 << 5);
    printf("Definir bit 5: ");
    print_bits(flags);

    flags &= ~(1 << 3);
    printf("Limpar bit 3: ");
    print_bits(flags);

    flags ^= (1 << 5);
    printf("Inverter bit 5: ");
    print_bits(flags);

    flags ^= (1 << 7);
    printf("Inverter bit 7: ");
    print_bits(flags);

    if (flags & (1 << 7)) {
        printf("Bit 7 é 1\n");
    }

    return 0;
}
▶ Experimente
TEXT
Definir bit 3: 00001000
Definir bit 5: 00101000
Limpar bit 3: 00100000
Inverter bit 5: 00000000
Inverter bit 7: 10000000
Bit 7 é 1

Técnicas de Máscara

Uma máscara é uma combinação predefinida de bits usada para extrair ou modificar campos de bits específicos dentro de dados.

Extrair Bits Baixos

C
unsigned int val = 0xABCD;
unsigned int low_byte = val & 0xFF;

0xFF é uma máscara que mantém apenas os 8 bits mais baixos.

Extrair Bits Altos

C
unsigned int high_byte = (val >> 8) & 0xFF;

Primeiro desloca à direita por 8 bits, depois aplica máscara para obter os 8 bits baixos.

Combinar Valores

C
unsigned int combined = (high << 8) | low;

Junta dois bytes em um valor de 16 bits.

Exemplo

Extração de valor de cor RGB. Um valor de cor de 24 bits tem 8 bits cada para vermelho, verde e azul:

C
#include <stdio.h>

int main(void) {
    unsigned int color = 0xFF6633;

    unsigned char r = (color >> 16) & 0xFF;
    unsigned char g = (color >> 8) & 0xFF;
    unsigned char b = color & 0xFF;

    printf("Cor #FF6633:\n");
    printf("  Vermelho: %d\n", r);
    printf("  Verde: %d\n", g);
    printf("  Azul: %d\n", b);

    unsigned int new_color = 0x00;
    new_color |= ((r / 2) << 16);
    new_color |= ((g / 2) << 8);
    new_color |= (b / 2);
    printf("Escurecida: #%06X\n", new_color);

    return 0;
}
▶ Experimente
TEXT
Cor #FF6633:
  Vermelho: 255
  Verde: 102
  Azul: 51
Escurecida: #7F3319
💡 Dica: Extração de cor RGB é uma aplicação clássica de operações bit a bit. No desenvolvimento web, o formato #RRGGBB é essencialmente um inteiro de 24 bits — vermelho nos bits 16-23, verde nos bits 8-15, azul nos bits 0-7.

Flags de Permissão

As permissões de arquivo do Linux são uma aplicação clássica de operações bit a bit. 9 bits de permissão representam dono/grupo/outros para leitura/escrita/execução:

rwxr-xr-x = 111101101 = 0755
rw-r--r-- = 110100100 = 0644
C
#include <stdio.h>

#define READ    (1 << 2)
#define WRITE   (1 << 1)
#define EXECUTE (1 << 0)

void show_permission(unsigned char perm) {
    printf("%c", (perm & READ) ? 'r' : '-');
    printf("%c", (perm & WRITE) ? 'w' : '-');
    printf("%c", (perm & EXECUTE) ? 'x' : '-');
}

int main(void) {
    unsigned char owner  = READ | WRITE | EXECUTE;
    unsigned char group  = READ | EXECUTE;
    unsigned char other  = READ | EXECUTE;

    printf("Permissões: ");
    show_permission(owner);
    show_permission(group);
    show_permission(other);
    printf("\n");

    owner &= ~WRITE;
    printf("Após remover escrita: ");
    show_permission(owner);
    show_permission(group);
    show_permission(other);
    printf("\n");

    return 0;
}
TEXT
Permissões: rwxr-xr-x
Após remover escrita: r-xr-xr-x

Truques Práticos com Bits

Trocar Duas Variáveis (Sem Temporária)

C
a ^= b;
b ^= a;
a ^= b;

Princípio: a propriedade auto-inversa do XOR. Porém, isso prejudica a legibilidade e não é recomendado no desenvolvimento real.

Verificar Par ou Ímpar

C
if (n & 1) {
}

Se o bit mais baixo for 1, o número é ímpar. Isso é mais rápido que n % 2.

Multiplicar/Dividir por Potências de 2

C
n << 1
n << 2
n >> 1

Compiladores tipicamente otimizam n * 2 em um deslocamento, mas deslocamentos são seguros apenas para inteiros positivos.

Calcular Potências de 2

C
unsigned int pow2 = 1u << n;

1u << 0 = 1, 1u << 1 = 2, 1u << 8 = 256... Deslocamento é muito mais rápido que pow(2, n).

Alinhar a uma Potência de 2

C
unsigned int aligned = (value + mask) & ~mask;

Por exemplo, alinhar a um limite de 4 bytes: (n + 3) & ~3.

C
#include <stdio.h>

int main(void) {
    int values[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    for (int i = 0; i < 10; i++) {
        int aligned = (values[i] + 3) & ~3;
        printf("%d -> %d\n", values[i], aligned);
    }
    return 0;
}
TEXT
0 -> 0
1 -> 4
2 -> 4
3 -> 4
4 -> 4
5 -> 8
6 -> 8
7 -> 8
8 -> 8
9 -> 12

❓ Perguntas Frequentes

P: Por que operações de deslocamento são mais rápidas que multiplicação e divisão? R: Deslocamentos são instruções de CPU de ciclo único, enquanto multiplicação e divisão requerem múltiplos ciclos de clock. Porém, compiladores modernos otimizam automaticamente multiplicações por potências de 2 em deslocamentos, então não há necessidade de escrevê-las manualmente.

P: O que acontece se você deslocar além do número de bits? *R: Comportamento indefinido! Deslocar um int de 32 bits por 32 ou mais bits não é permitido. Você deve garantir que a contagem de deslocamento esteja entre 0 e sizeof(tipo)8-1.

P: Operações bit a bit podem ser usadas em números de ponto flutuante? R: Não. Operadores bit a bit funcionam apenas em tipos inteiros. Para manipular os bits de um número de ponto flutuante, você precisa copiar seus bytes para um inteiro usando memcpy.

P: O que há de errado com a troca XOR? R: Se a e b apontam para a mesma memória (por exemplo, a mesma variável), a troca XOR zerará o valor. Além disso, é difícil de ler, e o compilador otimiza uma troca normal para ser tão eficiente quanto.

📖 Resumo

📝 Exercícios

  1. Escreva uma função que retorne o número de bits 1 na representação binária de um inteiro (usando operações bit a bit, não dividindo por 2 em um laço)
  2. Escreva um programa que use operações bit a bit para ajustar o brilho de uma cor RGB (multiplique cada canal por um coeficiente e recombine)
  3. Escreva uma função que extraia os bits m até n de um inteiro de 32 bits (m < n, contando a partir do bit 0)
100%