C语言中的尾调用优化是相对较新的特性。
Tail-call optimization in C is relatively recent (2025)

原始链接: https://lwn.net/Articles/1034703/

在这篇文章中,Anton 讨论了 C 编译器中尾调用优化(TCO)的演变。从历史上看,C 语言的调用约定因要求调用者负责栈清理而阻碍了 TCO 的实现,导致无法进行有效优化。虽然早期版本的 GCC 在间接调用方面存在显著局限,但现代编译器如 GCC 和 Clang 现已实现了强大的 TCO 支持。 在反思近期对“复制与修补”(Copy-and-Patch)编译的研究时,Anton 指出,现代 TCO 能力现已允许生成超过 10 万个代码片段,这远超传统基于“goto *”的分发系统的限制。作者提到,虽然他们的项目 Gforth 尚未实现这些先进技术,但他们对 Python 社区成功采用该方法以提升性能表示祝贺。

一篇近期的 LWN 文章引发了 Hacker News 关于 C 语言中尾调用优化(TCO)历史与实现的讨论。尽管 Mark Probst 早在 2001 年就率先在 GCC 中实现了 TCO,但社区的争论凸显出:C 语言中的 TCO 往往受到调用约定的限制,特别是栈清理和可变参数函数的复杂性。 这场讨论的核心矛盾在于,TCO 应被视为一种“优化”还是一种“语义特性”。批评者认为,将其贴上“优化”的标签是不妥的,因为它掩盖了一个事实,即 TCO 可以保证栈空间的使用量恒定,从而将递归有效地转化为循环。由于缺乏语言层面的保证(与 Scheme 不同),C 语言开发者往往面临栈溢出的风险,这使得依赖 TCO 变得并不稳妥。 现代解决方案,如 GCC 和 Clang 中的 `[[musttail]]` 属性,允许开发者显式地要求进行 TCO;如果编译器无法优化该调用,它会触发编译时错误,而不是默默地失败。讨论总结认为,虽然使用 `goto` 或内联汇编等手动变通方法可以模拟尾调用,但由编译器强制执行的标准化 TCO,仍然是编写健壮且高性能 C 代码的最理想途径。
相关文章

原文
Posted Aug 21, 2025 22:11 UTC (Thu) by anton (subscriber, #25547)
Parent article: Python, tail calls, and performance

Actually tail calls in C have not been around forever. The C calling convention has been that the callee does not remove any stuff the caller has put on the stack. The caller could see the declaration int f();, the actual call could have n>0 arguments, and the actual function could have m≤n parameters. That would not always work if the callee removed the arguments.

So the caller had to remove the arguments between the call and the following return, turning the call into a non-tail call.

When I looked in 1994 at the C compilers of the day, they did not perform tail-call optimization for the kind of usage shown in the article. In 2001 Mark Probst implemented tail-call optimization in GCC with a separate calling convention; he lists the limitations of the then-existing tail-call optimization in GCC in section 6.4, among them: "It cannot handle indirect calls" (which would have been used in tail calls for interpreter dispatch).

I have not looked at the issue since then (GCC's goto * was good enough (well, mostly)), and I had not much reason for assuming that something had changed wrt to GCC support for tail-calls (although one release note mentioned sibcalls, and I remember thinking that I should be checking that out.

Anyway, last year I read the paper on "Copy-and-Patch Compilation" by Xu and Kjolstad, and they use tail-call optimization. In any case, after reading that paper, I made some tests if gcc and clang can do tail-call optimization for the kind of tail calls shown in the article. And it works. And Xu and Kjolstad report that they use 100,000 code snippets, whereas we limit ourselves in Gforth to <2000 (for VM instructions, stack caching variations thereof, static superinstructions etc.). Being able to do 100,000 would allow us to use techniques that need too many different code snippets to be usable in a goto *-based system.

We have not gotten around to putting this into Gforth yet, so congratulations to the Python community for being there first.


联系我们 contact @ memedata.com