@ptrFromInt em Zig — Referência e Exemplos

@ptrFromInt em Zig

O @ptrFromInt converte um valor inteiro (usize) para um ponteiro tipado. É a operação inversa de @intFromPtr. Este builtin é essencial para programação de sistemas, onde endereços de hardware, registradores mapeados em memória (MMIO) e tabelas de página precisam ser acessados diretamente.

Sintaxe

@ptrFromInt(comptime T: type, endereco: usize) T

Na prática, o tipo de retorno é inferido pelo contexto:

const ptr: *u32 = @ptrFromInt(endereco);

O que faz

O @ptrFromInt interpreta um valor numérico como um endereço de memória e cria um ponteiro tipado apontando para esse endereço. O compilador não verifica se o endereço é válido — essa é responsabilidade do programador.

Esta operação é considerada insegura porque não há garantia de que o endereço fornecido contenha dados válidos do tipo esperado, ou sequer que o endereço seja acessível.

Parâmetros

  • endereco (usize): O endereço de memória numérico a ser convertido para ponteiro. Deve estar alinhado conforme os requisitos do tipo de destino.

Valor de retorno

Retorna um ponteiro do tipo especificado pelo contexto, apontando para o endereço fornecido.

Exemplos práticos

Exemplo 1: Acesso a registradores de hardware (MMIO)

const std = @import("std");

// Simulação de acesso a registradores mapeados em memória
const UART_BASE: usize = 0x1000_0000;
const UART_DATA_OFFSET: usize = 0x00;
const UART_STATUS_OFFSET: usize = 0x04;

fn registradorUart(offset: usize) *volatile u32 {
    return @ptrFromInt(UART_BASE + offset);
}

fn enviarByte(byte: u8) void {
    // Escrever no registrador de dados da UART
    const reg_status = registradorUart(UART_STATUS_OFFSET);
    const reg_data = registradorUart(UART_DATA_OFFSET);

    // Esperar até que o transmissor esteja pronto
    while (reg_status.* & 0x20 != 0) {}

    // Enviar o byte
    reg_data.* = @as(u32, byte);
}

Exemplo 2: Restaurando um ponteiro previamente convertido

const std = @import("std");

fn armazenarContexto(ptr: anytype) usize {
    return @intFromPtr(ptr);
}

fn recuperarContexto(comptime T: type, endereco: usize) T {
    return @ptrFromInt(endereco);
}

test "armazenar e recuperar contexto" {
    var dados: u64 = 0xDEADBEEF;
    const endereco = armazenarContexto(&dados);

    const ptr_recuperado: *u64 = recuperarContexto(*u64, endereco);
    try std.testing.expect(ptr_recuperado.* == 0xDEADBEEF);
}

Exemplo 3: Implementação de página de memória

const std = @import("std");

const TAMANHO_PAGINA = 4096;

const Pagina = struct {
    dados: [TAMANHO_PAGINA]u8,
};

fn paginaDoEndereco(endereco: usize) *Pagina {
    // Alinhar o endereço para o início da página
    const endereco_alinhado = endereco & ~(@as(usize, TAMANHO_PAGINA - 1));
    return @ptrFromInt(endereco_alinhado);
}

fn offsetNaPagina(endereco: usize) usize {
    return endereco & (TAMANHO_PAGINA - 1);
}

test "cálculo de página" {
    const endereco: usize = 0x1234_5678;
    const offset = offsetNaPagina(endereco);
    try std.testing.expect(offset == 0x678);
}

Casos de uso comuns

  1. Registradores MMIO: Acessar periféricos de hardware mapeados em endereços fixos de memória.
  2. Desenvolvimento de sistemas operacionais: Manipular tabelas de página, IDT, GDT e outras estruturas do processador.
  3. Callbacks com contexto: Armazenar ponteiros como usize em APIs C que usam void* para dados de usuário e recuperá-los posteriormente.
  4. Alocadores de memória: Converter endereços calculados aritmeticamente de volta para ponteiros utilizáveis.
  5. Depuração: Criar ponteiros para endereços conhecidos ao investigar dumps de memória.

Considerações de alinhamento

Um requisito fundamental de @ptrFromInt é que o endereço fornecido deve satisfazer o alinhamento do tipo de destino. Se o tipo T requer alinhamento de 4 bytes, o endereço deve ser múltiplo de 4. Violar essa regra causa undefined behavior em ReleaseFast ou panic em modo safe.

// Verificar alinhamento antes do cast
fn ptrSeguro(comptime T: type, endereco: usize) ?*T {
    const alinhamento = @alignOf(T);
    if (endereco % alinhamento != 0) return null;
    return @ptrFromInt(endereco);
}

Uso com volatile em MMIO

Registradores de hardware mapeados em memória devem quase sempre ser acessados via ponteiros volatile, para evitar que o compilador otimize ou reordene os acessos:

// Correto para MMIO: sempre usar *volatile
const UART_DR: *volatile u32 = @ptrFromInt(0x4000_4400);

// Ler o registrador sem otimização
const dado = UART_DR.*;

// Escrever no registrador (cada escrita é preservada)
UART_DR.* = 0x41; // 'A'

Sem volatile, o compilador pode eliminar leituras repetidas ou reordenar escritas — comportamento correto para memória normal, mas catastrófico para I/O de hardware.

Comparação com equivalente em C

Em C, a conversão de inteiro para ponteiro usa cast explícito:

// C: cast de inteiro para ponteiro
#define UART_BASE 0x40004400UL
volatile unsigned int *uart = (volatile unsigned int *)UART_BASE;
*uart = 0x41;

Em Zig, a mesma operação é mais explícita sobre a natureza da conversão:

// Zig: conversão explícita com tipo inferido pelo contexto
const uart: *volatile u32 = @ptrFromInt(0x4000_4400);
uart.* = 0x41;

Erros comuns

1. Usar endereço nulo sem verificação:

// PERIGOSO: derreferenciar endereço 0 causa segfault
const ptr: *u32 = @ptrFromInt(0);
_ = ptr.*; // undefined behavior / crash

// CORRETO: verificar se o endereço é válido antes de usar
const endereco: usize = obterEndereço();
if (endereco == 0) return error.EnderecoNulo;
const ptr: *u32 = @ptrFromInt(endereco);

2. Esquecer volatile em MMIO:

// ERRADO: compilador pode otimizar/eliminar leituras
const reg: *u32 = @ptrFromInt(0x4000_0000);

// CORRETO: volatile garante que cada acesso seja emitido
const reg: *volatile u32 = @ptrFromInt(0x4000_0000);

3. Misturar com @ptrCast desnecessariamente: Se você já tem um ponteiro e quer mudar o tipo, use @ptrCast diretamente. @ptrFromInt é apenas para quando você parte de um inteiro.

Perguntas Frequentes

P: Qual é a diferença entre @ptrFromInt e @intFromPtr?

R: São operações inversas. @intFromPtr converte um ponteiro existente para seu endereço numérico (*Tusize). @ptrFromInt faz o caminho contrário: cria um ponteiro a partir de um endereço numérico (usize*T). Juntos, permitem armazenar e recuperar ponteiros em APIs que usam inteiros para contexto (como callbacks C com void *).

P: @ptrFromInt pode ser usado em comptime?

R: Apenas em contextos onde o endereço é conhecido em comptime (raro em código normal). Em comptime, o compilador não tem acesso ao espaço de endereçamento de runtime, então a maioria dos usos de @ptrFromInt é exclusivamente runtime.

P: Como usar @ptrFromInt com linker symbols?

R: Para acessar símbolos do linker (como __start_data em scripts de linker), declare-os como extern e use @intFromPtr para obter o endereço, ou use @ptrFromInt com o valor exportado:

extern const __heap_start: u8;
const heap_inicio: usize = @intFromPtr(&__heap_start);

Builtins relacionados

Tutoriais relacionados

Continue aprendendo Zig

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