Pewarta Proxy

📋 Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

⚙️ Pengaturan Proxy Target

Flexible Array Members

Declare a record with a variable-length tail: data: array[] of T as the last field (the C99 flexible-array-member layout). The record header has a fixed size, the tail extends as far as the allocation says it does, and sizeof(rec) reports only the fixed part.

Modeswitch: flexiblearrays, enabled by default in {$mode unleashed}. Elsewhere:

{$mode objfpc}
{$modeswitch flexiblearrays}

What it does

A flexible array member (FAM) is the last field of a record, declared with empty brackets and no bound:

type
  PMessage = ^TMessage;
  TMessage = packed record
    code: integer;
    len:  integer;
    data: array[] of byte; // flexible array member
  end;

The record has the same memory layout as one ending after len, plus whatever payload you allocate behind it. There is no separate buffer, no pointer chase, no managed lifetime. The compiler does not track the runtime length, so FAM indexing skips both compile-time and runtime range checks even under {$rangechecks on}.

Allocation and use

The record and its tail live in one block: allocate fixed part plus payload with a single GetMem(), write the tail through the FAM field by index, free the whole thing with one FreeMem():

var msg: PMessage;
begin
  GetMem(msg, sizeof(TMessage)+1024);
  msg^.code := 42;
  msg^.len := 1024;
  for var i := 0 to 1023 do
    msg^.data[i] := byte(i);
  // ... use msg ...
  FreeMem(msg);
end;

sizeof(TMessage) returns 8 here (just code and len). The FAM contributes nothing to the static size, so the allocation math is the obvious sizeof(rec) + payload - no off-by-one for a phantom one-element tail.

Memory layout

            +----------+----------+----------+ ... +----------+
GetMem ---> | code (4) | len (4)  | data[0]  |     | data[N]  |
            +----------+----------+----------+ ... +----------+
            ^                     ^
            msg                   msg^.data
            |<-- sizeof(rec) -->|<---- payload bytes ---->|

The FAM starts at the offset natural alignment gives the element type after the last fixed field. For array[] of int64 after a 4-byte field on a 64-bit target, the compiler inserts the usual 4 padding bytes before the FAM, exactly as for any other field.

Why a FAM and not the alternatives

Three patterns are common today; each loses something the FAM keeps:

PatternProblem
data: array[0..0] of byte (the "struct hack", ANYSIZE_ARRAY){$rangechecks on} rejects every access past index 0; sizeof() is one element too large; the padding story is implicit
data: PByte to a separate buffertwo allocations, two frees, an extra indirection per access, and the single-block layout Win32 structures demand is gone
case integer of 0: (data: array[0..high(integer)-X] of byte)sizeof() rolls over, high() lies, and the compiler has no idea of the actual extent

The FAM gives the inline layout, honest sizeof(), a working {$R+} for the rest of the program, and a single allocation - in one feature.

Restrictions

Enforced at parse time, each with a dedicated diagnostic:

  1. The FAM must be the last field. Flexible array members are only allowed as the last field of a record.
  2. At least one fixed field must precede it. A record with a flexible array member must have at least one other field.
  3. One FAM per record, one identifier per FAM declaration. one, two: array[] of byte is rejected like a second FAM would be.
  4. Plain record instance fields only. Not in a class, object, variant part, class var, or threadvar.
  5. A FAM-record cannot be embedded in another structured type - use a pointer field instead.
  6. A FAM-record cannot be an array element type - the per-element size would be undefined.
  7. A FAM-record cannot be a stand-alone variable, value parameter, or function result. Variable of type "T" with flexible array member must be allocated dynamically (use a pointer).

Reference parameters (var, const, constref, out) of FAM-record type stay legal - they pass an address, no copy involved. Pointer-to-FAM-record (PFamRec) is unrestricted: field of any type, array element, any parameter kind, function result.

Use cases

Comparison with array of T

A FAM is not a dynamic array; they share no runtime machinery:

array of T (dynamic)array[] of T (FAM)
Storageseparate heap block via a managed pointerinline tail of the record, one block with the header
Lifetimereference-counted, automaticmanual, freed with the containing block
SetLength()resizesnot applicable - size fixed by the GetMem()
Length(x)element count0 (the static length); track the real count in a fixed field
Range checkingruntime checknone
Overhead per recordpointer + refcount blockzero

Want a managed, resizable array living elsewhere in memory - use array of T. Want a fixed-shape tail inline behind the header - use a FAM.

Debugger view

A FAM has no statically known length, so without help a debugger shows an empty array. The compiler emits a DWARF upper-bound expression that reads the element count at runtime from a sibling ordinal field; fpdebug and gdb evaluate it on every refresh and pretty-print the FAM with the right count.

By default the last ordinal field declared before the FAM is picked automatically - the typical "header + count + payload" layout needs no annotation:

type
  PTokenPrivileges = ^TTokenPrivileges;
  TTokenPrivileges = packed record
    PrivilegeCount: DWORD;
    Privileges:     array[] of LUID_AND_ATTRIBUTES; // auto-binds PrivilegeCount
  end;

If the count is not the last ordinal field before the FAM, bind it explicitly with the count clause:

type
  TBatch = packed record
    count:    DWORD;
    reserved: DWORD;
    items:    array[] of TItem count count;
  end;

The named field must be a field of the same record, declared before the FAM, of an ordinal type sized 1 / 2 / 4 / 8 bytes. Misuse diagnostics:

This is purely a debug-info detail: no runtime cost, no layout change, no effect on sizeof() or indexing, and range checks stay off for FAM accesses either way. DWARF only (-gw2 and -gw3); CodeView and Stabs are unaffected.

PPU streams

The FAM flag (ado_IsFlexibleArray) is part of arrayoptions, which PPU files already stream. A FAM-record declared in one unit behaves identically when used from another - same sizeof(), same disabled range check.

Limitations

Demo

program fam_demo;

{$mode unleashed}
{$rangechecks on}

type
  PMessage = ^TMessage;
  TMessage = packed record
    code: integer;
    len: integer;
    data: array[] of byte; // flexible array member
  end;

function newMessage(code: integer; const payload: array of byte): PMessage;
begin
  GetMem(result, sizeof(TMessage)+length(payload));
  result^.code := code;
  result^.len := length(payload);
  for var i := 0 to high(payload) do result^.data[i] := payload[i];
end;

procedure dump(msg: PMessage);
begin
  write($'code={msg^.code} len={msg^.len} bytes:');
  // FAM indexing is range-check-free even under rangechecks on
  for var i := 0 to msg^.len-1 do write(' $', HexStr(msg^.data[i], 2));
  writeln;
end;

begin
  writeln($'sizeof(TMessage) = {sizeof(TMessage)} (fixed part only)');
  var msg := newMessage(7, [$DE, $AD, $BE, $EF]);
  writeln($'length(msg^.data) = {length(msg^.data)} (static length, not payload)');
  dump(msg);
  FreeMem(msg);
  {$ifdef WINDOWS}readln;{$endif}
end.

Output:

sizeof(TMessage) = 8 (fixed part only)
length(msg^.data) = 0 (static length, not payload)
code=7 len=4 bytes: $DE $AD $BE $EF