Как D позволяет использовать делегаты в качестве параметров шаблона?

В книге «Язык программирования 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..$]);
}

Александреску объясняет, что это работает, потому что делегат на самом деле является толстым указателем, состоящим из двух частей: указателя на функцию и указателя на его фрейм стека (поэтому z доступен внутри его тела). За исключением того, что find принимает pred как параметр TEMPLATE, а не как аргумент. И аргументы шаблона могут быть только константами времени компиляции.

Я уверен, что адрес анонимного делегата в нашем модульном тесте действительно является константой времени компиляции, но адрес его стекового фрейма определенно не должен быть, так как же делегат может быть принят в качестве параметра шаблона?

Что здесь происходит на самом деле?


person dspyz    schedule 12.03.2014    source источник


Ответы (1)


Параметр псевдонима генерирует новый код, специально созданный для конкретного заданного символа, который включает в себя элементы в контексте.

Смотрим разборку:

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>

Обратите внимание на две вещи: во-первых, на имя: обратите внимание, что там есть dgliteral — это специально сгенерированная функция для этого конкретного аргумента!

Во-вторых, обратите внимание, что все, что было передано этой функции в eax, сохраняется и в конечном итоге также передается другой функции.

Давайте еще раз пройдемся по стеку вызовов, теперь мы находимся в _Dmain, где появляется вызов find:

 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 представляется как fffffffe в 32-битном формате). Он хранится в стеке как обычная локальная переменная.

Суть в том, что конкретный аргумент псевдонима генерирует совершенно новую функцию, которая знает, где находятся все локальные переменные. Когда он вызывается, ему пересылается базовый указатель на них, и это все, что ему нужно знать.

Кстати, обратите внимание, что вы также можете передавать локальные переменные в качестве параметров псевдонима и получать аналогичный код, он создает функцию, которая напрямую указывает смещение, а не берет указатель.

Кроме того, если вы попытаетесь передать делегат времени выполнения в аргумент псевдонима или попытаетесь сохранить псевдоним dg где-то еще, это не будет скомпилировано. Это специальная функция с кодом, специфичным для конкретного случая, многие общие элементы делегата не совсем работают для нее.

person Adam D. Ruppe    schedule 13.03.2014