Converter Ponteiros C para Zig

Introdução

A conversão de ponteiros entre C e Zig é uma das tarefas mais comuns na interoperabilidade e migração. Zig tem um sistema de ponteiros mais rico que C — com distinção entre ponteiros simples, muitos-itens, nullable, e com sentinela. Este guia cobre todas as conversões necessárias.

Para interoperabilidade geral, veja Interoperabilidade C-Zig. Para chamadas de funções C, consulte Chamar Funções C de Zig.

Pré-requisitos

Tipos de Ponteiros

Mapeamento C para Zig

CZigDescrição
int* (non-null)*i32Ponteiro para um elemento
int* (nullable)?*i32Ponteiro opcional
int* (array)[*]i32Ponteiro para muitos elementos
const int*[*]const i32Ponteiro constante para muitos
int[] (param)[]i32Slice (ptr + len)
const char*[*:0]const u8String C null-terminated
void**anyopaquePonteiro opaco
void* (nullable)?*anyopaquePonteiro opaco nullable

Ponteiro Simples

C

void incrementar(int* valor) {
    (*valor)++;
}

int x = 41;
incrementar(&x);
// x == 42

Zig

fn incrementar(valor: *i32) void {
    valor.* += 1;
}

var x: i32 = 41;
incrementar(&x);
// x == 42

O operador de dereference em Zig é .* (pós-fixo), em vez de * (pré-fixo) como em C.

Ponteiros para Arrays

C

void preencher(int* arr, size_t n) {
    for (size_t i = 0; i < n; i++) {
        arr[i] = (int)i;
    }
}

Zig com Slice (preferido)

fn preencher(arr: []i32) void {
    for (arr, 0..) |*item, i| {
        item.* = @intCast(i);
    }
}

Slices ([]T) são a forma idiomática em Zig — eles carregam o tamanho junto com o ponteiro, eliminando a necessidade de passar size_t n separadamente.

Zig com Many-Item Pointer (para interop C)

fn preencher(arr: [*]i32, n: usize) void {
    for (0..n) |i| {
        arr[i] = @intCast(i);
    }
}

Converter entre os dois

// Many-item pointer + length -> Slice
const slice: []i32 = arr[0..n];

// Slice -> Many-item pointer
const ptr: [*]i32 = slice.ptr;
const len: usize = slice.len;

Strings C (null-terminated)

C

size_t comprimento(const char* str) {
    return strlen(str);
}

Zig

// Receber string C null-terminated
fn comprimento(str: [*:0]const u8) usize {
    return std.mem.len(str);
}

// Converter para slice Zig (sem null terminator)
fn processar(str_c: [*:0]const u8) void {
    const str: []const u8 = std.mem.span(str_c);
    std.debug.print("Tamanho: {} - Conteúdo: {s}\n", .{ str.len, str });
}

// Converter slice Zig para string C (para chamar funções C)
fn chamarC(allocator: std.mem.Allocator, str_zig: []const u8) !void {
    const str_c = try allocator.dupeZ(u8, str_zig); // adiciona null terminator
    defer allocator.free(str_c);
    c.funcao_c(str_c.ptr);
}

void* (Ponteiro Opaco)

C

void* contexto = &meus_dados;
MeusDados* dados = (MeusDados*)contexto;

Zig

// Converter para anyopaque
const contexto: *anyopaque = @ptrCast(&meus_dados);

// Converter de volta
const dados: *MeusDados = @ptrCast(@alignCast(contexto));

Uso comum: callbacks com contexto

const c = @cImport(@cInclude("minha_lib.h"));

fn meuCallback(contexto: ?*anyopaque) callconv(.C) void {
    if (contexto) |ctx| {
        const dados: *MeusDados = @ptrCast(@alignCast(ctx));
        dados.processar();
    }
}

pub fn registrar(dados: *MeusDados) void {
    c.registrar_callback(meuCallback, @ptrCast(dados));
}

Ponteiros Nullable

C

// Em C, QUALQUER ponteiro pode ser NULL
int* buscar(int id) {
    if (id == 0) return NULL;
    // ...
}

int* resultado = buscar(42);
if (resultado != NULL) {
    printf("%d\n", *resultado);
}

Zig

// Em Zig, nullabilidade é explícita
fn buscar(id: i32) ?*i32 {
    if (id == 0) return null;
    // ...
}

if (buscar(42)) |resultado| {
    std.debug.print("{}\n", .{resultado.*});
} else {
    std.debug.print("Não encontrado\n", .{});
}

Aritmética de Ponteiros

C

int arr[10];
int* ptr = arr;
int* terceiro = ptr + 2;
ptrdiff_t diff = terceiro - ptr;

Zig

var arr: [10]i32 = undefined;
const ptr: [*]i32 = &arr;
const terceiro = ptr + 2;
// Aritmética de ponteiros é possível com [*]T

// Preferir slices:
const slice: []i32 = &arr;
const terceiro_valor = slice[2];

Conversão em Contexto de @cImport

Quando você usa @cImport, Zig traduz automaticamente os tipos C para tipos Zig:

const c = @cImport(@cInclude("sqlite3.h"));

pub fn exemplo() !void {
    var db: ?*c.sqlite3 = null;
    const rc = c.sqlite3_open("banco.db", &db);

    if (rc != c.SQLITE_OK) {
        if (db) |d| {
            const msg = c.sqlite3_errmsg(d);
            const msg_slice = std.mem.span(msg);
            std.debug.print("Erro: {s}\n", .{msg_slice});
        }
        return error.SqliteError;
    }
    defer _ = c.sqlite3_close(db);
}

Tabela de Conversões Comuns

DeParaMétodo
*T*anyopaque@ptrCast(ptr)
*anyopaque*T@ptrCast(@alignCast(ptr))
[*]T[]Tptr[0..len]
[]T[*]Tslice.ptr
[*:0]u8[]u8std.mem.span(ptr)
[]u8[*:0]u8allocator.dupeZ(u8, slice)
*T?*TImplícito
?*T*Topt_ptr orelse return error.NullPtr
*[N]T[]Tptr[0..] ou ptr (coerção)
usize*T@ptrFromInt(addr)
*Tusize@intFromPtr(ptr)

Conclusão

A conversão de ponteiros entre C e Zig é direta quando você entende o mapeamento de tipos. A regra geral: use slices ([]T) dentro de código Zig puro, e many-item pointers ([*]T) ou sentinel pointers ([*:0]T) na fronteira com C.

Para mais sobre interop C, veja Interoperabilidade C-Zig, Chamar Funções C de Zig e Portar uma Biblioteca C para Zig.

Continue aprendendo Zig

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