Na aula anterior, empacotamos várias flags num único byte usando máscaras e operadores bit a bit — na mão, com deslocamentos e ANDs. Funciona, mas é verboso e fácil de errar. O C oferece um recurso que formaliza essa ideia, deixando você declarar, dentro de uma struct, campos com um número exato de bits, cada um com seu nome. São os campos de bits, e eles trazem clareza ao empacotamento que fizemos manualmente, além de permitir estruturas extremamente compactas — úteis em sistemas embarcados, protocolos e qualquer lugar onde cada byte conte.
Declarando campos de bits
Um campo de bits é um membro de struct seguido de dois-pontos e o número de bits que ele deve ocupar:
#include <stdio.h>
struct Permissoes {
unsigned int ler : 1; // ocupa 1 bit
unsigned int escrever : 1; // ocupa 1 bit
unsigned int executar : 1; // ocupa 1 bit
};
int main(void) {
struct Permissoes p = {0};
p.ler = 1;
p.escrever = 1;
p.executar = 0;
printf("Ler: %u, Escrever: %u, Executar: %u\n",
p.ler, p.escrever, p.executar); // 1 1 0
printf("Tamanho da struct: %zu bytes\n", sizeof(p)); // tipicamente 4
return 0;
}
O : 1 após cada campo diz que ele ocupa apenas um bit — suficiente para guardar 0 ou 1. A grande vantagem sobre as máscaras da aula anterior é a legibilidade: você acessa p.ler e p.escrever como campos normais de struct, com nomes claros, em vez de decifrar flags & (1 << 0). O compilador cuida de todo o trabalho de deslocamento e mascaramento por baixo dos panos. É o mesmo empacotamento, mas expresso de forma limpa e nomeada.
Campos de mais de um bit
Um campo de bits pode ocupar quantos bits você especificar, o que permite guardar pequenos números num espaço mínimo:
#include <stdio.h>
struct DataCompacta {
unsigned int dia : 5; // 5 bits: 0 a 31 (suficiente para dias)
unsigned int mes : 4; // 4 bits: 0 a 15 (suficiente para meses)
unsigned int ano : 12; // 12 bits: 0 a 4095 (anos)
};
int main(void) {
struct DataCompacta d = {15, 6, 2026};
printf("%02u/%02u/%u\n", d.dia, d.mes, d.ano); // 15/06/2026
printf("Tamanho: %zu bytes\n", sizeof(d)); // ~4 bytes, não 12!
return 0;
}
Repare no cálculo do número de bits: o dia vai até 31, e 5 bits representam até 31 (2⁵ − 1), então bastam 5 bits. O mês vai até 12, cabendo em 4 bits (até 15). O ano precisa de 12 bits (até 4095). No total, 21 bits, empacotados em uns 4 bytes — enquanto três int normais ocupariam 12 bytes. Para uma data única isso é irrelevante, mas para um vetor de milhões de datas, ou para caber num pacote de rede minúsculo, a economia é decisiva.
Um cuidado ao dimensionar: o campo precisa de bits suficientes para o maior valor esperado. Se você declarasse dia : 4, só caberiam valores até 15, e atribuir 20 causaria truncamento silencioso — o valor "transbordaria" e daria errado. Sempre confira quantos bits o maior valor exige.
Onde os campos de bits brilham: protocolos e hardware
O uso mais poderoso dos campos de bits é modelar formatos de dados definidos externamente — cabeçalhos de protocolos de rede, registradores de hardware, formatos de arquivo compactos — onde bits específicos têm significados específicos. Imagine um byte de status de um dispositivo, onde cada grupo de bits significa algo:
#include <stdio.h>
struct StatusDispositivo {
unsigned int ligado : 1; // bit 0: está ligado?
unsigned int modo : 2; // bits 1-2: modo de operação (0 a 3)
unsigned int erro : 1; // bit 3: há erro?
unsigned int temperatura : 4; // bits 4-7: nível de temperatura (0 a 15)
};
int main(void) {
struct StatusDispositivo s = {0};
s.ligado = 1;
s.modo = 2; // modo 2 (cabe em 2 bits)
s.erro = 0;
s.temperatura = 9;
printf("Ligado: %u, Modo: %u, Erro: %u, Temp: %u\n",
s.ligado, s.modo, s.erro, s.temperatura);
printf("Struct inteira ocupa %zu byte(s)\n", sizeof(s)); // ~1 byte
return 0;
}
Uma struct inteira representando o status completo de um dispositivo em um único byte, com cada campo nomeado e acessível diretamente. Sem campos de bits, você precisaria de máscaras e deslocamentos manuais para extrair cada pedaço — exatamente o que fizemos, trabalhosamente, na aula passada. Os campos de bits transformam esse trabalho num acesso a struct comum, mantendo a compactação máxima.
As limitações e ressalvas (a honestidade de sempre)
Os campos de bits são úteis, mas têm restrições e imprevisibilidades que você precisa conhecer. Primeiro, você não pode tomar o endereço de um campo de bits (&s.modo é proibido), porque ele não ocupa um byte inteiro endereçável. Segundo, e mais importante: a forma exata como os campos são dispostos na memória — a ordem, o alinhamento, se cruzam fronteiras de bytes — é definida pela implementação e varia entre compiladores e arquiteturas. Isso significa que campos de bits não são portáveis entre sistemas diferentes, pela mesma razão que discutimos nos arquivos binários. Por isso, embora sejam ótimos para economizar memória dentro de um programa, muitos programadores preferem as máscaras manuais da aula anterior quando o layout exato precisa ser garantido (como em protocolos de rede reais), pois as máscaras dão controle total e previsível sobre cada bit. Conhecer essa fronteira é o que permite escolher a ferramenta certa: campos de bits pela clareza e economia local, máscaras pelo controle portável.
Comparando as duas abordagens
Vale resumir a escolha entre o que aprendemos nas duas últimas aulas. Os campos de bits (esta aula) oferecem sintaxe limpa e nomeada, com o compilador cuidando dos detalhes — ideais quando você quer clareza e economia de memória num programa e não precisa garantir o layout exato dos bits. As máscaras manuais (aula anterior) exigem mais código (deslocamentos e ANDs explícitos), mas dão controle total e portável sobre cada bit — necessárias quando o layout precisa ser exato e previsível entre sistemas. Ambas resolvem o mesmo problema de empacotamento; a diferença é o equilíbrio entre conveniência e controle. Um bom programador C conhece as duas e escolhe conforme o contexto.
Fechando o tema dos bits
Com esta aula, encerramos a incursão pela camada de bits. Você aprendeu a manipular bits individualmente com operadores e máscaras, e a formalizar o empacotamento com campos de bits em structs — junto com as ressalvas de portabilidade que orientam quando usar cada abordagem. Esse conhecimento de baixo nível é parte do que torna o C insubstituível em programação de sistemas. Agora chegamos ao momento mais aguardado da Fase 5. Na próxima aula, construiremos nossa primeira estrutura de dados dinâmica — a lista encadeada —, onde tudo o que aprendemos sobre ponteiros, memória dinâmica, structs autorreferentes e recursão se unirá numa estrutura viva, que cresce e encolhe conforme o programa precisa.
Fontes e leituras recomendadas
- The C Programming Language (K&R), Kernighan & Ritchie — Cap. 6.9, Bit-fields
- cppreference — Bit-fields — https://en.cppreference.com/w/c/language/bit_field
- Modern C, Jens Gustedt — seção sobre campos de bits e sua portabilidade — https://gustedt.gitlabpages.inria.fr/modern-c/
- Expert C Programming, Peter van der Linden — discussão sobre as armadilhas de campos de bits
- CERT C — sobre uso e limitações de campos de bits — https://wiki.sei.cmu.edu/confluence/display/c
Exercícios
Exercício 1
Defina uma struct Config com três campos de bits de 1 bit cada (modo_escuro, notificacoes, som). Crie uma instância, ligue dois deles e imprima os três valores. Verifique o tamanho da struct com sizeof.
Ver resposta
✓ Resposta:
#include <stdio.h>
struct Config {
unsigned int modo_escuro : 1;
unsigned int notificacoes : 1;
unsigned int som : 1;
};
int main(void) {
struct Config c = {0};
c.modo_escuro = 1;
c.som = 1;
printf("Escuro: %u, Notif: %u, Som: %u\n", c.modo_escuro, c.notificacoes, c.som); // 1 0 1
printf("Tamanho: %zu\n", sizeof(c)); // tipicamente 4
return 0;
}
Apesar de usar só 3 bits, a struct costuma ocupar 4 bytes (o tamanho do unsigned int base), porque os campos de bits são empacotados dentro de uma unidade de armazenamento desse tipo.
Exercício 2
Defina uma struct Cor com três campos de bits de 8 bits cada (vermelho, verde, azul, cada um de 0 a 255), representando uma cor RGB. Preencha uma cor e imprima seus componentes. Quantos bytes a struct ocupa?
Ver resposta
✓ Resposta:
#include <stdio.h>
struct Cor {
unsigned int vermelho : 8;
unsigned int verde : 8;
unsigned int azul : 8;
};
int main(void) {
struct Cor laranja = {255, 165, 0};
printf("RGB(%u, %u, %u)\n", laranja.vermelho, laranja.verde, laranja.azul);
printf("Tamanho: %zu\n", sizeof(laranja)); // tipicamente 4 (24 bits cabem em 4 bytes)
return 0;
}
Os 24 bits (3 × 8) cabem folgadamente numa unidade de 4 bytes.
Exercício 3
Defina uma struct RelogioBits com campos hora (5 bits, 0-23), minuto (6 bits, 0-59) e segundo (6 bits, 0-59). Preencha com 14:30:45 e imprima. Justifique o número de bits escolhido para cada campo.
Ver resposta
✓ Resposta:
#include <stdio.h>
struct RelogioBits {
unsigned int hora : 5; // 0-23 exige 5 bits (2^5-1 = 31 ≥ 23)
unsigned int minuto : 6; // 0-59 exige 6 bits (2^6-1 = 63 ≥ 59)
unsigned int segundo : 6; // 0-59 exige 6 bits
};
int main(void) {
struct RelogioBits r = {14, 30, 45};
printf("%02u:%02u:%02u\n", r.hora, r.minuto, r.segundo); // 14:30:45
return 0;
}
Justificativa: a hora vai até 23, e 4 bits só chegam a 15, então são necessários 5 bits (chegam a 31). Minuto e segundo vão até 59; 5 bits (até 31) seriam insuficientes, então usamos 6 bits (até 63). Sempre se escolhe o menor número de bits cujo valor máximo (2ⁿ − 1) cobre o maior valor esperado.
Exercício 4
Experimente atribuir um valor grande demais a um campo de bits pequeno: declare um campo x : 3 e atribua 10 a ele. Imprima o resultado. O que aconteceu, e por quê?
Ver resposta
✓ Resposta:
#include <stdio.h>
struct T { unsigned int x : 3; };
int main(void) {
struct T t;
t.x = 10; // 10 em binário é 1010, mas só cabem 3 bits!
printf("%u\n", t.x); // 2
return 0;
}
O valor 10 é 1010 em binário, mas o campo x só tem 3 bits. Os bits que não cabem (o 1 mais à esquerda) são descartados, restando 010, que vale 2. Ocorreu um truncamento silencioso: o C não avisa que o valor não coube, apenas guarda os bits que couberam. Por isso é essencial dimensionar cada campo com bits suficientes para o maior valor esperado.
Exercício 5
Explique duas limitações dos campos de bits que fazem com que, em certas situações (como protocolos de rede), as máscaras manuais da aula anterior sejam preferíveis.
Ver resposta
✓ Resposta: Duas limitações: (1) Layout definido pela implementação — a ordem em que os campos de bits são dispostos (do bit mais significativo ao menos significativo, ou o contrário), o alinhamento e se um campo pode cruzar a fronteira de uma unidade de armazenamento variam entre compiladores e arquiteturas. Isso torna os campos de bits não portáveis: a mesma struct pode ter disposições diferentes de bits em sistemas diferentes. Num protocolo de rede, onde a posição exata de cada bit é ditada por uma especificação que ambos os lados devem seguir, essa imprevisibilidade é inaceitável. (2) Impossibilidade de tomar o endereço — não se pode fazer &campo, o que limita certas operações. Com máscaras manuais, ao contrário, você controla exatamente qual bit é qual, com deslocamentos explícitos que produzem o mesmo resultado em qualquer sistema — dando o controle total e previsível que protocolos e formatos binários exigem. Por isso, para layouts que precisam ser exatos e portáveis, as máscaras são a escolha mais segura.