C99 introduced flexible array members, which allow structure to end with an array of unspecified size. This is a common pattern for packets of data 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 demonstration of a 'dynamic signal processor' using 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( Signal() ) + ( 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, Signal() );

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

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

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

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

# Cleanup
free($ptr);

How It Works

Kitchen Reminders