Cómo estructurar proyectos en C: estas mejores prácticas me funcionaron

Estructurar proyectos en C y elegir herramientas de compilación se vuelve rápidamente polémico cuando entra en juego la complejidad del mundo real. Los comentaristas comparan los diseños tradicionales basados en Make (`src/`, `include/` y Makefiles escritos a mano) con sistemas modernos como Meson, CMake e incluso las herramientas de Rust con Cargo o las de Go, discutiendo las compensaciones entre simplicidad, flexibilidad, portabilidad y comodidad de “cero configuración”. A lo largo del debate también tratan la organización de encabezados y fuentes, las estrategias de pruebas, la generación de código, la compilación cruzada y si el ecosistema fragmentado de herramientas de C es un precio aceptable por su ubicuidad o una razón para preferir lenguajes más nuevos.

Estructura general del proyecto

  • La estructura propuesta se considera muy similar al diseño “pitchfork” de C++ (src/, include/, etc.).
  • Algunos sostienen que separar .c y .h en directorios de nivel superior distintos es innecesario para encabezados internos; otros lo encuentran útil para implementaciones específicas de plataforma (por ejemplo, platform_windows.c frente a platform_linux.c).
  • Varios prefieren poner todos los subsistemas dentro de src/ y reservar include/ solo para encabezados públicos/de biblioteca.
  • La distinción entre encabezados instalables y encabezados internos se considera crucial para las bibliotecas.
  • A algunos no les gusta bin/ y lib/ dentro del repositorio, y prefieren un PREFIX configurable (build/ o $HOME/.local) y archivos de entorno que ajusten PATH.

Make, CMake y herramientas de compilación alternativas

  • Múltiples recetas para Makefiles flexibles: reglas de patrón, wildcard + patsubst, y listas de objetos generadas automáticamente para evitar actualizar los Makefiles manualmente.
  • Aclaración de que las reglas integradas de %.o solo funcionan cuando los स्रोतes y los objetos comparten directorio; las soluciones incluyen VPATH o reglas explícitas.
  • Crítica al Makefile del artículo: depende de un comportamiento frágil del orden de compilación bajo -j, guarda compile_commands.json y mezcla directorios de editor/CI como “basura”.
  • Algunos abogan por compilaciones muy minimalistas (un único all.c que #include todo) para proyectos pequeños; los críticos señalan que eso no escala a sanitizers, pruebas ni CI.
  • Las opiniones sobre Make divergen: para algunos es simple, componible y escalable; para otros está anticuado y es demasiado delicado frente a Meson, Buck2, Xmake, el sistema de compilación de Zig, etc.
  • CMake se ve como potente pero incómodo; hay debate sobre listar archivos explícitamente frente a usar globs (con compensaciones entre regeneración y corrección).

Herramientas, pruebas y generación de código

  • Sugerencias: MinUnit, Clang-Tidy (por ejemplo, el perfil CERT), Clang-Format, -Weverything, sanitizers (ASan/UBSan) y advertencias estrictas (-Werror=missing-declarations, etc.).
  • Visiones mixtas sobre las pruebas unitarias en C: algunos enfatizan aserciones y procedimientos sensibles al contexto; otros señalan proyectos de C muy probados como contraejemplos.
  • Varios describen la colocación conjunta de pruebas unitarias con la implementación, además de convenciones como *_test.c detectadas automáticamente por Make.
  • Fuerte incentivo para usar generadores de código (Lua, Python, motores de plantillas, incluso C) y tratar src/ como generadores, gen/ como C emitido y obj/ como salida de compilación.
  • Discusión sobre compilar encabezados por separado y el intercambio entre #pragma once y los guardas de inclusión.

Paquetes, compilación cruzada y debate meta

  • Deseo de simplicidad al estilo “Cargo”: una única herramienta estándar, configuración cero o mínima, disposición consistente.
  • Otros argumentan que la antigüedad de C, la portabilidad y la diversidad de plataformas hacen irreal una sola herramienta estándar; en su lugar coexisten varios sistemas de paquetes/compilación (Conan, vcpkg, pkg-config, vendoring).
  • Compilación cruzada: se menciona zig cc y toolchains de estilo embebido; énfasis en separar la capa de plataforma detrás de interfaces.
  • Largo hilo meta sobre Rust frente a C: se elogian Cargo de Rust y las herramientas de Go; algunos se quejan de los desvíos de “reescribir en Rust”, mientras que otros dicen que comparar herramientas es justo y esperable.