C 语言的尾调用优化其实很晚才出现

Tail-call optimization in C is relatively recent

很多人以为 C 语言早就支持尾调用优化,但事实并非如此。早期的 C 调用约定要求调用者清理栈帧,这直接阻碍了尾调用优化的实现。直到 2001 年,Mark Probst 才在 GCC 中通过特殊的调用约定实现了这一功能,但当时仍无法处理间接调用,这对解释器调度至关重要。近年来,随着 GCC 和 Clang 的进步,现代编译器已能处理此类优化。Xu 和 Kjolstad 在研究中利用这一特性处理了 10 万个代码片段,远超 Gforth 项目目前的规模。虽然 Python 社区率先应用了相关技术,但 C 语言生态的演进也让我们看到了更多可能性。

C 语言的调用约定一直是由调用者负责清理栈帧,这意味着如果 callee 移除参数,调用者就无法正常工作,从而将调用变成了非尾调用。
  1. drdexebtjl

    除非语言能保证尾调用优化(TCO),否则我不放心写尾递归代码,让自己完全听任编译器或解释器的摆布。

    我觉得把 TCO 仅仅描述为一种“优化”,这种提法非常不幸。

  2. torginus

    TCO 在 C 语言中到底能启用哪些实用模式?我的印象是,所有的尾调用都能更自然地改写成循环。尾调用在函数式语言中很重要,因为那里没有可变循环变量。

    而且在我看来,即便在函数式语言中,它也是一种丑陋的 hack——这是少数几个能让人一眼看出你并非在操作抽象机器,而是在操作一台真实且受限的计算机的核心构造之一。例如,写阶乘最自然的方式:

    let rec factorial n = if n <= 1 then 1 else n * factorial (n - 1)

    并不是尾递归的,如果编译器没能优化,就会导致溢出。

  3. kenjin4096

    我想 Anton 在那篇 LWN 文章里回复的是我,如果我没记错的话。我本人之前并不知道 C 语言这么晚才支持尾调用,从那里学到了新东西!

    另一方面,我自己对编译器领域也挺新的,我觉得 2000 年代初已经是很久以前了;不过话说回来,考虑到 ML 或其变体等语言自 80-90 年代起就支持尾调用,这个时间跨度也不算特别久远。

  4. amavect

    而且 TCO 曾经在 JS 中被加入,随后又被移除了!https://stackoverflow.com/a/54721813

    这也导致了很多 JS 代码中出现有趣的栈溢出 bug(一个解决方案是展平:https://joshua.hu/javascript-infinite-tail-call-recursion-st...)

  5. mmsc

    >That quote is the article, and it's a little surprising that it's buried so far into the content

    在 2026 年这真的让人惊讶吗?如今的网络写作风格首要目的并非沟通,而是尽可能长时间地留住读者。读者的时间是一种需要被榨取的资源。

    我绝对不是针对这位作者个人。这是整个网络的写作风格。

同日更多故事

2026-08-10