Portar a GCC 14: problemas del lenguaje C

La mayor conformidad de GCC 14 con los estándares modernos de C está convirtiendo advertencias de larga data —como `int` implícito, funciones no declaradas y algunos desajustes de punteros— en errores, lo que obliga a actualizar grandes bases de código y sistemas de construcción como autoconf. Los comentaristas sopesan las ventajas de una mayor seguridad, diagnósticos más claros y la adopción, muy retrasada, de características de C99/C23 (como los tipos de arreglos de longitud variable) frente al coste de romper C heredado y “portable” que dependía del comportamiento pre-ANSI o específico del compilador. El hilo también profundiza en cuestiones más básicas de diseño del lenguaje, incluidas la semántica de tipos enteros y de punteros, las convenciones de llamada y por qué algunas características de C evolucionaron tan lentamente pese a estar estandarizadas hace décadas.

Los valores predeterminados más estrictos de C en GCC 14

  • GCC 14 convierte varias advertencias de C de larga data en errores (por ejemplo, declaraciones implícitas de funciones, int implícito y algunos usos de punteros incompatibles).
  • Muchos comentaristas consideran que esto llega tarde: estos constructos han sido efectivamente obsoletos durante décadas y casi nunca son intencionales en el código moderno.
  • Otros señalan las roturas: el código antiguo (incluidas entradas de IOCCC y el userland histórico de GNU) fallará a menos que los errores se degraden. GCC sigue permitiendo banderas para restaurar el comportamiento antiguo.

Arreglos de longitud variable (VLAs) y C23

  • El hilo discute C99 (VLAs obligatorios), C11 (VLAs opcionales) y C23 (los tipos VLA vuelven a ser obligatorios; los objetos VLA con almacenamiento automático siguen siendo opcionales).
  • Se aclaró que los compiladores deben admitir la sintaxis y la semántica de los tipos de modificación variable (por ejemplo, sizeof y offsetof sobre ellos), pero aún pueden omitir los VLAs basados en la pila.
  • Debate sobre la complejidad de implementación: diseños dependientes del tiempo de ejecución y typedefs introducen una maquinaria semántica adicional más allá de ser solo “azúcar sintáctico de alloca”.
  • MSVC todavía carece de VLAs y se saltó por completo C99, lo que limita el uso portátil de VLAs.

Trabajo de portabilidad en distribuciones

  • Fedora y otras ya han hecho una limpieza a gran escala para prepararse para valores predeterminados más estrictos, muy afectados por pruebas generadas por autoconf que usan código C dudoso y ocultan advertencias.
  • El riesgo principal era la “pérdida silenciosa de funciones”: las compilaciones tienen éxito, pero las comprobaciones de configure fallan, deshabilitando características. Esto llevó a enfoques de diferenciación de configuraciones.
  • Que Clang/Xcode tuvieran valores predeterminados más estrictos y que Gentoo aportara parches al upstream ayudó a lograr consenso para endurecer GCC.

Construcciones C heredadas y modelos de compilación

  • El C pre-ANSI permitía llamar a funciones no declaradas, tratándolas como si devolvieran int; funcionaba “lo bastante bien” cuando sizeof(int) == sizeof(pointer).
  • Discusión sobre cómo los compiladores de una sola pasada manejaban funciones aún no declaradas y si una comprobación extra implicaría una “segunda pasada”.
  • Algunos sugieren que el lenguaje debería admitir oficialmente una referencia adelantada más flexible, citando infraestructuras de compiladores más nuevas donde “simplemente funciona”.

Tipos enteros, tamaño de palabra y estilo

  • Larga discusión sobre por qué int siguió siendo de 32 bits en sistemas de 64 bits: compatibilidad hacia atrás, escalera limitada de enteros estándar y supuestos existentes en el código.
  • Argumentos a favor y en contra de hacer alguna vez que int sea de 64 bits: portabilidad frente a huella de memoria/caché frente a claridad.
  • Distinción entre tipos enteros “estándar” (char/short/int/long/long long) y tipos enteros “extendidos”; se señala que stdint.h sobre todo crea alias (typedef) de los primeros, no anchuras realmente nuevas.
  • Prácticas divergentes: algunos prefieren tipos de ancho fijo (int32_t, int64_t), otros prefieren int + long long y evitan los tipos numerados por razones estéticas y de sobrecarga (en C++).

Punteros a funciones, void* y representaciones de punteros

  • Se pregunta por qué una función que toma const char* no puede usarse donde se espera un puntero a función de tipo int (*)(const void*, const void*).
  • El estándar prohíbe conversiones arbitrarias de punteros a función; C también carece de un tipo genérico de puntero a función.
  • C11 garantiza que void* y los punteros a caracteres comparten representación y alineación; otros tipos de puntero pueden no hacerlo, y los punteros a función pueden diferir de los punteros a objetos.
  • Se citan arquitecturas históricas y exóticas con tamaños de puntero y modos de direccionamiento no uniformes como razones por las que el estándar sigue siendo estricto.

Compatibilidad hacia atrás vs. casos de uso de nicho

  • Algunos critican la dirección “anti-C89”, argumentando que eliminar int implícito perjudica usos creativos poliglota (por ejemplo, código dual C/JavaScript) sin un beneficio claro.
  • La opinión mayoritaria en el hilo favorece valores predeterminados más fuertes para evitar código sutilmente roto, con salidas de escape para programas heredados o de estilo concurso.