On the Delphi documentation issues

4

Delphi XE2 documentation states:

‘Packed’ Now Forces Byte Alignment of Records

If you have legacy code that uses the packed record type and you want to link with an external DLL or with C++, you need to remove the word “packed” from your code. The packed keyword now forces byte alignment, whereas in the past it did not necessarily do this. The behavior change is related to C++ alignment compatibility changes in Delphi 2009.

The above cannot be classified even as a documentation bug; it is just a nonsense. There was a related StackOverflow question. The question remained unanswered because it is impossible to answer it reasonably. Now more than a month passed, and the documentation issue is still there – the documentation writers definitely don’t read SO.

The best reading about Delphi alignment rules that I know is the Barry Kelly answer on SO.

Let us try to analyze what the documentation is about.

  • The behavior change is related to C++ alignment compatibility changes in Delphi 2009
  • Delphi 2009 fixed a bug that caused different layout for

    type
      TRec = record
        A, B: Extended;
      end;
    

    and

    type
      TRec = record
        A: Extended;
        B: Extended;
      end;
    

    records in the previous versions, and introduced {$OLDTYPELAYOUT ON} directive to reproduce the alignment bug for compatibility reasons.

    Hard to explain how that is related to ‘Packed’ Now Forces Byte Alignment of Records.

  • The packed keyword now forces byte alignment, whereas in the past it did not necessarily do this
  • Seems like nobody can say now what the past the documentation writer is talking about. Maybe ages ago first Turbo Pascal versions treated external type alignment and internal record layout differently. Or maybe where was some bug in packed specifier implementation that nobody was ever aware of, and the bug was fixed ages ago – one can only guess.

  • If you have legacy code that uses the packed record type and you want to link with an external DLL or with C++, you need to remove the word “packed” from your code
  • If so, what is the Delphi equivalent of #pragma pack(1) in C/C++? Maybe the documentation writer suggests to use {$A1} or {$A-} directives instead of packed specifier? But {$A1}, {$A-} directives and packed specifier do exactly the same thing – force byte alignment. There is no difference here. Or maybe #pragma pack(1) in C/C++ affects only internal structure layout and not structure alignment?

    Just to be sure I have tested the following code sample in Visual Studio 2010:

    struct S {
       short j;    // size 2
       double k;   // size 8
    };
    
    #pragma pack(1)	// force byte alignment
    struct T {
       short j;
       double k;
    };
    
    #pragma pack() // restore default alignment
    struct S1{
        char cc;       // size 1
        S ss;          // size 16
    };
    
    struct T1{
        char cc;       // size 1
        T tt;          // size 10
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
       printf("%d %d\n", sizeof(S), offsetof(S, k));        // 16 8
       printf("%d %d\n", sizeof(T), offsetof(T, k));        // 10 2
    
       printf("%d %d\n", sizeof(S1), offsetof(S1, ss));     // 24 8
       printf("%d %d\n", sizeof(T1), offsetof(T1, tt));     // 11 1
    
       _getch();
       return 0;
    }
    

    As you can see #pragma pack(1) forces byte alignment for both T structure fields and T structure as a whole, exactly the same thing as packed specifier in Delphi do.

    So what the documentation writer is talking about ?

    On the type compatibility in Delphi

    4

    Delphi compiler evolves much faster than Delphi documentation, and some language features remain unnamed. Consider the following code snippet:

    type
      MyPChar1 = PChar;
      MyPChar2 = type PChar;
      MyPChar3 = ^Char;
    
    procedure Test(Ch: PChar);
    begin
    end;
    
    procedure TForm1.Button4Click(Sender: TObject);
    var
      Ch1: MyPChar1;
      Ch2: MyPChar2;
      Ch3: MyPChar3;
    
    begin
      Test(Ch1);
    //  Test(Ch2);     Error E2008 Incompatible types
    //  Test(Ch3);     Error E2010 Incompatible types: 'Unit1.Char' and 'System.Char'
    end;
    

    The documentation states that MyPChar1 and PChar are identical types, while MyPChar2 and MyPChar3 are distinct; that is why Test(Ch2); and Test(Ch3); lines does not compile. But notice – the compiler issues different error codes. Does it matter?

    Consider the next snippet:

    procedure Test1(Ch: MyPChar1);
    begin
    end;
    
    procedure Test2(Ch: MyPChar2);
    begin
    end;
    
    procedure Test3(Ch: MyPChar3);
    begin
    end;
    
    procedure TForm1.Button5Click(Sender: TObject);
    begin
      Test1('Foo');
      Test2('Foo');   // Compiles
    //  Test3('Foo');    Error E2010 Incompatible types: 'MyPChar3' and 'string'
    end;
    

    The documentation states that PChar type is assignment compatible on input with string literal. It appears that there is a difference in type ‘distinctness’. The type PChar type is somewhat more compatible with PChar than ^Char type.