Ingeniería y Programación de Software
Comprender y administrar las dependencias del programa C con Makefiles
Table of Contents
Introducción
En la programación C, un programa se divide a menudo en múltiples archivos fuente y archivos de cabecera para mejorar la organización, reutilización y velocidad de compilación. Sin embargo, esta modularidad presenta un desafío: cuando un archivo de cabecera cambia, cada archivo de fuente que incluye debe ser recompilado. Hacer esto manualmente es errores propensa y consume tiempo.
¿Qué son los Makefiles?
[LT] [FLT] [FLT] [FLT]] [FLT]] [FLT]]] [FLT]]] [FLT]]] [FLT]]] [FLT]] [FLT]] [FLT]] [FLT]] [Flic]]] [Flic]]] [F
Los Makefiles han sido parte de Unix desde los años 70, y GNU Make es el estándar de facto de Linux y macOS. La sintaxis es concisa pero puede ser sutil; conseguir dependencias correctas es la habilidad primaria que un desarrollador C necesita.
Comprensión de las dependencias
En un proyecto C, las dependencias no se limitan a archivos. Cada archivo fuente incluye uno o más ficheros de encabezado (por ejemplo, ). Si se modifica un encabezado, todos los archivos que lo incluyen deben ser recompilados. De manera similar, los archivos de objeto dependen de sus archivos correspondientes, y el ejecutable final depende de todos los archivos objeto.
Gastos en función implícita
En los primeros Makefiles, los programadores enumeran todos los requisitos manualmente. Ese enfoque es frágil: olvidar un encabezado significa que se construyen escalones, mientras que la inclusión de demasiados desencadena recompiles innecesarios. Peor, a medida que el proyecto crece, las listas manuales se vuelven inmantenibles. La solución moderna es que el compilador genere automáticamente las dependencias, convirtiéndolos en prerrequisitos implícitos y controlados por máquina.
Un Makefile bien diseñado trata a las dependencias como una preocupación de primera clase. El objetivo nunca es reconstruir nada que no necesite reconstruir, y reconstruir siempre todo lo que hace. Esta es la esencia de la correlación] y ]eficiencia] en construcciones incrementales.
Estructura básica de un fichero de maquillaje
Un típico Makefile contiene definiciones, reglas y objetivos falsos variables. Aquí hay un ejemplo mínimo pero funcional para un proyecto con y :
CC = gcc
CFLAGS = -Wall -Wextra -O2
main: main.o utils.o
$(CC) $(CFLAGS) -o main main.o utils.o
main.o: main.c
$(CC) $(CFLAGS) -c main.c
utils.o: utils.c
$(CC) $(CFLAGS) -c utils.c
clean:
rm -f main main.o utils.o
Este Makefile tiene cuatro objetivos: (el ejecutable), dos ficheros de objetos y un objetivo falso . Las líneas de dependencia después de que el colon diga make que los archivos a revisar antes de decidir reconstruir el objetivo.
Objetivos falsos
Los objetivos como o no representan ficheros. Para evitar make] confundirlos con nombres de ficheros, deben ser declarados como ]fónica:
.PHONY: clean all
Sin esto, si existiera un archivo llamado , make lo consideraría actualizado y saltaría la receta.
Gestión eficaz de las dependencias
El manual Makefile arriba tiene un defecto serio: la dependencia de de ] es correcta, pero ¿qué hay de los encabezados? Si cambios, (que lo incluye) debe ser reconstruido, sin embargo la regla dice que sólo depende de . La solución es dejar que el compilador produzca la lista de dependencia real.
Generación automática de dependencia con GCC
GCC (y Clang) pueden generar información de dependencia utilizando la familia de banderas . La combinación más práctica para la mayoría de los proyectos es :
- – escribe un archivo de dependencia () durante la compilación, enumerando sólo los encabezados definidos por el usuario (no los encabezados del sistema).
- ] [ especifica el nombre del archivo de dependencia.
- ] añade objetivos falsos para cada encabezado, evitando errores cuando se elimina un encabezado.
Así es como incorporar el seguimiento automático de dependencia en un Makefile:
CC = gcc
CFLAGS = -Wall -Wextra -O2 -MMD -MP
SRCDIR = src
OBJDIR = obj
SRCS = $(wildcard $(SRCDIR)/*.c)
OBJS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS))
DEPS = $(OBJS:.o=.d)
all: myprogram
myprogram: $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
$(OBJDIR)/%.o: $(SRCDIR)/%.c
@mkdir -p $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
-include $(DEPS)
.PHONY: all clean
clean:
rm -rf $(OBJDIR) myprogram
Explicación:
- recopila todos los archivos en el directorio de origen.
- los transforma en caminos de archivo de objetos.
- enumera los archivos de dependencia (por ejemplo, ).
- La regla de patrón compila cada archivo y, debido a , genera un archivo como efecto secundario.
- La línea lee los archivos generados , convirtiéndolos en verdaderos requisitos de Makefile. La dash (]) suprime los errores cuando los archivos no existen todavía (por ejemplo, en la primera construcción).
Ahora, si se modifica, la siguiente invocación de ] se recompilará automáticamente porque el archivo para incluye como requisito previo.
Técnicas avanzadas
Manejo de las dependencias generadas de manera segura
Cuando se elimina un encabezado, el archivo todavía puede referirse a él, causando make para fallar con un error de destino desaparecido. La bandera se refiere a esto mediante la adición de reglas falsas vacías para cada encabezado de dependencia. Si el encabezado se ha ido, make
Incluyendo archivos de dependencia después de cambios de fuente
Una sutileza: si un archivo fuente añade o elimina un , el archivo correspondiente debe ser regenerado. Debido a que el archivo es en sí mismo un requisito del archivo objeto, ]make notará el timetamp cambiado y la recompilación, que regenera el archivo [F recursi4 ] completa una vez.
Usando Orden-Sólo Prerequisitos
A veces necesitas un directorio para existir antes de construir, pero no quieres que su timetamp active una reconstrucción. Ese es el papel de order-only requirements] (separado por ). En la regla del patrón anterior, usamos dentro de la receta; una alternativa es:
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@
$(OBJDIR):
mkdir -p $@
Esto asegura que el directorio se crea antes de cualquier compilación, pero un cambio en el directorio en sí (por ejemplo, un nuevo archivo dentro) no activará la recompilación.
Prácticas óptimas para la gestión de las dependencias
- Utiliza la generación de dependencia automática del primer día. Incluso para un proyecto de un solo fichero, es un buen hábito. No cuesta nada y evita errores futuros.
- Mantenga los archivos de dependencia separados de los archivos fuente.] Ponlos en un directorio o . Esto hace que la limpieza sea más fácil y evita que se arruine el árbol de origen.
- Incluya los archivos generados después de las reglas que los crean. La directiva está bien, pero situándolo después de la regla de patrón asegura que make primero sabe cómo construir archivos de objetos antes de intentar leer los archivos .
- Use falsos objetivos para el mantenimiento de la casa. ], ], y son comunes. Declararlos siempre con ]]].
- Variables de margen para banderas de compilador, directorios de fuentes y listas de archivos. Esto hace que el Makefile sea reutilizable en todos los proyectos y más fácil de personalizar.
- Prueba su Makefile con un cambio deliberado de encabezado. Modificar un encabezado, ejecutar ], y confirmar sólo los archivos de objeto afectados son recompilados. Si ocurre una reconstrucción completa, algo está mal con el seguimiento de la dependencia.
- Mantenga el Makefile simple pero no más simple de lo necesario. La ingeniería excesiva con funciones avanzadas como puede hacer que la depuración sea dolorosa. Comience con el patrón mostrado en este artículo; escala bien a docenas de archivos.
Recursos externos
Para profundizar su comprensión, consulte estas referencias autorizadas:
- GNU Hacer manual] – La guía definitiva para hacer sintaxis, funciones y características avanzadas.
- Opciones de Preprocesador de GCC ] – Documentación para , , y banderas relacionadas.
- Makefile Tutorial de Chase Lambert – Un tutorial práctico y bien estructurado que abarca muchos casos de uso del mundo real.
Conclusión
Gestionar dependencias del programa C con Makefiles no es un lujo, sino una necesidad para cualquier proyecto que supere un solo archivo. Al combinar reglas de patrón, generación de dependencia automática con , y la inclusión cuidadosa de los archivos generados , puede crear un sistema de construcción que sea rápido y correcto. Las técnicas mostradas aquí eliminan el seguimiento manual, reducen los tiempos de compilación, y evitan los errores causados por los archivos de gestión manual de pago.