A otimização de chamadas de cauda em C é relativamente recente (2025)

A otimização de chamadas de cauda em C e linguagens relacionadas está deixando de ser um truque de compilador de nicho para algo em que os desenvolvedores querem confiar por correção, e não apenas por desempenho. Os comentadores contrastam linguagens em que chamadas de cauda são garantidas pela especificação (como Scheme, F# ou a implementação de JS no JavaScriptCore) com C, C++, Rust e C#, onde TCO costuma ser tratada como uma otimização opcional, o que motiva propostas como `[[musttail]]` em C++ e uma palavra-chave `become` em Rust para transformar falhas de TCO em erro de compilação. O debate também aborda como convenções de chamada, funções variádicas, destruidores/RAII e restrições de ferramentas dificultam a implementação de chamadas de cauda corretas, e por que elas são cruciais para interpretadores, máquinas de estados e para escrever código recursivo sem risco de estouro de pilha.

Escopo e garantias de TCO

  • Há um forte sentimento de que tratar a otimização de chamadas de cauda (TCO) como “apenas uma otimização” é problemático, porque a correção (evitar estouro de pilha, habilitar certos estilos) pode depender dela.
  • Contraste entre linguagens em que TCO é exigida pela especificação (por exemplo, Scheme) e C/C++/JS, em que ela é opcional e dependente da implementação.
  • Várias pessoas argumentam que, sem uma garantia no nível da linguagem ou um mecanismo “must tail”, depender de TCO é inseguro.

Atributos, palavras-chave e suporte da linguagem

  • Compiladores modernos de C/C++ (GCC/Clang/MSVC) expõem atributos [[...::musttail]]: eles tentam fortemente fazer TCO e emitem um erro de compilação se não conseguirem.
  • Proposta da palavra-chave become em Rust: solicita explicitamente TCO, remove primeiro todos os valores locais para que a chamada fique em verdadeira posição de cauda e faz com que a falha em aplicar TCO gere um erro de compilação.
  • Comparações com @tailrec do Scala (limitado à recursão sobre si mesma) e com as instruções IL tail. do F# na .NET; o C# não as emite de forma confiável.

Padronização de C e histórico

  • O C, por si só, ainda não tem TCO garantida; o suporte atual são extensões específicas de compilador.
  • Está sendo elaborada uma especificação técnica (TS) para chamadas de cauda em C e para defer; mesmo depois de padronizada, a adoção será lenta e implementações podem estar conformes e ainda assim rejeitar a sintaxe.
  • A discussão observa limitações antigas do GCC (por exemplo, chamadas indiretas, variádicas) e que uma TCO geral e utilizável no GCC é relativamente recente em comparação com a vida útil do compilador.
  • O MSVC historicamente teve suporte fraco para C e para TCO, melhorou um pouco nos últimos anos, mas ainda é visto com ceticismo por alguns em relação ao C.

Obstáculos técnicos: convenções de chamada e gerenciamento de pilha

  • Funções variádicas e convenções de chamada do C pré-ANSI complicam chamadas de cauda corretas, já que apenas o chamador conhece o tamanho dos argumentos e deve limpar a pilha.
  • Convenções de limpeza pelo chamador versus pelo callee e chamadas entre módulos tornam a TCO genérica e garantida difícil; ponteiros para função e símbolos exportados restringem ainda mais os compiladores.
  • Há debate sobre o quanto a convenção de chamada importa quando o compilador controla tanto chamador quanto callee; outros apontam para as restrições de compilação separada e de estabilidade da ABI.

Padrões práticos e debates de estilo

  • TCO é importante para interpretadores, máquinas de estados, estilo de passagem de continuação e conjuntos de funções mutuamente recursivas, não apenas para laços recursivos como fatorial.
  • Alguns preferem laços explícitos em C; outros acham formulações recursivas em cauda mais claras e argumentam que TCO viabiliza abstrações melhores, especialmente em estilos mais funcionais.
  • A “TCO” manual com goto é mostrada, mas desencorajada por ser propensa a erros em comparação com a recursão verdadeira mais o suporte do compilador.