5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Delphi] 生成時に消えて無くなるコードの書き方

Last updated at Posted at 2022-04-25

コンパイル時にコードを生成したり抑制したい事は良くあります。
そのため多くの高級言語に条件付きコンパイルという仕組みがあります。

条件付きコンパイル

Object Pascal の条件付きコンパイルを使えば、下記のように特定のシンボルが定義されているかどうかによって、コードの生成を制御できます。

Fooの定義
{$IFDEF SOMETHING}
procedure Foo(const AText: String);
begin
  WriteLn(AText);
end;
{$ENDIF}

上記の例では SOMETHING という条件シンボルが定義されている時だけ Foo という手続きのコードが生成されます。
ただ問題なのは、上記 Foo 手続きを呼び出す箇所も IFDEF~ENDIF で囲まないと行けないことです(定義されていない時、Foo の呼び出しは未定義エラーになるため)。

Fooの呼び出し
procedure Bar;
begin
  {$IFDEF SOMETHING}
  Foo('Hello, Foo');
  {$ENDIF}
end;

呼び出し箇所が数カ所ならまだ何とかなりますが、何十箇所もあるととても面倒です。

呼び出し側に手を加えずコンパイルを通す方法

下記の様に IFDEF~ENDIF の範囲を手続き宣言の中に入れてしまえば、呼び出し側に手を加えずともコンパイルが通るようになります。

procedure Foo(const AText: String);
begin
  {$IFDEF SOMETHING}
  WriteLn(AText);
  {$ENDIF}
end;

// 呼び出し側は IFDEF~ENDIF が要らない
procedure Bar;
begin
  Foo('Hello, Foo');
end;

ですが、これだと Foo 本体と呼び出しコードは残ります(呼び出しに渡している文字列などの引数も残ります)。

シンボルが未定義の時は手続きの定義も呼び出すコードも一気に全部無くなればいいのに!

すべての呼び出しを無に帰す…

では、Foo の定義に手を加えて… inline 指令を付けてみます。

procedure Foo(const AText: String); inline; //← ここに inline !
begin
  {$IFDEF SOMETHING}
  WriteLn(AText);
  {$ENDIF}
end;

inline 指令を付けるとコンパイラはこの関数をインライン展開しようとします。

さて、ここで SOMETHING が定義されていない場合を考えます。
そうすると Foo は以下のようになります。

procedure Foo(const AText: String); inline;
begin
end;

つまり何もしない手続きになります。

この手続き Foo をコンパイルしようとすると、コンパイラは「inline 展開したくても展開する物が何も無いよ~😣」という状態になります。
そこで、コンパイラはこの手続きの存在を消去します
呼び出しコードがあったとしたら、その呼び出しも消去します!

Log('Hello, Foo'); // ←このコードも跡形も無く消える!

「手続き (procedure)」と書きましたが「関数 (function)」は戻り値を利用するかも知れないため存在は抹消されません。
なお、メソッドであっても procedure であれば存在は抹消されます。

実験

では次のコードをコンパイルして、逆アセンブル結果を確認します。

program Project1;

{$DEFINE SOMETHING} // ←ここをコメントアウトすると SOMETHING を未定義化する

procedure Foo(const AText: String); inline;
begin
  {$IFDEF SOMETHING}
  WriteLn(AText);
  {$ENDIF}
end;

begin
  Foo('Hello, Foo');
end.

SOMETHING が定義済みの場合

image.png

↓コードにするとこんな状態になっているのが解ります。

begin
Foo('Hello, Foo'); // Foo が展開されて WriteLn 呼び出しに変わっている
end.

SOMETHING が未定義の場合

image.png

↓コードにするとこんな状態で、Foo や WriteLn の呼び出しコードが無くなっているのが解ります。

begin
end.

まとめ

条件付きコンパイルで procedure の存在を消しさりたい場合は inline を組み合わせると全部消えるので良いですよ。

ひな形
procedure Something; inline;
begin
  {$IFDEF SOMETHING}
  // シンボルが定義されていない時は全部消え去る…
  {$ENDIF}
end;
5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?