Unions e campos de bits
1. Introdução às Unions
Em Linguagem C, uma union é um tipo de dado que permite armazenar diferentes tipos de dados no mesmo espaço de memória. Diferentemente de uma struct, onde cada membro ocupa seu próprio endereço, todos os membros de uma union compartilham o mesmo local de memória.
A sintaxe básica é semelhante à de uma struct:
union exemplo {
int inteiro;
float flutuante;
char caractere;
};
Para declarar e acessar membros:
#include <stdio.h>
union Dado {
int i;
float f;
char c;
};
int main() {
union Dado d;
d.i = 42;
printf("Inteiro: %d\n", d.i);
d.f = 3.14;
printf("Float: %.2f\n", d.f);
// Após atribuir d.f, d.i foi sobrescrito
printf("Inteiro após float: %d\n", d.i); // Valor imprevisível
return 0;
}
2. Funcionamento da Memória em Unions
O princípio fundamental da union é que todos os membros ocupam o mesmo endereço inicial. O tamanho total da union é determinado pelo maior membro declarado.
#include <stdio.h>
union Teste {
char c; // 1 byte
int i; // 4 bytes
double d; // 8 bytes
};
int main() {
union Teste t;
printf("Endereço de t.c: %p\n", &t.c);
printf("Endereço de t.i: %p\n", &t.i);
printf("Endereço de t.d: %p\n", &t.d);
printf("Tamanho da union: %zu bytes\n", sizeof(t));
// Todos os endereços são iguais, tamanho = 8 bytes
return 0;
}
Isso significa que escrever em um membro automaticamente corrompe os dados dos outros membros. Para evitar acessos inválidos, é responsabilidade do programador controlar qual membro está ativo.
3. Aplicações Práticas de Unions
Economia de memória em sistemas embarcados:
union Sensor {
int temperatura;
unsigned int pressao;
float umidade;
};
Tagged unions (união com discriminador):
#include <stdio.h>
enum Tipo { INT, FLOAT, STRING };
struct Valor {
enum Tipo tipo;
union {
int i;
float f;
char* s;
} dado;
};
void imprimir(struct Valor v) {
switch(v.tipo) {
case INT: printf("%d\n", v.dado.i); break;
case FLOAT: printf("%.2f\n", v.dado.f); break;
case STRING: printf("%s\n", v.dado.s); break;
}
}
int main() {
struct Valor v1 = {INT, .dado.i = 10};
struct Valor v2 = {FLOAT, .dado.f = 3.14};
imprimir(v1);
imprimir(v2);
return 0;
}
Manipulação de bytes de um inteiro:
#include <stdio.h>
union BytesInt {
int valor;
unsigned char bytes[sizeof(int)];
};
int main() {
union BytesInt bi;
bi.valor = 0x12345678;
for(int i = 0; i < sizeof(int); i++) {
printf("Byte %d: 0x%02X\n", i, bi.bytes[i]);
}
return 0;
}
4. Introdução aos Campos de Bits
Campos de bits permitem especificar o número exato de bits que um membro de uma struct deve ocupar. A sintaxe utiliza dois pontos seguidos da largura em bits:
struct Flags {
unsigned int ativo : 1;
unsigned int modo : 2;
unsigned int prioridade : 3;
unsigned int : 2; // bits de preenchimento
};
Exemplo prático de empacotamento:
#include <stdio.h>
struct Pacote {
unsigned int versao : 4;
unsigned int tipo : 4;
unsigned int tamanho : 8;
unsigned int checksum : 8;
};
int main() {
struct Pacote p = {2, 5, 100, 0xFF};
printf("Versão: %u\n", p.versao);
printf("Tipo: %u\n", p.tipo);
printf("Tamanho: %u\n", p.tamanho);
printf("Checksum: 0x%02X\n", p.checksum);
printf("Tamanho total: %zu bytes\n", sizeof(p));
return 0;
}
5. Funcionamento e Armazenamento de Campos de Bits
O compilador aloca campos de bits em unidades de armazenamento (geralmente 4 bytes). A ordem de bits depende da arquitetura (endianness) e do compilador.
#include <stdio.h>
struct Bits {
unsigned int a : 3;
unsigned int b : 5;
unsigned int c : 8;
};
int main() {
struct Bits b = {7, 31, 255};
printf("Tamanho: %zu bytes\n", sizeof(b));
// A ordem dos bits na memória é dependente de implementação
return 0;
}
Limitações importantes:
- Tipos suportados: int, unsigned int, signed int (e _Bool em C99)
- Não é possível obter endereço de um campo de bits (&b.a é inválido)
- Não podem ser usados em sizeof
- Arrays de campos de bits não são permitidos
6. Aplicações Práticas de Campos de Bits
Representação de registradores de hardware:
struct Registrador {
unsigned int enable : 1;
unsigned int mode : 2;
unsigned int interrupt : 1;
unsigned int reserved : 4;
unsigned int status : 8;
};
Estruturas compactas para protocolos:
struct CabecalhoTCP {
unsigned int source_port : 16;
unsigned int dest_port : 16;
unsigned int seq_num : 32;
unsigned int ack_num : 32;
unsigned int data_offset : 4;
unsigned int reserved : 3;
unsigned int flags : 9;
unsigned int window : 16;
unsigned int checksum : 16;
unsigned int urgent_ptr : 16;
};
Otimização de flags booleanos:
struct Configuracao {
unsigned int debug : 1;
unsigned int verbose : 1;
unsigned int log : 1;
unsigned int cache : 1;
unsigned int ssl : 1;
unsigned int compress : 1;
unsigned int : 2; // preenchimento
};
int main() {
struct Configuracao cfg = {1, 0, 1, 0, 1, 0};
printf("Tamanho: %zu byte(s)\n", sizeof(cfg));
return 0;
}
7. Boas Práticas e Cuidados
Portabilidade: O comportamento de campos de bits pode variar entre compiladores e arquiteturas. A ordem de bits, o alinhamento e o tipo de int usado são dependentes de implementação.
Alternativas modernas: Em muitos casos, máscaras de bits e operadores bitwise oferecem mais controle e portabilidade:
#define FLAG_DEBUG (1 << 0)
#define FLAG_VERBOSE (1 << 1)
#define FLAG_LOG (1 << 2)
unsigned int flags = 0;
flags |= FLAG_DEBUG;
if (flags & FLAG_DEBUG) { /* ... */ }
Quando evitar:
- Quando a portabilidade entre diferentes compiladores é crítica
- Em código que precisa ser depurado facilmente (campos de bits são difíceis de inspecionar)
- Quando o desempenho é mais importante que a economia de memória (acessos a campos de bits podem gerar código mais lento)
- Em interfaces de rede ou arquivos binários que precisam seguir um layout exato
Recomendações finais:
- Sempre use unsigned int para campos de bits a menos que sinais sejam necessários
- Documente claramente o layout esperado dos bits
- Considere usar union com campos de bits e tipos inteiros para inspeção direta
- Teste em diferentes plataformas se a portabilidade for necessária
Referências
- Documentação oficial do GCC sobre campos de bits — Especificação detalhada de como o GCC implementa estruturas, uniões e campos de bits
- C Programming: Unions (Tutorialspoint) — Tutorial completo sobre unions em C com exemplos práticos
- Bit Fields in C (GeeksforGeeks) — Artigo técnico explicando campos de bits, limitações e aplicações
- Union in C (Programiz) — Guia introdutório sobre unions com exemplos de código
- Embedded C: Bit Fields and Unions (Embedded.com) — Artigo focado em aplicações de campos de bits e unions em sistemas embarcados
- C Standard (ISO/IEC 9899:2018) - Section 6.7.2.1 — Especificação oficial da linguagem C sobre structs, unions e campos de bits