The Delphi Bug List

Entry No.
623
Compiler - Code generation
The new record alignment introduced in Delphi 5 seems to be flawed
1.02 2.01 3.0 3.01 3.02 4.0 4.01 4.02 4.03 5.0 5.01 6.0 6.01 6.02 Kylix 1.0
N/AN/AN/AN/AN/AN/AN/AN/AN/AExistsExistsExistsExistsExistsExists
Description
Reported by Thomas Schulz in newsgroup; checked by Jordan Russell
Here's the example (just drop a memo on a form and insert this code):
type
  ttest1 = record
    e1: extended;
    e2: extended;
  end;

  ttest2 = record
    e1, e2: extended; // this is the only difference!
  end;

procedure TForm1.FormActivate(Sender: TObject);
var
  test1: ttest1;
  test2: ttest2;
begin
  Memo1.Clear;
  with Memo1.Lines do
  begin
    Add(' test1:');
    Add(' sizeof(test1): '+inttostr(sizeof(test1)));
    Add(' longword(@test1.e1): '+inttostr(longword(@test1.e1)));
    Add(' longword(@test1.e2): '+inttostr(longword(@test1.e2)));
    Add('');
    Add(' test2:');
    Add(' sizeof(test2): '+inttostr(sizeof(test2)));
    Add(' longword(@test2.e1): '+inttostr(longword(@test2.e1)));
    Add(' longword(@test2.e2): '+inttostr(longword(@test2.e2)));
    Add('');
    Add('Why this difference in alignment?');
  end;
end;
Solution / workaround
  1. Use 'packed record' instead to avoid all alignment issues. (Packed records are stored consistently throughout all Delphi versions.)
  2. Declare all variables inside unpacked records separately; do not use commas.
User-contributed comments
Christopher Burke
31 Aug 2001  01:20 AM GMT
Hasn't Delphi always allocated
A,B:integer;
as ONE block and
A:integer;
B:integer;
as TWO blocks ?

And therefore this isn't really a bug ?
Jordan Russell
06 Sep 2001  07:54 PM GMT
It has always been my understanding that "A, B: Type" should be equivalent to "A: Type; B: Type". The former is just a shorthand notation for the latter. They shouldn't function differently.
Christian NineBerry Schwarz
06 Sep 2001  08:37 PM GMT
There have always been differences:

var
  A:    array[1..10] of byte;
  B:    array[1..10] of byte;
  C, D: array[1..10] of byte;
begin
  C:= D; // Compiles fine
  A:= B; // Type mismatch from Turbo Pascal 1 to Delphi 6
end;
Jordan Russell
09 Sep 2001  03:51 AM GMT
Ah, that's true. But that's because the compiler sees it as:

var 
  A: Type1; 
  B: Type2; 
  C, D: Type3;

Each time you declare a variable without using a pre-defined type, the compiler internally allocates a new type. That's why A and B aren't assignment compatible.
Arsène von Wyss
09 Oct 2001  11:23 PM GMT
I don't see why this would be a bug, at most I'd call it a gotcha. Only when a record is declared as "packed" you can assume what structure the record will exactly have. Every other declaration leaves it to the compiler to do whatever it wants with the fields as long as they remain available, and this includes aligning.

While it is certainly strange that the compiler does not use the same alignment in these situations, the compiler pretty well does align the way one could assume is useful. For instance, if I declare:
type
  Test1=record
    a,b,c,d: Byte;
  end;

it optically looks as if these bytes form one group. However, if I declare it like this:
type
  Test2=record
    a: Byte;
    b: Byte;
    c: Byte;
    d: Byte;
  end;

then it looks much more like four separated byte entries. It seems to me that a programmer will usually choose the "style" which represents what he is thinking about, and therefore I don't see any wrongdoing of the compiler if it aligns comma-separated values other than line-separated ones.
Marcelo González Bergweiler
19 Nov 2001  05:49 PM GMT
This is certainly a bug if you use records with variant parts.
Consider the following code:

type  tV2 = record case byte of
         1: (x,y: extended); {BUG bypassed}
{        1: (x: real;
             y: real);       {BUG if not PACKED}
         2: (u: array[1..2] of extended); end;

      tV4 = record case integer of
         1: (v: array[1..4] of extended);
         2: (n: array[1..2] of tV2); end; {BUG if not PACKED}


procedure TForm1.FormActivate(Sender: TObject);
var v4 : tV4;
    v2 : tV2;
begin
  Memo.Clear;
  with Memo.Lines do begin
    Add(' OK if comma-separated:');
    Add(' sizeof(v2): '+inttostr(sizeof(v2)));
    Add(' longword(@v2.x): '+inttostr(longword(@v2.x)));
    Add(' longword(@v2.y): '+inttostr(longword(@v2.y)));
    Add('');
    Add(' longword(@v2.u[1]): '+inttostr(longword(@v2.u[1])));
    Add(' longword(@v2.u[2]): '+inttostr(longword(@v2.u[2])));
    Add('');
    Add(' BUG: n[2]<>v[3]');
    Add(' sizeof(v4): '+inttostr(sizeof(v4)));
    Add(' longword(@v4.n[1]): '+inttostr(longword(@v4.n[1])));
    Add(' longword(@v4.n[2]): '+inttostr(longword(@v4.n[2])));
    Add('');
    Add(' longword(@v4.v[1]): '+inttostr(longword(@v4.v[1])));
    Add(' longword(@v4.v[3]): '+inttostr(longword(@v4.v[3])));
    Add(''); end;
end;


The variant parts n[2].u[1] and v[3] in v4 do not allign properly.
This is certainly not correct. The same problem happens if REAL48 (6 bytes) is used instead of extended (10 bytes).
Also I do not agree that declaring records separately (by not using commas)
is good. The opposite is better in this case (see v2 variable).
My suggestion for a workaround is to use the {$ALIGN OFF} switch once on top of the source code and wait until the bug is corected by Borland. Otherwise you have to use PACKED records on every record with variants you define.
My Delphi is version 6.01.
Arsène von Wyss
29 Nov 2001  10:53 PM GMT
The compiler is allowed to align data freely as long as the record is not marked as packed. How the data is aligned does not matter in normal applications, even if a variant record is used without the intention to use it the dirty way (that is, to write some data on a variant and read data using another one).

The problem only arises when you, as a programmer, assume a certain data alignment. However, the alignment may (and does) change with the different Delphi versions, and therfore the only correct solution is to pack these records. If, and only if, they are declared as packed, you have the knowledge over the used alignment, and it will be correct. Therefore I still don't see any *bug* in the compiler, even if the behaviour is unexpected. If your code does not work because you didn't declare a record it as packed, its *your* bug, not a compiler one.
bin
30 Jan 2003  04:46 PM GMT
Well I haven't tried none of the examples, but if they really work as they are said, then I think this is a bug. Using variant records "the dirty way" is a very normal programming technique, and if you dig for a while in the sources of the delphi libraries you could see this. The thing is that a packed record was used, and the reason was that it was an imported struct from a C header file. The question is whether they would use packed if it wasnt...
Latest update of this entry: 2002-02-28

Post a comment on this bug


Index page
Delphi Bug List home page
The Delphi Bug Lists are presently maintained by Jordan Russell, who has taken over this task from Reinier Sterkenburg since August 2000.
All feedback is appreciated. See also the feedback section of the Delphi Bug List home page.