¿Cómo usar @ptrToInt en punteros a funciones?

¿Cómo usar @ptrToInt en punteros a funciones?

En Zig, los punteros a función son tratados como punteros normales, pero su uso en ciertos contextos, como el despacho dinámico o la serialización, puede requerir convertirlos a un entero. El builtin @ptrToInt facilita esa conversión, convirtiendo cualquier puntero, incluido uno que apunte a una función, en un usize. Este artículo desglosa cómo funciona, cuándo y cómo aplicarlo de forma segura.

Entendiendo @ptrToInt

  • Qué hace: Toma un puntero y devuelve su dirección de memoria representada como usize.
  • Restricciones: El puntero debe ser válido y alineado adecuadamente. En Zig, @ptrToInt admite cualquier puntero, incluso punteros a funciones.
  • Usos comunes: Comparación de punteros, hashing, índices de tabla, serialización de direcciones, etc.

Funciones y punteros a función en Zig

Antes de convertir un puntero a función, veamos cómo se declara y se usa una función básica.

const std = @import("std");

/// Una función simple que suma dos enteros
fn add(a: i32, b: i32) i32 {
    return a + b;
}

pub fn main() void {
    // Llamar directamente a la función
    const result = add(2, 3);
    std.debug.print("Resultado: {}\n", .{result});
}

En el código anterior, add es una función cuyo nombre es un puntero al código que se ejecutará cuando se invoque.

Obtener un puntero a función

Para usar @ptrToInt, primero necesitamos un puntero explícito:

const std = @import("std");

fn add(a: i32, b: i32) i32 {
    return a + b;
}

pub fn main() void {
    // Declaramos un tipo de función con la misma firma que add
    const Func = fn (i32, i32) i32;

    // Asignamos la función a una variable de tipo Func
    var fn_ptr: *const Func = &add;

    // Podemos llamar a la función a través del puntero
    const result = fn_ptr(4, 5);
    std.debug.print("Resultado: {}\n", .{result});
}

Observa que el puntero fn_ptr tiene tipo *const Func, lo que indica que apunta a una función constante.

Convertir puntero a función a entero con @ptrToInt

Ahora veremos cómo usar @ptrToInt para transformar ese puntero en un entero.

const std = @import("std");

fn add(a: i32, b: i32) i32 {
    return a + b;
}

pub fn main() void {
    const Func = fn (i32, i32) i32;
    var fn_ptr: *const Func = &add;

    // Convertimos el puntero a función en un usize
    const ptr_as_int: usize = @ptrToInt(fn_ptr);

    std.debug.print("Dirección de add como entero: {x}\n", .{ptr_as_int});
}

En la línea donde se llama a @ptrToInt, el compilador verifica que fn_ptr sea un puntero válido y que el tipo de dato de destino sea usize. El resultado es la dirección de memoria donde está ubicada la función add.

Usar el entero como índice o tabla de despacho

Un caso típico de @ptrToInt es usar la dirección de una función como clave en una tabla hash o para construir un dispatch table basado en la identidad de la función. A continuación, se muestra cómo crear una tabla de funciones y acceder a ellas mediante su dirección convertida.

const std = @import("std");

/// Tres funciones auxiliares
fn inc(a: i32) i32 { return a + 1; }
fn dec(a: i32) i32 { return a - 1; }
fn dbl(a: i32) i32 { return a * 2; }

// Definimos el tipo de función con un parámetro y un retorno
const Func = fn (i32) i32;

// Estructura simple para almacenar la dirección y la función
const FuncEntry = struct {
    addr: usize,
    fn_ptr: *const Func,
};

pub fn main() void {
    // Creamos un array con nuestras funciones
    const funcs = [_]FuncEntry{
        .{ .addr = @ptrToInt(&inc), .fn_ptr = &inc },
        .{ .addr = @ptrToInt(&dec), .fn_ptr = &dec },
        .{ .addr = @ptrToInt(&dbl), .fn_ptr = &dbl },
    };

    // Supongamos que queremos ejecutar la función que corresponde a una dirección dada
    const target: usize = @ptrToInt(&dbl); // Imaginemos que lo conocemos de alguna fuente externa

    // Buscamos la entrada que coincida con la dirección
    for (funcs) |entry| {
        if (entry.addr == target) {
            // Ejecutamos la función encontrada
            const val = entry.fn_ptr(21);
            std.debug.print("Resultado de la función con dirección {x}: {}\n", .{entry.addr, val});
            return;
        }
    }

    std.debug.print("Función no encontrada\n", .{});
}

Este ejemplo demuestra cómo @ptrToInt permite usar direcciones de función como claves. En la práctica, podrías llenar la tabla a partir de un enumerado o de una configuración externa.

Precauciones y buenas prácticas

  • No confundir con comptime: @ptrToInt opera en tiempo de ejecución. Si necesitas la dirección en tiempo de compilación, usa @ptrToInt(comptime fn), pero solo dentro de funciones comptime.
  • Alineación: Asegúrate de que el puntero sea válido y esté alineado al tamaño de usize. Los punteros a funciones en Zig suelen cumplir con esta condición.
  • Portabilidad: La representación de una función como entero no es portable entre diferentes arquitecturas o ejecuciones. Evita escribir la dirección a disco si la intención es usarla en otra sesión.
  • Comparaciones: Se pueden comparar direcciones, pero no es recomendable usarlas como identificadores únicos en el mundo de la programación, ya que pueden cambiar entre ejecuciones.

Ejemplo completo: Despacho dinámico con @ptrToInt

const std = @import("std");

// Definimos el tipo de función
const Op = fn (i32, i32) i32;

// Funciones de operación
fn add(a: i32, b: i32) i32 { return a + b; }
fn sub(a: i32, b: i32) i32 { return a - b; }
fn mul(a: i32, b:

Comments

No comments yet. Why don’t you start the discussion?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *