问题的标题可能有点奇怪,但问题是,据我所知,根本没有什么可以反对尾调用优化。但是,在浏览开源项目时,我已经遇到了一些主动尝试阻止编译器进行尾调用优化的函数,例如CFRunLoopRef的实现,其中充满了这样的 hacks 。例如:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
if (func) {
func(observer, activity, info);
}
getpid(); // thwart tail-call optimization
}
我很想知道为什么这看起来如此重要,有没有我作为普通开发人员应该保持这种想法呢?例如。尾调用优化是否存在常见的陷阱?
答案 0 :(得分:82)
我的猜测是确保__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
在堆栈跟踪中用于调试目的。它有__attribute__((no inline))
支持这个想法。
如果你注意到,那个函数只是去反弹到另一个函数,所以它是一种蹦床形式,我只能想到它有一个冗长的名称来帮助调试。这将是特别有用的,因为该函数正在调用已从其他地方注册的函数指针,因此该函数可能没有可访问的调试符号。
另请注意其他类似命名的函数,这些函数执行类似的操作 - 它看起来确实有助于查看从回溯中发生的事情。请记住,这是核心Mac OS X代码,也会出现在崩溃报告和处理样本报告中。
答案 1 :(得分:33)
这只是一个猜测,但可能是为了避免无限循环与使用堆栈溢出错误进行轰炸。
由于所讨论的方法没有在堆栈上放置任何内容,因此尾调用递归优化可能会生成将进入无限循环的代码,而不是将放置返回地址的非优化代码在堆栈上,如果误用,最终会溢出。
我唯一的另一个想法是保留堆栈上的调用以进行调试和堆栈跟踪打印。
答案 2 :(得分:21)
一个潜在的原因是使调试和分析更容易(使用TCO,父堆栈帧消失,这使堆栈跟踪更难理解。)