arraysdelphialignmentpacked

Are there any difference between array and packed array in Delphi?


In C/C++ you always have

SizeOf(array[N] of T) = N * SizeOf(T);

In Pascal/Delphi you can use 'packed array' to be sure that the above assert is true, but does 'packed' specifier have any practical value for arrays in Delphi? I can't create an example of 'unpacked' array, the arrays seems always 'packed':

type
  A = array[0..2] of Byte;
  B = array[0..99] of A;
  C = packed record
    C1, C2, C3: Byte;
  end;
  D = array[0..99] of C;

procedure TForm10.Button1Click(Sender: TObject);
begin
  Assert(SizeOf(A) = 3);
  Assert(SizeOf(B) = 300);
  Assert(SizeOf(D) = 300);
end;

(The C/C++ structures and Delphi records are different - they can be 'unpacked' so that the size of the structure is greater than the sum of fields' sizes due to the fields' alignment.)


Solution

  • It has no practical effect in Delphi. The only type it could reasonably affect is the type with the oddest alignment and size combination, Extended, which has a size of 10 and an alignment of 8. However, arrays of Extended are essentially packed already (though they still have an alignment of 8; if the packed directive worked like it did on records, they would have an alignment of 1).

    Why do I say arrays of Extended is the only type it could affect? There is no other Delphi type, built-in or that you can compose, which has a size that is not an integer multiple of its alignment (leaving aside older versions of Delphi, and some bugs). Alignment is the thing that makes records larger with padding; it causes fields to be spaced out so that every field starts at an offset which is an integer multiple of its type's alignment. In the analogous case with arrays, there is only one type involved, and if the size is already a multiple of the type's alignment, then there's no need for padding.

    Here's a program which shows how Extended affects size and alignment depending on whether it's wrapped in a record or not; you can add packed to the arrays, and see it makes no difference:

    type
      TWrap = record
        X: Extended;
      end; // field size=10, align=8, => actual size=16
    
      TArr1 = array[1..3] of TWrap; // 3*16 => size=48, align=8
      TArr2 = array[1..3] of Extended; // 3 * 10 => size=30, align=8
    
      TRec1 = record
        A: Byte;
        B: TArr1;
      end;
    
      TRec2 = record
        A: Byte;
        B: TArr2;
      end;
    
    var
      x: TRec1;
      y: TRec2;
    begin
      Writeln('Size of TArr1: ', SizeOf(TArr1));
      Writeln('Alignment of TArr1: ', Integer(@x.B) - Integer(@x.A));
      Writeln('Size of TArr2: ', SizeOf(TArr2));
      Writeln('Alignment of TArr2: ', Integer(@y.B) - Integer(@y.A));
    end.
    

    More words about alignment and packed: packed has another effect (on records) rather than just guaranteeing that there is no padding added: it also marks the record as having itself an alignment of 1. This has the negative effect of causing it to be frequently misaligned when it is used elsewhere. For purposes of language / OS interoperability, only in the case where the other language is not using OS alignment rules (normally meaning C alignment rules) should the packed directive be used. (Some Windows API headers have incorrect alignment for types defined within them, mind you, and have had to live with it ever since.) For purposes of compatibility with a file format, on the other hand, packed may be justified, but there are lots of other concerns there too, with respect to type choice (e.g. Integer was 2 bytes in 16-bit Delphi, but 4 bytes subsequently).

    Delphi tries to use C-compatible rules for alignment. In the past, it had some bugs here (particularly with records like TRec = record A, B: Extended end; versus TRec = record A: Extended; B: Extended end;), but these bugs should be fixed now