C Operadores de Bits

Para o computador, todo número é uma fileira de bits, zeros e uns. Os operadores que você já conhece (+, *, <) trabalham com o número inteiro. Os operadores de bits trabalham com cada bit separado: comparam bit a bit, invertem, empurram a fileira para um lado. O C, que nasceu para escrever sistemas operacionais, tem seis deles: &, |, ^, ~, << e >>. Eles são a base das permissões de arquivos, das cores, da compressão, dos protocolos de rede e dos jogos antigos.

Você vai aprender
  • Ler um número em binário e escrever um em C: 0b1101 C23.
  • Escrever uma função que mostra os bits de um número, e o %b do C23 (que funciona no gcc, mas não no editor).
  • Os operadores & (E), | (OU), ^ (OU exclusivo) e ~ (NÃO), bit a bit.
  • Os deslocamentos << e >>, e por que eles multiplicam e dividem por 2.
  • As armadilhas: ~ num uint8_t, x & 1 == 0, & no lugar de &&.
  • Os deslocamentos proibidos (negativos ou maiores que a largura), que são comportamento indefinido.

Os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com gcc -std=c23 -Wall -Wextra arquivo.c -o programa, e conferidos no editor do guia. A próxima aula, Máscaras e Flags, usa tudo isto para ligar, desligar e testar bits um por um.


A ideia: uma fileira de lâmpadas

Imagine 8 lâmpadas numa fileira, cada uma acesa (1) ou apagada (0). A da direita vale 1, a seguinte vale 2, depois 4, 8, 16… cada uma vale o dobro da vizinha da direita. O número é a soma das lâmpadas acesas:

Posição do bit76543210
Vale1286432168421
13 em binário00001101

13 = 8 + 4 + 1, então as lâmpadas 3, 2 e 0 estão acesas: 00001101. Os bits são numerados da direita para a esquerda, começando do 0. O bit 0 é o "menos significativo" (vale menos); o bit 7, o "mais significativo". (A aula Memória, Bits e Bytes conta essa história com mais calma.)

Em C, você pode escrever o mesmo número de três jeitos:

EscritaBaseObservação
13decimal (10)O jeito de sempre.
0x0Dhexadecimal (16)Cada dígito hexa são exatamente 4 bits: 0 = 0000, D = 1101. Por isso programadores adoram hexa para bits.
0b1101binário (2)C23 Oficial no C23; o gcc e o Clang já aceitavam antes, como extensão.
0b1010'0101binário com separadorC23 O apóstrofo só separa os dígitos para você ler melhor; o compilador ignora.

1. Ver os bits: a função mostra_bits

O printf tradicional não tem uma letra para binário. A solução clássica é escrever uma função que mostra os bits um por um. Ela usa dois operadores desta aula (>> e &); por enquanto, confie nela. Na seção 6 você vai entender cada pedaço, e na missão final vai escrevê-la sozinho.

#include <stdio.h>

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

int main(void) {
    mostra_bits(13);
    mostra_bits(0b1101);
    mostra_bits(0x0D);
    mostra_bits(255);
    mostra_bits(0b1010'0101);
    return 0;
}
unsigned char x
Um byte sem sinal: 8 bits, de 0 a 255. (Um uint8_t da aula Inteiros de Tamanho Fixo serviria igual.)
for (int i = 7; i >= 0; i--)
Visita os bits do 7 até o 0, da esquerda para a direita, que é a ordem em que lemos.
(x >> i) & 1
"Pegue o bit número i de x": dá 0 ou 1. Explicado na seção 6.
$ gcc -std=c23 -Wall -Wextra binario.c -o binario
$ ./binario
00001101
00001101                 « 0b1101: o mesmo número
00001101                 « 0x0D: o mesmo número
11111111
10100101

As três primeiras linhas são iguais: 13, 0b1101 e 0x0D são o mesmo número, escrito em bases diferentes. A base é só um jeito de escrever; dentro do computador é sempre binário.

Experimente: mostre os bits de 1, 2, 4, 8 e 128. Que padrão você vê? Depois tente 100 e confira: 100 = 64 + 32 + 4.

#include <stdio.h>

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

int main(void) {
    mostra_bits(13);
    mostra_bits(255);
    return 0;
}

O %b do C23

O C23 finalmente criou uma letra para binário no printf: %b C23. Ela aceita as mesmas opções das outras letras: largura com zeros (%08b) e o prefixo 0b com # (%#b):

#include <stdio.h>

int main(void) {
    printf("13 em binário: %b\n", 13);
    printf("Com 8 dígitos: %08b\n", 13);
    printf("Com prefixo:   %#b\n", 13);
    printf("Fim\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra pctb.c -o pctb
$ ./pctb
13 em binário: 1101
Com 8 dígitos: 00001101
Com prefixo:   0b1101
Fim

Funciona no laboratório: quem faz o trabalho do printf é a biblioteca (a glibc), não o compilador, e a glibc já aprendeu o %b. A biblioteca do editor ainda não. Lá, o mesmo programa compila sem aviso, mas as três primeiras linhas somem: o printf desiste quando encontra uma letra que não conhece, e só o Fim aparece. Por isso este guia usa a função mostra_bits, que funciona em qualquer lugar.


2. &: E, bit a bit

O & (lê-se "E") compara os dois números coluna por coluna. Em cada coluna, o resultado é 1 só se os dois bits forem 1:

bit de abit de ba & ba | ba ^ b
00000
01011
10011
11110

A tabela já mostra os três operadores de duas entradas. Agora com números de verdade:

#include <stdio.h>

void mostra_bits(unsigned char x) {
    for (int i = 7; i >= 0; i--) {
        printf("%d", (x >> i) & 1);
    }
}

int main(void) {
    unsigned char a = 12;
    unsigned char b = 10;
    printf("a     = "); mostra_bits(a);     printf(" (%d)\n", a);
    printf("b     = "); mostra_bits(b);     printf(" (%d)\n", b);
    printf("a & b = "); mostra_bits(a & b); printf(" (%d)\n", a & b);
    printf("a | b = "); mostra_bits(a | b); printf(" (%d)\n", a | b);
    printf("a ^ b = "); mostra_bits(a ^ b); printf(" (%d)\n", a ^ b);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra e_ou.c -o e_ou
$ ./e_ou
a     = 00001100 (12)
b     = 00001010 (10)
a & b = 00001000 (8)
a | b = 00001110 (14)
a ^ b = 00000110 (6)

Olhe as colunas dos bits 3 a 0. Em a & b, só o bit 3 está aceso, porque é o único aceso nos dois (1100 e 1010 → 1000). Pense no & como um filtro: ele deixa passar só os bits que estão acesos no outro número. É assim que se testa um bit, como você vai ver na seção 6.


3. |: OU, bit a bit

O | (lê-se "OU"; é a barra vertical, que no teclado brasileiro fica perto do Shift esquerdo) dá 1 se pelo menos um dos bits for 1. Na saída acima, 1100 | 1010 = 1110 (14): juntou as lâmpadas acesas dos dois. Pense no | como somar acessórios: ele acende bits sem apagar nenhum. É o operador para ligar bits e combinar opções, assunto da próxima aula.


4. ^: OU exclusivo (XOR)

O ^ (acento circunflexo; em inglês XOR, de exclusive or) dá 1 quando os bits são diferentes. 1100 ^ 1010 = 0110 (6). Pense nele como um interruptor de escada: onde o segundo número tem 1, o bit do primeiro inverte; onde tem 0, fica como estava.

Duas propriedades curiosas: x ^ x é sempre 0, e aplicar o mesmo ^ chave duas vezes devolve o número original. Com isso dá para "embaralhar" um recado de brincadeira:

#include <stdio.h>

int main(void) {
    char msg[] = "Oi, Bia!";
    char chave = 0x2A;
    for (int i = 0; msg[i] != '\0'; i++) msg[i] = msg[i] ^ chave;
    printf("Embaralhada: ");
    for (int i = 0; msg[i] != '\0'; i++) printf("%02X ", (unsigned char)msg[i]);
    printf("\n");
    for (int i = 0; msg[i] != '\0'; i++) msg[i] = msg[i] ^ chave;
    printf("De volta:    %s\n", msg);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra segredo.c -o segredo
$ ./segredo
Embaralhada: 65 43 06 0A 68 43 4B 0B
De volta:    Oi, Bia!

Cada letra virou outro byte (mostrado em hexa com %02X) e voltou depois do segundo ^. Isso não é criptografia de verdade: quem souber o truque descobre a chave em segundos. Mas a ideia de "XOR com uma chave" aparece dentro de algoritmos sérios.


5. ~: NÃO, bit a bit

O ~ (til) recebe um número só e inverte todos os bits: 0 vira 1, 1 vira 0. Mas cuidado com uma surpresa:

#include <stdio.h>
#include <stdint.h>

int main(void) {
    uint8_t x = 0x0F;
    uint8_t y = ~x;
    printf("~x direto:       %d\n", ~x);
    printf("guardado em y:   %d\n", y);
    printf("~0 = %d, ~5 = %d\n", ~0, ~5);
    unsigned int u = 0;
    printf("~0u = %u\n", ~u);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra nao.c -o nao
$ ./nao
~x direto:       -16     « esperava 240?
guardado em y:   240
~0 = -1, ~5 = -6
~0u = 4294967295

Por que ~x deu -16 e não 240 (11110000)? Antes de qualquer conta, o C promove os tipos pequenos (char, uint8_t, short) para int, de 32 bits. Então o ~ inverteu 32 bits, não 8: 00000000 00000000 00000000 00001111 virou 11111111 11111111 11111111 11110000, que num int com sinal é −16. Ao guardar em y, um uint8_t, só os 8 bits da direita ficam: 240.

Bits combinam com tipos sem sinal

Com números com sinal, o bit da esquerda decide se o número é negativo, e os resultados ficam confusos (~0 é −1, ~5 é −6). Para mexer com bits, use unsigned, uint8_t, uint32_t… e guarde o resultado numa variável do tipo certo antes de comparar ou mostrar.


6. << e >>: empurrar os bits

x << n (deslocar para a esquerda) empurra todos os bits de x n casas para a esquerda; entram zeros pela direita. x >> n (deslocar para a direita) empurra para a direita, e os bits que saem pela direita são perdidos.

#include <stdio.h>

int main(void) {
    printf("3 << 1 = %d\n", 3 << 1);
    printf("3 << 2 = %d\n", 3 << 2);
    printf("3 << 4 = %d\n", 3 << 4);
    printf("1 << 10 = %d\n", 1 << 10);
    printf("200 >> 1 = %d\n", 200 >> 1);
    printf("200 >> 3 = %d\n", 200 >> 3);
    printf("7 >> 1 = %d\n", 7 >> 1);
    printf("-8 >> 1 = %d\n", -8 >> 1);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra desloca.c -o desloca
$ ./desloca
3 << 1 = 6
3 << 2 = 12
3 << 4 = 48
1 << 10 = 1024
200 >> 1 = 100
200 >> 3 = 25
7 >> 1 = 3                « 3,5 arredondado para baixo
-8 >> 1 = -4
  • 3 é 11; 3 << 2 é 1100 = 12. Cada casa para a esquerda dobra o valor (como acrescentar um zero no fim de um número decimal multiplica por 10). Então x << n = x vezes 2n.
  • 1 << n é um número com só o bit n aceso: 1 << 10 = 1024. Esse é o truque mais usado da próxima aula.
  • Cada casa para a direita divide por 2, jogando fora o resto: 7 >> 1 = 3.
  • Deslocar um negativo para a direita (-8 >> 1) depende do compilador. O gcc e o Clang copiam o bit de sinal e dão −4, mas o padrão não garante. Mais um motivo para usar sem sinal.

Agora a mostra_bits faz sentido

Veja (x >> i) & 1 com x = 13 (00001101) e i = 2:

  1. x >> 2 empurra tudo duas casas para a direita: 00000011. O bit que estava na posição 2 agora está na posição 0.
  2. & 1 é um filtro com só o bit 0 aceso (00000001): apaga todos os outros bits. Sobra 1.
  3. Com i indo de 7 até 0, o laço mostra cada bit, da esquerda para a direita.

Par ou ímpar com & 1

O bit 0 é o único que vale um número ímpar (1); todos os outros valem números pares. Então o número é ímpar se, e só se, o bit 0 estiver aceso:

Missão: complete a condição do if usando & 1 (e não %) para o programa dizer se cada número de 1 a 6 é par ou ímpar. Atenção aos parênteses: escreva (n & 1) == 0.

#include <stdio.h>

int main(void) {
    for (int n = 1; n <= 6; n++) {
        if (0 /* troque por: o bit 0 de n está apagado */) printf("%d é par\n", n);
        else printf("%d é ímpar\n", n);
    }
    return 0;
}
#include <stdio.h>

int main(void) {
    for (int n = 1; n <= 6; n++) {
        if ((n & 1) == 0) printf("%d é par\n", n);
        else printf("%d é ímpar\n", n);
    }
    return 0;
}

No laboratório, a solução mostrou 1 é ímpar, 2 é par e assim por diante até 6 é par. O n % 2 == 0 da aula Operadores Aritméticos faz o mesmo e é mais fácil de ler; o compilador transforma os dois no mesmo código. Prefira o % quando a ideia é "par", e o & quando a ideia é "bit".


7. Deslocamentos proibidos: comportamento indefinido

Um int tem 32 bits. Deslocar 32 casas ou mais, ou um número negativo de casas, não tem resposta definida em C: é comportamento indefinido (aula Comportamento Indefinido). Veja o que acontece na prática:

#include <stdio.h>

int main(void) {
    int n = 32;
    printf("1 << 32 escrito direto: %d\n", 1 << 32);
    printf("1 << n, com n = 32:     %d\n", 1 << n);
    printf("1 << -1 escrito direto: %d\n", 1 << -1);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra ub.c -o ub
ub.c: In function ‘main’:
ub.c:5:46: warning: left shift count >= width of type [-Wshift-count-overflow]
    5 |     printf("1 << 32 escrito direto: %d\n", 1 << 32);
      |                                              ^~
ub.c:7:46: warning: left shift count is negative [-Wshift-count-negative]
    7 |     printf("1 << -1 escrito direto: %d\n", 1 << -1);
      |                                              ^~
$ ./ub
1 << 32 escrito direto: 0
1 << n, com n = 32:     1
1 << -1 escrito direto: -2147483648
left shift count >= width of type
"Quantidade do deslocamento à esquerda maior ou igual à largura do tipo". Você pediu 32 casas num tipo de 32 bits.
left shift count is negative
"Quantidade do deslocamento é negativa".
0 e 1 para a mesma conta
1 << 32 escrito direto deu 0 (o compilador fez a conta); com a variável, deu 1 (o processador fez a conta, e o processador do laboratório só olha os 5 bits de baixo do 32, que valem 0). A mesma operação deu dois resultados no mesmo programa.

E no editor? Os avisos são parecidos (shift count >= width of type, shift count is negative), mas os resultados mudam de novo: lá, 1 << 32 escrito direto mostrou 1792244464, um número sem sentido, e 1 << -1 mostrou 0. É isso que "indefinido" quer dizer: qualquer resultado vale, e nenhum é confiável.

O bit 31 de um int

Também é indefinido deslocar um 1 para dentro do bit de sinal: 1 << 31 não cabe num int (o maior é 231 − 1). Nem o gcc nem o Clang avisam, e os dois mostram -2147483648. O jeito certo é deslocar um número sem sinal: 1u << 31 vale 2147483648, sem nada de indefinido. Para 64 bits, use UINT64_C(1) << 40.

Regras para deslocar com segurança
  • Desloque números sem sinal: 1u << n, x >> n com x do tipo unsigned/uint32_t.
  • A quantidade n tem que ficar entre 0 e a largura do tipo menos 1 (0 a 31 para 32 bits). Se ela vem de fora do programa, confira antes.
  • Nunca ignore os avisos -Wshift-count-overflow e -Wshift-count-negative.

8. Armadilhas de escrita

A precedência: x & 1 == 0

Os operadores &, | e ^ têm precedência menor que == (aula Precedência). Então x & 1 == 0 é lido como x & (1 == 0):

#include <stdio.h>

int main(void) {
    int x = 6;
    if (x & 1 == 0) {
        printf("%d é par\n", x);
    } else {
        printf("%d é ímpar\n", x);
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra prec.c -o prec
prec.c: In function ‘main’:
prec.c:5:11: warning: suggest parentheses around comparison in operand of ‘&’ [-Wparentheses]
    5 |     if (x & 1 == 0) {
      |           ^
$ ./prec
6 é ímpar                « errado!

Suggest parentheses around comparison in operand of ‘&’ = "sugiro parênteses em volta da comparação usada com o &". O 1 == 0 deu 0, e 6 & 0 deu 0 (falso), então o programa foi para o else. O Clang explica ainda melhor: & has lower precedence than ==; == will be evaluated first ("o & tem precedência menor que o ==; o == será feito primeiro"). Conserto: (x & 1) == 0. Regra: sempre ponha parênteses em volta de contas com bits.

& não é &&; | não é ||

#include <stdio.h>

int main(void) {
    int a = 2, b = 1;
    printf("a && b = %d\n", a && b);
    printf("a & b  = %d\n", a & b);
    printf("a || b = %d\n", a || b);
    printf("a | b  = %d\n", a | b);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra logico.c -o logico
$ ./logico
a && b = 1
a & b  = 0                « 10 & 01: nenhum bit em comum
a || b = 1
a | b  = 3

O && (aula Operadores Lógicos) pergunta "os dois são verdadeiros?": 2 e 1 são diferentes de zero, então sim (1). O & compara bit a bit: 10 & 01 não tem nenhuma coluna com dois 1, então dá 0 (falso!). Trocar um pelo outro num if compila sem aviso e dá resultados errados de vez em quando, o pior tipo de erro. Além disso, && e || param assim que sabem a resposta; & e | sempre calculam os dois lados.

Atribuição composta

Como +=, cada operador de bits de duas entradas tem sua forma "faça e guarde": &=, |=, ^=, <<=, >>=.

#include <stdio.h>

int main(void) {
    unsigned x = 5;
    x <<= 2;  printf("x <<= 2  -> %u\n", x);
    x |= 1;   printf("x |= 1   -> %u\n", x);
    x &= 6;   printf("x &= 6   -> %u\n", x);
    x ^= 7;   printf("x ^= 7   -> %u\n", x);
    x >>= 1;  printf("x >>= 1  -> %u\n", x);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra composto.c -o composto
$ ./composto
x <<= 2  -> 20
x |= 1   -> 21
x &= 6   -> 4
x ^= 7   -> 3
x >>= 1  -> 1

Siga com os bits: 5 é 101; <<= 2 → 10100 (20); |= 1 → 10101 (21); &= 6 (00110) → 00100 (4); ^= 7 (111) → 011 (3); >>= 1 → 1.


9. Formas diferentes e qual usar

Para…Com bitsSem bitsQual usar
Multiplicar por 8x << 3x * 8x * 8: o compilador já troca por deslocamento sozinho. Escreva a intenção.
Dividir por 4x >> 2x / 4x / 4 (com negativos, os dois arredondam diferente)
Par ou ímpar(x & 1) == 0x % 2 == 0Tanto faz; o % é mais claro
Pegar o bit n(x >> n) & 1—Bits
Guardar várias opções sim/não num número|, &, ~Vários bool ou campos de bitsVeja Máscaras e Flags
Mostrar em binárioFunção mostra_bitsprintf("%b") C23A função (o %b ainda não funciona em todo lugar)
C23 <stdbit.h>: funções prontas para bits

O C23 também criou o cabeçalho <stdbit.h>, com funções como stdc_count_ones(x) (quantos bits acesos) e stdc_bit_width(x) (quantos bits são necessários). No laboratório, stdc_count_ones(200) deu 3 e stdc_bit_width(200) deu 8. A biblioteca do editor ainda não tem esse cabeçalho (o Clang diz fatal error: 'stdbit.h' file not found).


Quando dá errado

warning: suggest parentheses around comparison in operand of ‘&’

O que significa: você escreveu algo como x & 1 == 0, e o == é feito antes do &. O resultado quase nunca é o que você queria. No Clang: & has lower precedence than ==; == will be evaluated first.

Como consertar: parênteses em volta da conta de bits: (x & 1) == 0.

warning: left shift count >= width of type / left shift count is negative

O que significa: deslocamento de 32 casas ou mais num tipo de 32 bits, ou de um número negativo de casas. É comportamento indefinido: o resultado muda de compilador para compilador, e até dentro do mesmo programa.

Como consertar: use um tipo maior (UINT64_C(1) << 40) ou confira a quantidade antes (if (n < 32)).

~x deu um número negativo

O que significa: o x era pequeno (uint8_t, char) e foi promovido para int antes do ~. Os 24 bits de cima, que eram 0, viraram 1.

Como consertar: guarde o resultado no tipo certo (uint8_t y = ~x;) ou corte com uma máscara (~x & 0xFF).

O printf("%b") não mostra nada no editor

O que significa: o %b é do C23 e depende da biblioteca. A glibc do laboratório tem; a biblioteca do editor não, e o printf para no meio sem mostrar a linha.

Como consertar: use uma função como a mostra_bits desta aula.

Um if (a & b) que falha "às vezes"

O que significa: provavelmente você queria &&. Com &, dois números verdadeiros (como 2 e 1) podem dar 0.

Como consertar: para "e" de condições, &&; para "ou", ||. Os operadores de um símbolo só são para bits.


Referência rápida

OperadorNomeBit a bit, dá 1 quando…Exemplo (8 bits)
a & bE (AND)os dois bits são 11100 & 1010 = 1000
a | bOU (OR)pelo menos um é 11100 | 1010 = 1110
a ^ bOU exclusivo (XOR)os bits são diferentes1100 ^ 1010 = 0110
~aNÃO (NOT)o bit era 0 (inverte todos)~00001111 = 11110000
a << ndeslocar à esquerdaempurra n casas; entram zeros (= vezes 2n)0011 << 2 = 1100
a >> ndeslocar à direitaempurra n casas; os bits da direita somem (= dividido por 2n)1100 >> 2 = 0011
&= |= ^= <<= >>=atribuição compostafaz a conta e guarda em ax |= 1;
(x >> n) & 1receitao bit n de x (0 ou 1)(13 >> 2) & 1 = 1
1u << nreceitanúmero com só o bit n aceso1u << 3 = 1000

Exercícios

1. E bit a bit

Quanto vale 6 & 3? (Dica: 6 é 110 e 3 é 011.)

6 & 3 =

2. Deslocamento

Quanto vale 5 << 3?

5 << 3 =

3. O operador que inverte

Complete com o operador que dá 1 só onde os bits são diferentes:

unsigned trocado = x 0xFF;

4. Com int x = 6;, o que if (x & 1 == 0) faz?

5. Com uint8_t x = 0x0F;, o que printf("%d", ~x); mostra?

6. Qual destas linhas é comportamento indefinido num int de 32 bits?

Missão final: escreva a sua mostra_bits

Missão: a função mostra_bits está vazia. Escreva o laço que mostra os 8 bits de x, do bit 7 até o bit 0, usando >> e & 1, e termine com uma quebra de linha. (Não vale %b: aqui no editor ele não funciona.) A saída tem que ser:

10100101
00000001
11111111
#include <stdio.h>

void mostra_bits(unsigned char x) {
    // mostre os 8 bits de x, do 7 ao 0, e depois "\n"
}

int main(void) {
    mostra_bits(0xA5);
    mostra_bits(1);
    mostra_bits(255);
    return 0;
}
#include <stdio.h>

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

int main(void) {
    mostra_bits(0xA5);
    mostra_bits(1);
    mostra_bits(255);
    return 0;
}

Repare: 0xA5 é A (1010) seguido de 5 (0101). Cada dígito hexa vira exatamente 4 bits, e é por isso que o hexa é a "língua" preferida de quem trabalha com bits.


Resumo

  • Todo número é uma fileira de bits; o bit n vale 2n, contando da direita a partir do 0. Em C23, escreva binário com 0b1101.
  • Para ver os bits, use uma função com (x >> i) & 1. O %b do C23 funciona na glibc do laboratório, mas não no editor.
  • & (os dois), | (pelo menos um), ^ (diferentes) comparam coluna por coluna; ~ inverte todos os bits.
  • << n multiplica por 2n, >> n divide; 1u << n acende só o bit n.
  • Use tipos sem sinal: tipos pequenos são promovidos para int (o ~ de um uint8_t dá negativo), e o bit de sinal confunde tudo.
  • Deslocar um número negativo de casas, ou casas demais, ou um 1 para dentro do bit de sinal, é comportamento indefinido.
  • Ponha parênteses: (x & 1) == 0. E não confunda &/| com &&/||.