@alignOf em Zig
O @alignOf retorna o alinhamento de memória de um tipo em bytes. O alinhamento determina em quais endereços de memória um valor desse tipo pode ser armazenado — o endereço deve ser divisível pelo alinhamento. Esse conceito é fundamental para desempenho e corretude em programação de sistemas.
Sintaxe
@alignOf(comptime T: type) comptime_int
O que faz
O @alignOf consulta o compilador para obter o requisito de alinhamento natural de um tipo em bytes. O alinhamento é sempre uma potência de 2. Tipos mal alinhados podem causar penalidades de desempenho em algumas arquiteturas ou até mesmo falhas de hardware em outras.
Parâmetros
- T (
type, comptime): O tipo cujo alinhamento será consultado. Pode ser qualquer tipo válido em Zig.
Valor de retorno
Retorna um comptime_int representando o alinhamento em bytes. O valor é sempre uma potência de 2.
Exemplos práticos
Exemplo 1: Alinhamento de tipos primitivos
const std = @import("std");
test "alinhamento de tipos primitivos" {
// Em arquiteturas de 64 bits típicas:
try std.testing.expect(@alignOf(u8) == 1);
try std.testing.expect(@alignOf(u16) == 2);
try std.testing.expect(@alignOf(u32) == 4);
try std.testing.expect(@alignOf(u64) == 8);
try std.testing.expect(@alignOf(f32) == 4);
try std.testing.expect(@alignOf(f64) == 8);
try std.testing.expect(@alignOf(bool) == 1);
}
Exemplo 2: Alinhamento de structs
const std = @import("std");
const Compacta = struct {
a: u8,
b: u8,
};
const ComPadding = struct {
a: u8,
b: u64, // Força alinhamento de 8
c: u8,
};
test "alinhamento de structs" {
// Struct alinha pelo campo de maior alinhamento
try std.testing.expect(@alignOf(Compacta) == 1);
try std.testing.expect(@alignOf(ComPadding) == 8);
// Tamanho inclui padding para manter alinhamento
std.debug.print("Tamanho de Compacta: {}\n", .{@sizeOf(Compacta)});
std.debug.print("Tamanho de ComPadding: {}\n", .{@sizeOf(ComPadding)});
}
Exemplo 3: Alocação alinhada personalizada
const std = @import("std");
fn alocarAlinhado(
allocator: std.mem.Allocator,
comptime T: type,
n: usize,
) ![]T {
const alinhamento = @alignOf(T);
std.debug.print("Alocando {} elementos de {s} com alinhamento {}\n", .{
n,
@typeName(T),
alinhamento,
});
return allocator.alloc(T, n);
}
const VetorSimd = @Vector(4, f32);
test "alocação com alinhamento SIMD" {
const allocator = std.testing.allocator;
// Vetores SIMD geralmente requerem alinhamento de 16 bytes
std.debug.print("Alinhamento de @Vector(4, f32): {}\n", .{
@alignOf(VetorSimd),
});
const dados = try alocarAlinhado(allocator, VetorSimd, 100);
defer allocator.free(dados);
// Verificar que os dados estão alinhados
try std.testing.expect(@intFromPtr(dados.ptr) % @alignOf(VetorSimd) == 0);
}
Casos de uso comuns
- Alocadores de memória: Garantir que blocos alocados respeitem o alinhamento necessário dos tipos armazenados.
- Operações SIMD: Verificar e garantir alinhamento correto para instruções vetoriais que exigem alinhamento de 16 ou 32 bytes.
- Interoperabilidade com C: Garantir que structs passadas para código C tenham o alinhamento esperado.
- Otimização de layout: Entender o padding inserido pelo compilador para otimizar o layout de structs.
- Hardware MMIO: Respeitar requisitos de alinhamento para acesso a registradores de hardware.
@alignOf vs alignof em C
Em C, _Alignof(T) (ou alignof(T) com <stdalign.h>) faz a mesma coisa. O Zig mapeia @alignOf diretamente para o mesmo conceito, mas como builtin comptime — o valor é sempre conhecido em tempo de compilação.
// C
#include <stdalign.h>
size_t al = alignof(uint32_t); // 4
// Zig equivalente
const al = @alignOf(u32); // 4, comptime_int
Como o alinhamento afeta o layout de structs
O compilador insere padding (bytes de preenchimento) entre campos de structs para respeitar os requisitos de alinhamento de cada campo. Entender isso é crucial para otimizar o uso de memória:
const Ineficiente = struct {
a: u8, // offset 0, 1 byte
// 7 bytes de padding aqui
b: u64, // offset 8, 8 bytes
c: u8, // offset 16, 1 byte
// 7 bytes de padding no final
};
// @sizeOf(Ineficiente) == 24
const Otimizada = struct {
b: u64, // offset 0, 8 bytes
a: u8, // offset 8, 1 byte
c: u8, // offset 9, 1 byte
// 6 bytes de padding no final
};
// @sizeOf(Otimizada) == 16
Reordenar campos do maior para o menor alinhamento reduz padding. O @alignOf ajuda a entender e verificar esse layout.
Alinhamento especificado manualmente
O Zig permite especificar alinhamento customizado com a sintaxe align(N):
const Buffer = struct {
dados: [64]u8 align(64), // alinhado a 64 bytes para cache line
};
var buf: Buffer align(4096) = undefined; // alinhado a página
fn processarSIMD(dados: []align(32) f32) void {
// garante dados alinhados para AVX2
}
@alignOf é útil para verificar que o alinhamento especificado foi respeitado.
Erros comuns
Assumir alinhamento fixo entre plataformas: @alignOf(usize) é 4 em sistemas 32-bit e 8 em 64-bit. Código que assume um valor fixo pode quebrar em arquiteturas diferentes.
Confundir @sizeOf com @alignOf: @sizeOf(u24) retorna 4 (com padding), mas @alignOf(u24) retorna 1. São valores diferentes com usos diferentes.
Perguntas Frequentes
O alinhamento pode ser zero?
Não. O alinhamento mínimo é sempre 1 (qualquer endereço é válido). Tipos como u8 e bool têm alinhamento 1.
@alignOf funciona com tipos genéricos?
Sim. Como é um builtin comptime, pode ser usado com qualquer tipo, incluindo tipos gerados por funções comptime ou parâmetros genéricos comptime T: type.
Por que alguns tipos têm alinhamento maior que seu tamanho?
Isso não ocorre para tipos padrão no Zig. Mas com align(N) personalizado, o alinhamento pode ser maior que o tamanho. Em C, structs com __attribute__((aligned(N))) têm esse comportamento.
Builtins relacionados
- @sizeOf — Retorna o tamanho em bytes de um tipo
- @bitSizeOf — Retorna o tamanho em bits
- @alignCast — Converte alinhamento de ponteiro
- @ptrCast — Conversão entre tipos de ponteiro