@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
- Registradores MMIO: Acessar periféricos de hardware mapeados em endereços fixos de memória.
- Desenvolvimento de sistemas operacionais: Manipular tabelas de página, IDT, GDT e outras estruturas do processador.
- Callbacks com contexto: Armazenar ponteiros como
usizeem APIs C que usamvoid*para dados de usuário e recuperá-los posteriormente. - Alocadores de memória: Converter endereços calculados aritmeticamente de volta para ponteiros utilizáveis.
- 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 (*T → usize). @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
- @intFromPtr — Operação inversa: converte ponteiro para inteiro
- @ptrCast — Conversão entre tipos de ponteiro
- @alignCast — Ajusta alinhamento de ponteiro
- @volatileCast — Remove qualificador volatile