4

Andrei Alexandrescu による「D プログラミング言語」では、

デリゲートがテンプレート パラメーターとして使用される例があります。

T[] find(alias pred, T)(T[] input)
  if(is(typeof(pred(input[0])) == bool))
{
  for(; input.length > 0; input = input[1 .. $]) {
    if (pred(input[0])) break;
  }
  return input;
}

unittest {
  int[] a = [1,2,3,4,-5,3,-4];
  int z = -2;
  auto b = find!(delegate(x) { return x < z; })(a);
  asssert(b == a[4..$]);
}

Alexandrescu は、デリゲートは実際には関数ポインターとそのスタック フレームへのポインターの 2 つの部分で構成されるファット ポインターであるため、これが機能すると説明しています (これが z が本体内でアクセスできる理由です)。find が "pred" を引数としてではなく、TEMPLATE パラメータとして取ることを除いて。また、テンプレート引数はコンパイル時の定数のみにすることができます。

単体テストの匿名デリゲートのアドレスは確かにコンパイル時の定数であると確信していますが、そのスタック フレームのアドレスは確かにそうであってはなりません。

ここで実際に何が起こっているのですか?

4

1 に答える 1

7

エイリアス パラメーターは、指定された特定のシンボル用にカスタム作成された新しいコードを生成します。これには、コンテキスト内のものが含まれます。

分解を見てみましょう:

0805c850 <_D6test564mainFZv20__T12__dgliteral1TiZ12__dgliteral1MFNbNfiZb>:
805c850:       55                      push   ebp
805c851:       8b ec                   mov    ebp,esp
805c853:       83 ec 04                sub    esp,0x4
805c856:       8b 48 d8                mov    ecx,DWORD PTR [eax-0x28]
805c859:       3b 4d 08                cmp    ecx,DWORD PTR [ebp+0x8]
805c85c:       0f 9f c0                setg   al
805c85f:       0f b6 c0                movzx  eax,al
805c862:       c9                      leave
805c863:       c2 04 00                ret    0x4

これは、ここで作成されたリテラル デリゲートです (最適化なし)。興味深い行は、中央の mov と cmp です。

コンテキスト ポインターは、eax レジスターでデリゲートに渡されることに注意してください。これがどこで呼び出されるか見てみましょう:

0805c868 <_D6test5632__T4findS18main12__dgliteral1TiZ4findMFNaNbNfAiZAi>:
 // snip a bunch of irrelevant code
805c870:       89 45 fc                mov    DWORD PTR [ebp-0x4],eax
// snip
805c892:       8b 45 fc                mov    eax,DWORD PTR [ebp-0x4]
805c895:       89 95 f8 ff ff ff       mov    DWORD PTR [ebp-0x8],edx
805c89b:       e8 b0 ff ff ff          call   805c850 <_D6test564mainFZv20__T12__dgliteral1TiZ12__dgliteral1MFNbNfiZb>

そこにある 2 つの点に注意してください。まず、名前: dgliteral がそこにあることに注意してください。これは、この特定の引数に対して特別に生成された関数です!

次に、eax でこの関数に渡されたものはすべて格納され、最終的に他の関数にも渡されることに注意してください。

もう一度コール スタックを上ってみましょう。今、find コールが表示される _Dmain にいます。

 805c7da:       89 e8                   mov    eax,ebp
 805c7dc:       e8 87 00 00 00          call   805c868 <_D6test5632__T4findS18main12__dgliteral1TiZ4findMFNaNbNfAiZAi>

ベースポインタを渡しています!ところで、0x28 を覚えていますか? _Dmain でも確認できます。いくつかの行があります。

 805c7d1:       c7 45 d8 fe ff ff ff    mov    DWORD PTR [ebp-0x28],0xfffffffe

それがint z = -2;行です (-2 は 32 ビットでは fffffffe として表されます)。通常のローカル変数のようにスタックに格納されます。

要するに、特定のエイリアス引数が、すべてのローカル変数がどこにあるかを知っているまったく新しい関数を生成するということです。呼び出されると、それらへの基本ポインタが転送されます。これだけで十分です。

ところで、ローカル変数をエイリアス params として渡して同様のコードを取得することもできることに注意してください。これは、ポインターを取得するのではなく、オフセットを直接突く関数を作成します。

また、ランタイム デリゲートをエイリアス引数に渡そうとしたり、エイリアス dg を別の場所に保存しようとすると、コンパイルされません。これはケース固有のコードを持つ特別な関数であり、一般的なデリゲートの多くはうまく機能しません。

于 2014-03-13T00:47:32.710 に答える