额外的ldnull和tail的目的是什么?f#实现vs c#
本文关键字:实现 vs 是什么 ldnull tail | 更新日期: 2023-09-27 17:51:07
以下c#函数:
T ResultOfFunc<T>(Func<T> f)
{
return f();
}
的编译结果如下:
IL_0000: ldarg.1
IL_0001: callvirt 05 00 00 0A
IL_0006: ret
而等价的f#函数:
let resultOfFunc func = func()
编译成:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldnull
IL_0003: tail.
IL_0005: callvirt 04 00 00 0A
IL_000A: ret
(两者都处于释放模式)。开头有一个额外的nop,我不是很好奇,但有趣的是额外的ldnull
和tail.
指令。
我的猜测(可能是错误的)是,ldnull
是必要的,如果函数是void
,所以它仍然返回的东西(unit
),但这并不能解释什么是tail.
指令的目的。如果函数确实在栈上压入了什么会发生什么,它不是被一个额外的空卡住了吗?
c#和f#版本有一个重要的区别:c#函数没有任何参数,但f#版本有一个类型为unit
的参数。unit
的值就是显示为ldnull
的值(因为null
被用作唯一的unit
值()
的表示)。
如果你把第二个函数翻译成c#,它看起来像这样:
T ResultOfFunc<T>( Func<Unit, T> f ) {
return f( null );
}
对于.tail
指令——即所谓的"尾部调用优化"。
在常规函数调用期间,返回地址被压入堆栈(CPU堆栈),然后调用函数。当函数完成后,它执行"return"指令,该指令将返回地址从堆栈中弹出并将控制转移到那里。
然而,当函数A
调用函数B
,然后立即返回函数B
的返回值,而不做任何其他事情,CPU可以跳过在堆栈上推送额外的返回地址,并执行"跳转"到B
,而不是"调用"。这样,当B
执行"return"指令时,CPU将从堆栈中弹出返回地址,该地址不会指向A
,而是指向最初调用A
的人。
另一种思考方式是:函数A
调用函数B
时,在返回之前不是,而是而不是返回,这样就把返回的荣誉委托给了B
。
它被称为"尾部调用",因为对B
的调用发生在A
的尾部