Sometimes you don't want to call a C function; you just want to use C's knowledge of memory layout. If you are parsing a binary file format or a network protocol defined in a C header, you could manually calculate offsets and padding, but that is a recipe for "off-by-one" disasters.
Affix allows you to use its internal JIT compiler to perform runtime introspection on types, giving you the exact sizeof and offsetof values for the current CPU architecture.
The Recipe
We will define a complex, padded C struct and use Affix to write a "pure Perl" binary parser that respects C's alignment rules.
use v5.40;
use Affix qw[:all];
# 1. Define the structure in the global registry
typedef Header => Struct [
magic => Array [ Char, 4 ],
version => UInt16,
_pad => UInt16, # Manual padding is often needed in binary formats
flags => UInt32,
offset => UInt64
];
# 2. Introspect the layout
my $size = sizeof Header();
my $v_off = offsetof Header(), 'version';
my $f_off = offsetof Header(), 'flags';
say "Total Struct Size: $size bytes";
say 'Version field starts at byte: ' . $v_off;
say 'Flags field starts at byte: ' . $f_off;
# 3. Use this info to parse raw data (e.g. from a file)
# Build a 24-byte buffer that matches the aligned layout.
# Note: the UInt64 'offset' field is aligned to an 8-byte boundary, so it
# actually starts at byte 16 (not byte 12) even though the preceding fields
# only take up 12 bytes.
my $raw_data = pack 'a4vvVx4Q', 'AFFX', 1, 0, 0xFF, 0x1234;
# Copy the raw bytes into a heap buffer, then plate the struct over it
my $buf = calloc 1, sizeof Header();
memcpy $buf, $raw_data, length $raw_data;
my $header = cast $buf, Header();
if ( $header->{magic} eq 'AFFX' ) {
say 'Valid header found!';
say sprintf 'Flags: 0x%X', $header->{flags};
say sprintf 'Offset: 0x%X', $header->{offset};
}
The output looks like this:
Total Struct Size: 24 bytes
Version field starts at byte: 4
Flags field starts at byte: 8
Valid header found!
Flags: 0xFF
Offset: 0x1234
How It Works
- Compiler-Grade Accuracy
When you call
sizeoforoffsetof, Affix isn't guessing. It uses theinfixengine to perform a full mock-layout of the structure according to the System V or Windows ABI. It accounts for tail-padding and member alignment automatically. - Zero-Header Parsing
Affix can't "plate" a struct directly over a Perl string buffer (the string is not addressable, writable C memory). Instead, you
calloca heap buffer the size of your struct,memcpyyour raw bytes into it, and thencastthat buffer to the struct type. Accessing$header->{flags}performs a raw memory read at the exact offset calculated during introspection — and since the view is zero-copy, writes through$headergo straight back into the buffer.
Kitchen Reminders
- Packing Rules
Standard C structs often have "hidden" padding. For example, a
charfollowed by anintwill usually have 3 bytes of invisible padding between them so theintstarts on a 4-byte boundary.offsetofis the only safe way to find where thatintactually lives.
Comments