C99 introduced Flexible Array Members (FAM), which allow a structure to end with an array of unspecified size. This is a common pattern for "Packets" or "Messages" where a header is followed by a variable amount of payload data.

typedef struct {
    int count;
    double samples[]; // The size isn't known until runtime
} Signal;

Interfacing with these from a high-level language can be tricky because the sizeof the struct only includes the header, not the array.

The Recipe

We will build a "Dynamic Signal" processor. We will use a single malloc to create a structure large enough to hold our header AND our variable data, and then use Affix to treat it as a single object.

use v5.40;
use Affix qw[:all];

# 1. Define the Structure
# We use '?' to indicate a flexible array member.
typedef Signal => Struct[
    count   => Int,
    samples => Array[ Double, '?' ]
];

# 2. Allocation
# We want a Signal header + 5 doubles.
my $num_samples = 5;
my $total_size = sizeof( Int ) + ( sizeof( Double ) * $num_samples );

say "Allocating $total_size bytes for FAM struct...";
my $ptr = malloc( $total_size );

# 3. Mapping
# We cast the raw pointer to our Signal type.
# Affix understands that 'samples' is flexible and will 
# allow array access based on the memory we allocated.
my $sig = cast( $ptr, Pointer[ Signal() ] );

# 4. Initialization
$sig->{count} = $num_samples;

for (0 .. $num_samples - 1) {
    # Accessing the flexible array member naturally
    $sig->{samples}[$_] = $_ * 1.5;
}

# 5. Usage
say "Signal Header Count: " . $sig->{count};
say "Sample 3: " . $sig->{samples}[2];

# 6. Passing to C
# If we had a C function: void process_signal(Signal *s);
# we can just pass $sig!
# process_signal($sig);

# 7. Cleanup
free($ptr);

How It Works

Kitchen Reminders