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 reservarinclude/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/elib/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
%.osó 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, armazenacompile_commands.jsone mistura diretórios de editor/CI como “litter”. - Alguns defendem builds muito minimalistas (um único
all.cque#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.cdetectadas 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 oncee 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 cce 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.