Como Estruturar Projetos em C: Estas Melhores Práticas Funcionaram para Mim

Como estruturar projetos em C e escolher ferramentas de build rapidamente se torna um assunto controverso quando a complexidade do mundo real entra em cena. Os comentaristas avaliam layouts tradicionais baseados em Make, com `src/`, `include/` e Makefiles escritos à mão, contra sistemas modernos como Meson, CMake e até o Cargo do Rust ou as ferramentas do Go, debatendo os trade-offs entre simplicidade, flexibilidade, portabilidade e conveniência de “configuração zero”. Ao longo do caminho, discutem organização de headers e fontes, estratégias de teste, geração de código, cross-compilation e se o ecossistema fragmentado de ferramentas do C é um preço aceitável pela ubiquidade da linguagem ou um motivo para preferir linguagens mais novas.

Layout geral do projeto

  • Estrutura proposta vista como muito semelhante ao layout C++ “pitchfork” (src/, include/, etc.).
  • Alguns argumentam que separar .c e .h em diretórios de topo diferentes é desnecessário para cabeçalhos internos; outros consideram isso útil para implementações específicas de plataforma (por exemplo, platform_windows.c vs platform_linux.c).
  • Vários preferem colocar todos os subsistemas em src/ e reservar include/ apenas para cabeçalhos públicos/de biblioteca.
  • A distinção entre cabeçalhos instaláveis e cabeçalhos internos é considerada crucial para bibliotecas.
  • Alguns não gostam de bin/ e lib/ dentro do repositório, preferindo um PREFIX configurável (build/ ou $HOME/.local) e arquivos de ambiente que ajustem o PATH.

Make, CMake e ferramentas alternativas de build

  • Várias receitas para Makefiles flexíveis: regras por padrão, wildcard + patsubst, e listas de objetos geradas automaticamente para evitar atualizar Makefiles manualmente.
  • Esclarecimento de que as regras internas %.o só funcionam quando fontes e objetos compartilham um diretório; alternativas incluem VPATH ou regras explícitas.
  • Crítica ao Makefile do artigo: depende de um comportamento frágil de ordem de build sob -j, armazena compile_commands.json e mistura diretórios de editor/CI como “litter”.
  • Alguns defendem builds muito minimalistas (um único all.c que #includes tudo) para projetos pequenos; críticos observam que isso não escala para sanitizers, testes, CI.
  • As opiniões divergem sobre Make: visto por alguns como simples, composável e escalável; por outros como ultrapassado e trabalhoso em comparação com Meson, Buck2, Xmake, o sistema de build do Zig, etc.
  • CMake é visto como poderoso, mas desajeitado; há debate sobre listar arquivos explicitamente versus usar globs (com trade-offs de regeneração e correção).

Ferramentas, testes e geração de código

  • Sugestões: MinUnit, Clang-Tidy (por exemplo, perfil CERT), Clang-Format, -Weverything, sanitizers (ASan/UBSan) e warnings rígidos (-Werror=missing-declarations, etc.).
  • Visões mistas sobre testes unitários em C: alguns enfatizam asserções e procedimentos sensíveis ao contexto; outros apontam projetos C amplamente testados como contraexemplos.
  • Vários descrevem colocar testes unitários junto da implementação, além de convenções como *_test.c detectadas automaticamente por Make.
  • Forte incentivo ao uso de geradores de código (Lua, Python, motores de template, até o próprio C) e tratar src/ como geradores, gen/ como C emitido, obj/ como saída de build.
  • Discussão sobre compilar cabeçalhos isoladamente e o trade-off entre #pragma once e guardas de inclusão.

Pacotes, cross-compilation e meta-debate

  • Desejo por simplicidade “parecida com Cargo”: uma única ferramenta padrão, configuração zero ou baixa, layout consistente.
  • Outros argumentam que a idade do C, sua portabilidade e as diversas plataformas tornam irrealista uma única ferramenta padrão; múltiplos sistemas de pacote/build (Conan, vcpkg, pkg-config, vendoring) coexistem em vez disso.
  • Cross-compilation: menção a zig cc e toolchains de estilo embarcado; ênfase em separar a camada de plataforma atrás de interfaces.
  • Longo meta-thread sobre Rust vs C: o Cargo do Rust e as ferramentas do Go são elogiados; alguns reclamam de desvios para “reescrever em Rust”, outros dizem que comparar ferramentas é justo e esperado.