La optimización de llamadas en cola en C es relativamente reciente (2025)

La optimización de llamadas en cola en C y lenguajes relacionados está pasando de ser un truco de compilador de nicho a algo en lo que los desarrolladores quieren apoyarse para la corrección, no solo para la velocidad. Los comentaristas contrastan lenguajes donde las llamadas en cola están garantizadas por la especificación (como Scheme, F# o la implementación de JS en JavaScriptCore) con C, C++, Rust y C#, donde TCO suele tratarse como una optimización opcional, lo que impulsa propuestas como `[[musttail]]` en C++ y una palabra clave `become` en Rust para convertir un TCO fallido en un error en tiempo de compilación. El hilo también profundiza en cómo las convenciones de llamada, las funciones variádicas, los destructores/RAII y las restricciones de herramientas dificultan implementar llamadas en cola correctas, y por qué son cruciales para intérpretes, máquinas de estados y para escribir código recursivo sin arriesgar desbordamientos de pila.

Alcance y garantías de TCO

  • Existe un fuerte sentimiento de que tratar la optimización de llamadas en cola (TCO) como “solo una optimización” es problemático, porque la corrección (evitar desbordamientos de pila, habilitar ciertos estilos) puede depender de ella.
  • Contraste entre lenguajes donde TCO está exigida por la especificación (por ejemplo, Scheme) y C/C++/JS donde es opcional y depende de la implementación.
  • Varias personas sostienen que, sin una garantía a nivel de lenguaje o un mecanismo de “must tail”, depender de TCO es inseguro.

Atributos, palabras clave y compatibilidad del lenguaje

  • Los compiladores modernos de C/C++ (GCC/Clang/MSVC) exponen atributos [[...::musttail]]: intentan con fuerza aplicar TCO y emiten un error de compilación si no pueden.
  • Propuesta de la palabra clave become en Rust: solicita explícitamente TCO, descarta primero todos los valores locales para que la llamada quede en verdadera posición de cola, y convierte el fallo de TCO en un error de compilación.
  • Comparaciones con @tailrec de Scala (limitado a la recursión sobre sí mismo) y con las instrucciones IL tail. de F# en .NET; C# no las emite de forma fiable.

Estandarización de C e इतिहास

  • C sigue sin tener TCO garantizada; el soporte actual son extensiones específicas del compilador.
  • Se está redactando una especificación técnica (TS) para llamadas en cola en C y para defer; incluso una vez estandarizada, la adopción será lenta y las implementaciones pueden ser conformes y aun así rechazar la sintaxis.
  • La discusión menciona limitaciones antiguas de GCC (por ejemplo, llamadas indirectas, variádicas) y que una TCO general y usable en GCC es relativamente reciente en comparación con su historia.
  • Históricamente, MSVC tuvo un soporte débil para C y TCO, mejoró algo en años recientes, pero algunos siguen siendo escépticos respecto a C.

Obstáculos técnicos: convenciones de llamada y gestión de pila

  • Las funciones variádicas y las convenciones de llamada de C pre-ANSI complican las llamadas en cola correctas, ya que solo el llamador conoce el tamaño de los argumentos y debe limpiar la pila.
  • Las convenciones de limpieza por el llamador frente a la limpieza por el llamado, y las llamadas entre módulos, hacen difícil una TCO genérica y garantizada; los punteros a función y los símbolos exportados también restringen a los compiladores.
  • Debate sobre cuánto importa la convención de llamada cuando el compilador controla tanto al llamador como al llamado; otros señalan las restricciones de compilación separada y estabilidad de ABI.

Patrones prácticos y debates de estilo

  • La TCO es importante para intérpretes, máquinas de estados, estilo de paso de continuación y conjuntos de funciones mutuamente recursivas, no solo para bucles recursivos como factorial.
  • Algunos prefieren bucles explícitos en C; otros encuentran más claras las formulaciones recursivas en cola y argumentan que TCO permite abstracciones más agradables, especialmente en estilos más funcionales.
  • Se muestra una “TCO” manual con goto, pero se desaconseja por propensa a errores frente a la recursión real con soporte del compilador.