Portando para o GCC 14: problemas da linguagem C

A conformidade mais rígida do GCC 14 com os padrões modernos do C está transformando avisos de longa data — como `int` implícito, funções não declaradas e algumas incompatibilidades de ponteiros — em erros, forçando atualizações em grandes bases de código e sistemas de build como o autoconf. Comentadores ponderam os benefícios de maior segurança, diagnósticos mais claros e a adoção tardia de recursos do C99/C23 (como tipos de arrays de tamanho variável) contra a dor de quebrar C legado e “portátil” que dependia de comportamento pré-ANSI ou específico de compilador. A discussão também aprofunda questões de projeto da linguagem, incluindo semântica de tipos inteiros e de ponteiros, convenções de chamada e por que alguns recursos do C evoluíram tão lentamente apesar de estarem padronizados há décadas.

Defaults mais rígidos do GCC 14 para C

  • O GCC 14 transforma vários avisos de longa data em erros (por exemplo, declarações implícitas de função, int implícito, alguns usos incompatíveis de ponteiros).
  • Muitos comentaristas veem isso como algo atrasado: essas construções estão efetivamente obsoletas há décadas e quase nunca são intencionais em código moderno.
  • Outros observam a quebra: código antigo (incluindo entradas do IOCCC e software histórico do userland GNU) vai falhar a menos que os erros sejam rebaixados. O GCC ainda permite flags para restaurar o comportamento antigo.

Arrays de tamanho variável (VLAs) e C23

  • A discussão aborda C99 (VLAs obrigatórios), C11 (VLAs opcionais) e C23 (tipos de VLA obrigatórios novamente; objetos de VLA com armazenamento automático continuam opcionais).
  • Foi esclarecido que compiladores devem suportar a sintaxe e a semântica de tipos de modificação variável (por exemplo, sizeof e offsetof sobre eles), mas ainda podem omitir VLAs baseados na pilha.
  • Debate sobre complexidade de implementação: layouts dependentes do tempo de execução e typedefs introduzem mais maquinaria semântica do que apenas “açúcar para alloca”.
  • O MSVC ainda não tem VLAs e pulou totalmente o C99, limitando o uso portátil de VLAs.

Trabalho de portabilidade nas distros

  • O Fedora e outros já fizeram grandes limpezas para se preparar para defaults mais rígidos, com forte impacto de testes gerados por autoconf que usam código C duvidoso e escondem avisos.
  • O principal risco era a “perda silenciosa de funcionalidades”: as compilações passam, mas as verificações de configure falham, desativando recursos. Isso levou a abordagens de diffing de configuração.
  • O fato de o Clang/Xcode terem defaults mais rígidos e a upstreamização de correções pelo Gentoo ajudaram a obter consenso para apertar o GCC.

Construções legadas de C e modelos de compilação

  • O C pré-ANSI permitia chamar funções não declaradas, tratando-as como retornando int; isso funcionava “bem o suficiente” quando sizeof(int) == sizeof(pointer).
  • Há discussão sobre como compiladores de uma passagem lidavam com funções ainda não declaradas e se checagem extra implicaria uma “segunda passagem”.
  • Alguns sugerem que a linguagem deveria oficialmente suportar referência adiantada mais flexível, citando infraestruturas de compiladores mais novas em que isso “simplesmente funciona”.

Tipos inteiros, tamanho de palavra e estilo

  • Longa discussão sobre por que int permaneceu com 32 bits em sistemas de 64 bits: compatibilidade retroativa, escada limitada de inteiros padrão e suposições já existentes no código.
  • Argumentos a favor e contra tornar int 64 bits algum dia: portabilidade versus uso de memória/cache versus clareza.
  • Distinção entre tipos inteiros “padrão” (char/short/int/long/long long) e tipos inteiros “estendidos”; observação de que stdint.h em geral cria typedefs dos primeiros, e não larguras realmente novas.
  • Práticas divergentes: alguns preferem tipos de largura fixa (int32_t, int64_t), outros preferem int + long long e evitam tipos numerados por estética e por problemas de sobrecarga (em C++).

Ponteiros de função, void* e representações de ponteiros

  • Surge a প্রশ্নão de por que uma função que recebe const char* não pode ser usada onde se espera um ponteiro de função do tipo int (*)(const void*, const void*).
  • O padrão proíbe conversões arbitrárias entre ponteiros de função; o C também não tem um tipo genérico de ponteiro de função.
  • O C11 garante que void* e ponteiros para caracteres compartilham representação e alinhamento; outros tipos de ponteiro podem não compartilhar, e ponteiros de função podem ser diferentes de ponteiros para objetos.
  • Arquiteturas históricas e exóticas com tamanhos e modos de endereçamento não uniformes são citadas como razões para o padrão permanecer estrito.

Compatibilidade retroativa vs. casos de uso de nicho

  • Alguns criticam a direção “anti-C89”, argumentando que remover int implícito prejudica usos criativos de poliglotas (por exemplo, código duplo C/JavaScript) sem benefício claro.
  • A visão majoritária na discussão favorece defaults mais fortes para evitar código sutilmente quebrado, com saídas de escape para programas legados ou estilo concurso.