The Delphi Bug List

Entry No.
148
コンパイラ - ヒントと警告
Delphi 2.0 と Delphi 3.0 には、コンパイラ指令子 $HINTS と $WARNINGS の "局所性" に関連するバグがある。これらの指令子は、$ALIGN のようなその他のローカルコンパイラ指令子とは異なるスコープを持つ
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 Exists Exists Exists Exists Exists Exists Exists Exists Exists Exists Exists Exists Exists Exists
解説
Reported by Hallvard Vassbotn; checked by Rune Moberg
以下の3つのファイルでこの問題を明らかにします:
LOCBUG.DPR:
program LocBug;
uses
  LocBugA;
begin
end.
LOCBUGA.PAS:
unit LocBugA;
interface
{$HINTS OFF} {$WARNINGS OFF} {$ALIGN OFF}
type
  TObjectA = class
  private
    UnusedA: integer;  // ヒントが出るべきではなく、実際出ない
  public
    procedure Destroy; // 警告が出るべきではなく、実際出ない
  end;
  TRecA = record       // SizeOf(TRecA) は 5 であるべきで、実際そうである
    A: char;
    L: longint;
  end;

implementation

uses
  LocBugB;
{.$HINTS OFF}      // バグを回避するにはドットを取り除く
{.$WARNINGS OFF}
type
  TObjectB = class
  private
    UnusedB: integer;  // ヒントが出るべきではないが、D3 では(時々)出る
  public
    procedure Destroy; // 警告が出るべきではないが、D3 では(時々)出る
  end;
  {.$ALIGN ON}    // ドットを取り除くと、下で無効なキャストになる
  TRecB = record  // SizeOf(TRecA) は 5 であるべきで、実際そうである
    A: char;
    L: longint;
  end;
procedure TObjectA.Destroy; begin end;
procedure TObjectB.Destroy;
var
  A: TRecA;
begin
  TRecB(A).L := 123; // SizeOf(TRecA) = SizeOf(TRecB) なので、これは正しい
end;
end.
LOCBUGB.PAS:
unit LocBugB;
interface
implementation
{$HINTS ON} {$WARNINGS ON} {$ALIGN ON}
end.
問題は、コンパイラがユニット LocBugB を再コンパイルするとき、常に HINTS と WARNINGS をオンにし、このユニットが他のユニットで使用されている場所以降で有効である続けることです。コンパイラ指令子 ALIGN ではこの現象は発生しません。上のコメントの"(時々)"というのは、LocBugB が再コンパイルされるときは常に、ということに言及しているのです。

この意味するところは、あるユニットで HINTS と WARNINGS を確実にオフにするには、interface セクションの uses 節の後と、さらに implementation セクションの uses 節の後の両方に、{$HINTS OFF} と {$WARNINGS OFF} を加えなければならないということです。

このバグは無害のように見えますが、以前に私が書いた the very dangerous hints-bug (これは D3 を終了しなければならないアクセス違反を起こします)と組み合わされると、この新しいバグはとても面倒です。以前のバグに対する回避方法のひとつでは、全てのコードにおいてヒントをオフにしました。この新しいバグは、これが単純なものではないということを意味しています。
解決策 / 回避方法
あるユニットで HINTS と WARNINGS を確実にオフにするには、interface セクションの uses 節の後と、さらに implementation セクションの uses 節の後の両方に、{$HINTS OFF} と {$WARNINGS OFF} を加える必要があります。

関連する"バグ"あるいは欠点

しばしばユニット内の小さい範囲でヒントや警告をオフにする必要があります。理想的には、これは HINTS の初期状態の影響を受けることなく行われるべきです。これを実現するためにコンパイラ指令子 $IFOPT が利用できると思うかもしれませんが、それは無理です。コンパイラ指令子 $IFOPT は単一文字のコンパイラ指令子のテストだけをサポートしています。HINTS と WARNINGS には単一文字のものは用意されていないのです。

これは以下のようには書けないということを意味しています:
{$IFDEF _HINTS_ON_} Error: Define name clash! {$ENDIF}
{$IFOPT HINTS ON} {$HINTS OFF} {$DEFINE _HINTS_ON_} {$ENDIF}
...
{ヒント不要のコードをここに書く}
{$IFDEF _HINTS_ON_} {$HINTS ON} {$UNDEF _HINTS_ON_} {$ENDIF}
これは明らかに醜いコードですが、TP/BP の頃にはこの方法でローカルのコンパイラ指令子を扱っていました。この状況を避けるために Borland が {$PUSHOPT} {$POPOPT} の組を追加してくれていれば、とても役に立ったことでしょう。

もう1つの重要な欠点

最初の AV を生成するヒントのバグに対して私が提案したもう1つの回避方法は、DCC32.EXE を使ってプロジェクトをコンパイルするという方法です。確かにこれは可能なのですが、うまく動かすためにはとても複雑で間違いやすい手作業が必要です。

まず最初に、Delphi IDE はとてもうまくプロジェクトを管理し、全てのオプションを .DOF ファイルに保持します。いったいどうして DCC32 はそれを利用できないのでしょうか?代わりに DCC32 は、全てのオプション情報を複製した DCC32.CFG ファイルをプロジェクトディレクトリ内に置く(これは1つのディレクトリに複数のプロジェクトを入れることを不可能にしています)か、全てをコマンドラインで与えることを要求します。

次に、ツールメニューから DCC32 を開始するのも面倒です。エラーメッセージなどを見るには、バッチファイルを実行する必要があります。バッチファイルを実行するには、cmd/command に引数として /C DCC32.BAT を追加して実行する必要があります。そしてプロジェクトファイルの名前を与えるために、$NAME($EXENAME) を追加する必要もあります。
Latest update of this entry: 2002-04-03
本家 The Delphi Bug List のエントリーはこちら
The Delphi Bug List 日本語訳 へ