The Delphi Bug List

Entry No.
623
コンパイラ - コード生成
Delphi 5 で導入された新しいrecord型のアラインメントは、間違っているように見える
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/A N/A N/A N/A N/A N/A N/A N/A N/A Exists Exists Exists Exists Exists Exists
解説
Reported by Thomas Schulz in newsgroup; checked by Jordan Russell
以下、サンプルです(フォームにmemoを置き、このコードを追加して下さい)。
type
  ttest1 = record
    e1: extended;
    e2: extended;
  end;

  ttest2 = record
    e1, e2: extended; // ここが唯一の違いです!
  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('なぜアラインメントに違いがあるのか?');
  end;
end;
解決策 / 回避方法
  1. アラインメントにまつわる全ての問題を回避する為に、'packed record'を使用して下さい(packed recordはDelphiの全てのバージョンにおいて、連続して保存されます)。
  2. packedではないレコードの変数は、カンマを使わず、全て個別に宣言して下さい。
ユーザーからのコメント
Christopher Burke
31 Aug 2001  01:20 AM GMT
Delphi は通常、
A,B:integer;
をひとつのブロックとして割り当て、
A:integer;
B:integer;
を二つのブロックとして割り当てるのでは?

従ってこれは、実際はバグではないのではないでしょうか?
Jordan Russell
06 Sep 2001  07:54 PM GMT
私はこれまで、"A, B: Type" は "A: Type; B: Type" と同じだと理解して来ました。前者は後者の単なる省略形であって、機能的に異なるべきではないと思います。
Christian NineBerry Schwarz
06 Sep 2001  08:37 PM GMT
違いはあります。

var
  A:    array[1..10] of byte;
  B:    array[1..10] of byte;
  C, D: array[1..10] of byte;
begin
  C:= D; // 正常にコンパイルされる
  A:= B; // Turbo Pascal 1 から Delphi 6 まで、これは型が異なるとされる
end;
Jordan Russell
09 Sep 2001  03:51 AM GMT
確かにそうですが、それはコンパイラがそれを、
var 
  A: Type1; 
  B: Type2; 
  C, D: Type3;
と見なすからです。
事前に定義された型を使用せずに変数を宣言すると、そのたびにコンパイラは内部的に新しい型を割り当てます。これが、AとBに互換性がないとされる理由です。
Arsene von Wyss
09 Oct 2001  11:23 PM GMT
私はなぜこれがバグとされるのか分かりません。せいぜい混乱の元(gotcha)でしょう。レコードがどのような構造を持っているのかが正確に分かると見なせるのは、そのレコードが"packed"として宣言されている場合だけでしょう。それ以外の宣言は、フィールドが有効である限り、コンパイラの好きなように任せます。これにはアラインメントも含まれます。

これらの状況でコンパイラが同じアラインメントを行わないのは確かに奇妙ですが、コンパイラは良かれと思う方法でアラインメントを行います。例えば以下のような宣言、
type
  Test1=record
    a,b,c,d: Byte;
  end;

これは、これらのバイトが1つのグループになるように見えます。しかしながら、これを以下のように宣言すると、
type
  Test2=record
    a: Byte;
    b: Byte;
    c: Byte;
    d: Byte;
  end;

これは、四つ別々のバイト型エントリーのように見えます。プログラマは通常、自分が考えていることを表している方の"スタイル"を選択するでしょう。ですから、コンパイラが行区切りではなくカンマ区切りを選んだとしても、それが悪いことだとは思えません。
Marcelo Gonzarez Bergweiler
19 Nov 2001  05:49 PM GMT
可変部分を持つレコードを使う場合、これは確かにバグです。
以下のコードに付いて考えてください:

type  tV2 = record case byte of
         1: (x,y: extended); {バグは回避される}
{        1: (x: real;
             y: real);       {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; {PACKEDでなければバグになる}


procedure TForm1.FormActivate(Sender: TObject);
var v4 : tV4;
    v2 : tV2;
begin
  Memo.Clear;
  with Memo.Lines do begin
    Add(' OK カンマ区切りの場合:');
    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;

v4の可変部分n[2].u[1]とv[3]は、適切にアラインされません。
これは確かに間違いです。同じ問題は、extended(10バイト)の代わりにREAL48(6バイト)を使用した場合にも発生します。
レコードを個別に(カンマを使わずに)宣言するのが良いという意見には賛成出来ません。上のプログラムの場合には、もう一方の方法がより適切です(変数v2を見てください)。
私の提案する回避方法は、ソースコードの頭で一度だけスイッチ{$ALIGN OFF}を使用し、Borlandがこのバグを修正するまで待つ、というものです。さもなければ、可変部分を持つ全てのレコードにPACKEDレコードを使用する必要があります。
私のDelphiはバージョン6.01です。
Arsene von Wyss
29 Nov 2001  10:53 PM GMT
レコードがpackedではない限り、コンパイラはデータを自由にアラインすることが許されています。データがどのようにアラインされるかは、たとえ汚い方法で使用すること(つまり、ある可変部分にデータを書き込み、それを別の可変部分を使って読み込むこと)を意図せずに可変レコードを使用したとしても、通常のアプリケーションでは問題になりません。

この問題は、(プログラマとしての)あなたが、ある特定のアラインメントを前提とした場合にだけ起こります。しかしながら、アラインメントはDelphiの異なるバージョンで違うかもしれません(実際違います)。ゆえに唯一の正しい解決策は、それらのレコードをpackedにすることです。packedとして宣言されている場合(その場合にのみ)、あなたは使用されているアラインメントに関する知識を持ち、それは正しいでしょう。ゆえに、たとえその振る舞いが予想外だとしても、私はコンパイラのバグだとは思えません。レコードをpackedとして宣言しない事であなたのコードが正常に動作しないのであれば、それはあなたのバグであって、コンパイラのバグではありません。
bin
30 Jan 2003  04:46 PM GMT
サンプルのコードはどれも実行していませんが、本当に彼らの言う通りに動作するのなら、私はこれはバグだと思います。可変レコードを"汚い方法"で使用することはごく一般的なプログラミングテクニックであって、Delhpiのライブラリのソースをしばらく探ってみれば、それを見つけることが出来ます。それにはpacked recordが使用されており、その理由はそれがC言語のヘッダファイルからインポートされた構造体だからです。問題は、もしそれにpackedが無かった場合に、packedを使用するのかどうかという事ですが……
Latest update of this entry: 2002-02-28
本家 The Delphi Bug List のエントリーはこちら
The Delphi Bug List 日本語訳 へ