Packed Struct em Zig — O que é e Como Usar
Definição
Uma packed struct em Zig é uma struct declarada com a palavra-chave packed que possui um layout de memória exato e previsível, sem padding (bytes de preenchimento) entre os campos. Os campos são empacotados bit a bit, permitindo definir campos com tamanhos arbitrários em bits — como campos de 3 bits, 1 bit (booleano) ou 5 bits.
Packed structs são fundamentais para trabalhar com protocolos de rede, formatos de arquivo binário, registradores de hardware e qualquer situação onde o layout exato dos bits importa.
Por que Packed Structs Importam
- Layout garantido: Você sabe exatamente onde cada bit está na memória.
- Bitfields: Campos podem ter tamanhos menores que 1 byte (ex:
u3,u1). - Interoperabilidade: Mapeiam diretamente para estruturas de protocolos binários.
- Conversão segura: Podem ser convertidas de/para inteiros com
@bitCast. - Hardware: Permitem mapear registradores de dispositivos com precisão bit a bit.
Exemplo Prático
Bitfields para Protocolo
const std = @import("std");
const CabecalhoTCP = packed struct {
porta_origem: u16,
porta_destino: u16,
numero_sequencia: u32,
numero_ack: u32,
offset: u4,
reservado: u3,
flags: packed struct {
ns: u1,
cwr: u1,
ece: u1,
urg: u1,
ack: u1,
psh: u1,
rst: u1,
syn: u1,
fin: u1,
},
janela: u16,
checksum: u16,
ponteiro_urgente: u16,
};
pub fn main() void {
std.debug.print("Tamanho do cabeçalho: {} bytes\n", .{@sizeOf(CabecalhoTCP)});
}
Conversão para Inteiro
const Flags = packed struct {
leitura: bool, // 1 bit
escrita: bool, // 1 bit
execucao: bool, // 1 bit
_padding: u5 = 0, // 5 bits para completar 1 byte
};
pub fn main() void {
const flags = Flags{
.leitura = true,
.escrita = true,
.execucao = false,
};
// Converter para inteiro
const valor: u8 = @bitCast(flags);
std.debug.print("Flags como byte: 0b{b:0>8}\n", .{valor});
// Saída: 0b00000011
}
Lendo Dados Binários
fn lerCabecalho(dados: []const u8) *const CabecalhoTCP {
return @ptrCast(@alignCast(dados.ptr));
}
Packed vs Extern vs Normal
// Normal: compilador pode reordenar e adicionar padding
const Normal = struct {
a: u8,
b: u32, // padding de 3 bytes antes de b
c: u8,
};
// Packed: sem padding, campos empacotados bit a bit
const Packed = packed struct {
a: u8,
b: u32,
c: u8,
};
// Extern: layout compatível com C ABI
const Extern = extern struct {
a: u8,
b: u32,
c: u8,
};
// @sizeOf(Normal) >= 12 (com padding)
// @sizeOf(Packed) == 5 (sem padding)
// @sizeOf(Extern) == 12 (padding C)
Armadilhas Comuns
- Performance: Acessar campos não alinhados em packed structs pode ser mais lento que structs normais. O processador pode precisar de instruções extras.
- Ponteiros para campos: Não é possível obter ponteiros para campos individuais de uma packed struct que não estejam alinhados a bytes.
- Endianness: Packed structs usam a endianness nativa. Para protocolos de rede (big-endian), use
std.mem.bigToNativepara converter. - Confundir com
extern struct: Para interop com C, useextern struct. Packed structs são para controle bit-a-bit. - Bool em packed struct:
boolocupa 1 bit, não 1 byte como em structs normais.
Casos de Uso
Packed structs são a ferramenta certa quando o layout exato dos bits na memória importa:
- Registradores de hardware: Microcontroladores expõem periféricos como registradores mapeados em memória com campos de tamanho específico em bits.
- Protocolos de rede: Formatos como IPv4, TCP e DNS têm campos com tamanhos arbitrários definidos pelos RFCs.
- Formatos de arquivo: Cabeçalhos de BMP, ELF, PNG e outros formatos binários definem layouts específicos.
- Compressão de dados: Representar flags e pequenos valores em poucos bits para economizar memória.
const std = @import("std");
// Byte de flags típico em protocolos embarcados
const StatusReg = packed struct {
pronto: u1,
ocupado: u1,
erro: u1,
overflow: u1,
_reservado: u4 = 0,
};
pub fn lerStatus(porta: u8) StatusReg {
return @bitCast(porta);
}
pub fn main() void {
const byte_recebido: u8 = 0b00000101; // pronto=1, erro=1
const status = lerStatus(byte_recebido);
std.debug.print("Pronto: {}, Erro: {}\n", .{ status.pronto, status.erro });
}
Boas Práticas
- Sempre verifique o tamanho com
@sizeOfe@bitSizeOfpara confirmar que o layout está correto. - Para protocolos de rede (big-endian), combine packed structs com
@byteSwapoustd.mem.bigToNativeapós ler os dados. - Use
_paddingcom valor default zero para campos reservados, tornando o propósito explícito. - Prefira
extern structquando a interoperabilidade com C for o objetivo; usepacked structapenas quando o controle bit a bit for necessário.
Termos Relacionados
- Struct — Structs regulares em Zig
- Alignment — Alinhamento de memória
- Union — Tipos union em Zig
- Slice — Referências a sequências de memória