In" D编程语言"作者:Andrei Alexandrescu,
这是一个将委托作为模板参数的示例:
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解释说这是有效的,因为委托实际上是一个由两部分组成的胖指针:函数指针和指向其堆栈框架的指针(这就是为什么z可以在其体内访问)。除了找到&#34; pred&#34;作为TEMPLATE参数,而不是参数。模板参数只能是编译时常量。
我确定单元测试中匿名委托的地址确实是编译时常量,但其堆栈框架的地址当然不应该是,所以代表如何被采取作为模板参数?
这里到底发生了什么?
答案 0 :(得分: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
这是在这里创建的文字委托(没有优化btw)。有趣的线条是中间的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中显示查找调用:
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传递并获得类似的代码,它会生成一个直接戳偏移而不是指针的函数。
此外,如果您尝试将运行时委托传递给alias参数,或者尝试将别名dg存储在其他位置,则无法编译。这是一个特殊的函数,具有特定于案例的代码,很多泛型委托的东西并不适合它。