@sizeOf em Zig — Referência e Exemplos

@sizeOf em Zig

O @sizeOf retorna o tamanho de armazenamento de um tipo em bytes, incluindo qualquer padding de alinhamento. É o equivalente do sizeof do C, mas disponível como builtin em tempo de compilação. Fundamental para alocação de memória, serialização e interoperabilidade com código C.

Sintaxe

@sizeOf(comptime T: type) comptime_int

O que faz

O @sizeOf consulta o tamanho que um valor do tipo especificado ocupa na memória, em bytes. O valor retornado inclui qualquer padding inserido pelo compilador para manter o alinhamento correto. Este é o número de bytes que um std.mem.Allocator alocaria para uma instância desse tipo.

Parâmetros

  • T (type, comptime): O tipo cujo tamanho será consultado. Pode ser qualquer tipo válido exceto comptime_int, comptime_float e outros tipos que existem apenas em comptime.

Valor de retorno

Retorna um comptime_int representando o tamanho em bytes do tipo.

Exemplos práticos

Exemplo 1: Tamanho de tipos primitivos

const std = @import("std");

test "tamanho de tipos primitivos" {
    try std.testing.expect(@sizeOf(u8) == 1);
    try std.testing.expect(@sizeOf(u16) == 2);
    try std.testing.expect(@sizeOf(u32) == 4);
    try std.testing.expect(@sizeOf(u64) == 8);
    try std.testing.expect(@sizeOf(i32) == 4);
    try std.testing.expect(@sizeOf(f32) == 4);
    try std.testing.expect(@sizeOf(f64) == 8);
    try std.testing.expect(@sizeOf(bool) == 1);

    // Ponteiros em sistemas de 64 bits
    try std.testing.expect(@sizeOf(*u8) == 8);
    try std.testing.expect(@sizeOf(usize) == 8);
}

Exemplo 2: Entendendo padding em structs

const std = @import("std");

const SemPadding = struct {
    a: u32,
    b: u32,
};

const ComPadding = struct {
    a: u8,    // 1 byte + 7 de padding
    b: u64,   // 8 bytes
    c: u8,    // 1 byte + 7 de padding
};

const Otimizada = struct {
    b: u64,   // 8 bytes
    a: u8,    // 1 byte
    c: u8,    // 1 byte + 6 de padding
};

test "padding em structs" {
    try std.testing.expect(@sizeOf(SemPadding) == 8);   // 4 + 4
    std.debug.print("ComPadding: {} bytes\n", .{@sizeOf(ComPadding)});
    std.debug.print("Otimizada: {} bytes\n", .{@sizeOf(Otimizada)});

    // Reordenar campos pode reduzir o tamanho total
    // ComPadding provavelmente é 24 bytes (com padding)
    // Otimizada provavelmente é 16 bytes (menos padding)
}

Exemplo 3: Buffer de serialização

const std = @import("std");

const Cabecalho = packed struct {
    versao: u8,
    tipo: u8,
    tamanho: u16,
    id: u32,
};

fn serializar(cabecalho: Cabecalho) [@sizeOf(Cabecalho)]u8 {
    return @bitCast(cabecalho);
}

fn deserializar(bytes: [@sizeOf(Cabecalho)]u8) Cabecalho {
    return @bitCast(bytes);
}

test "serialização com tamanho conhecido" {
    // packed struct garante layout previsível
    try std.testing.expect(@sizeOf(Cabecalho) == 8);

    const cab = Cabecalho{
        .versao = 1,
        .tipo = 3,
        .tamanho = 256,
        .id = 42,
    };

    const bytes = serializar(cab);
    const recuperado = deserializar(bytes);

    try std.testing.expect(recuperado.versao == 1);
    try std.testing.expect(recuperado.id == 42);
}

Casos de uso comuns

  1. Alocação de memória: Calcular quantos bytes alocar para arrays ou buffers de tipos específicos.
  2. Serialização binária: Determinar o tamanho de estruturas para leitura/escrita em arquivos ou rede.
  3. Interoperabilidade com C: Garantir que structs Zig tenham o mesmo tamanho que as equivalentes em C.
  4. Otimização de structs: Comparar tamanhos com diferentes ordenações de campos para minimizar padding.
  5. Pools de memória: Dimensionar pools e caches baseados no tamanho dos objetos armazenados.

Diferenças entre @sizeOf e @bitSizeOf

  • @sizeOf retorna o tamanho em bytes, incluindo padding de alinhamento.
  • @bitSizeOf retorna o tamanho em bits, sem incluir padding extra.
  • Para um u24, @sizeOf retorna 4 (arredondado para bytes com alinhamento), enquanto @bitSizeOf retorna 24.

Comportamento com tipos especiais

Alguns tipos merecem atenção especial ao usar @sizeOf:

  • Tipos comptime-only (comptime_int, comptime_float, type): @sizeOf não é aplicável e causa erro de compilação.
  • void: @sizeOf(void) == 0. Void é um tipo de tamanho zero — útil para containers genéricos onde o tipo pode ser void.
  • noreturn: Erro de compilação, pois não existe instância de noreturn.
  • Slices ([]T): @sizeOf([]T) == 2 * @sizeOf(usize) — um slice é fat pointer (ponteiro + comprimento).
  • Tipos opcionais (?T): Pode ser maior que T por adicionar um byte de discriminante, ou igual se T já tiver um valor inválido reservado (null pointer optimization).
const std = @import("std");

test "tamanhos especiais" {
    try std.testing.expect(@sizeOf(void) == 0);
    try std.testing.expect(@sizeOf([]u8) == 2 * @sizeOf(usize)); // fat pointer
    try std.testing.expect(@sizeOf(?*u8) == @sizeOf(*u8)); // null pointer optimization
    try std.testing.expect(@sizeOf(?u32) == 8); // u32 + padding + discriminante
}

Otimização de layout de struct

Reordenar campos de uma struct pode reduzir significativamente seu tamanho em memória, eliminando padding desnecessário. A regra geral é ordenar campos do maior para o menor alinhamento:

const std = @import("std");

// Layout ruim: 24 bytes por causa do padding
const Ruim = struct {
    flag: bool,   // 1 byte + 7 de padding
    valor: u64,   // 8 bytes
    id: u32,      // 4 bytes + 4 de padding
};

// Layout otimizado: 16 bytes
const Bom = struct {
    valor: u64,   // 8 bytes
    id: u32,      // 4 bytes
    flag: bool,   // 1 byte + 3 de padding (para alinhar a próxima instância)
};

test "otimização de layout" {
    std.debug.print("Ruim: {} bytes\n", .{@sizeOf(Ruim)}); // 24
    std.debug.print("Bom: {} bytes\n", .{@sizeOf(Bom)});   // 16
}

Comparação com sizeof do C

O @sizeOf do Zig é semanticamente idêntico ao sizeof do C para tipos compatíveis. Ambos incluem padding de alinhamento. A diferença é que em Zig é um builtin da linguagem, sempre avaliado em comptime, sem necessidade de cabeçalhos:

// C
#include <stdio.h>
printf("%zu\n", sizeof(int));    // 4
printf("%zu\n", sizeof(float));  // 4
// Zig
std.debug.print("{}\n", .{@sizeOf(i32)}); // 4
std.debug.print("{}\n", .{@sizeOf(f32)}); // 4

Para packed struct em Zig, @sizeOf retorna o tamanho exato sem padding extra — equivalente a __attribute__((packed)) no GCC.

Perguntas Frequentes

P: Por que @sizeOf(bool) == 1 se bool só precisa de 1 bit?

R: Em Zig (como em C), o menor endereçável é o byte. Não é possível ter uma variável que ocupa apenas 1 bit de memória endereçável individualmente. Para compactar múltiplos bools, use packed struct ou campos de bits.

P: @sizeOf e @bitSizeOf sempre retornam valores consistentes?

R: Para tipos primitivos, @sizeOf(T) * 8 == @bitSizeOf(T). Para u24, porém, @sizeOf pode retornar 4 (arredondado para alinhamento) enquanto @bitSizeOf retorna 24. Em packed struct, os campos são compactados e @bitSizeOf reflete o tamanho real sem padding.

P: Como usar @sizeOf para calcular o número de elementos que cabem em um buffer?

R: Divida o tamanho do buffer pelo tamanho do elemento:

const BUFFER_SIZE = 4096;
var buffer: [BUFFER_SIZE]u8 = undefined;
const n_elementos = BUFFER_SIZE / @sizeOf(MinhaStruct);

Builtins relacionados

  • @bitSizeOf — Retorna o tamanho em bits
  • @alignOf — Retorna o alinhamento de um tipo
  • @typeInfo — Informações detalhadas sobre o tipo
  • @memcpy — Copia bloco de memória

Tutoriais relacionados

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.