Catto / Mapa de Temas · Informática II 2do nivel
Informática II · 120 h · Contenido 3 de 8

Herramientas de construcción de software

Entre el archivo de texto y el programa que corre hay cuatro etapas, y cada una tira sus propios errores. Saber cuál falló ahorra la mitad del tiempo de depuración.

Preprocesador Compilador Enlazador make git

01Las cuatro etapas

Escribir gcc main.c -o programa parece un solo paso, pero son cuatro programas encadenados. Saber cuál falló es la mitad de la depuración:

EtapaEntraSaleError típico
Preprocesador.c.iNo encuentra un encabezado
Compilador.i.s (ensamblador)Error de sintaxis o de tipos
Ensamblador.s.o (objeto)Rara vez falla
Enlazador.o y bibliotecasEjecutableReferencia indefinida
Cómo distinguir un error de compilación de uno de enlazado

El de compilación menciona archivo y línea: el compilador está leyendo tu código. El de enlazado dice undefined reference to 'algo' y no señala ninguna línea: el código compiló bien, pero falta el cuerpo de una función. Casi siempre es una biblioteca que no se agregó —el clásico -lm para las funciones matemáticas— o un archivo que quedó fuera de la compilación.

Laboratorio · qué hace cada etapa

Recorré las cuatro etapas y mirá en qué se va convirtiendo el archivo, con el comando que ejecuta cada una.

02Compilación separada y bibliotecas

Cada .c se compila por separado en su .o, y el enlazador los junta. La ventaja es concreta: si se toca un solo archivo, se recompila sólo ese.

gcc -c main.c → main.o
gcc -c sensor.c → sensor.o
gcc main.o sensor.o -o programa -lm
Tipo de bibliotecaArchivoCómo se usa
Estática.a en Linux, .lib en WindowsSe copia dentro del ejecutable al enlazar
Dinámica.so en Linux, .dll en WindowsSe carga al ejecutar; se comparte entre programas

La estática da un ejecutable autónomo y más grande; la dinámica, uno chico que depende de que la biblioteca esté instalada. Para un equipo embebido casi siempre conviene la estática: menos sorpresas en el campo.

03make

Cuando hay más de tres archivos, recompilar a mano es inviable. make lee un archivo de reglas y rehace sólo lo que quedó desactualizado, comparando fechas de modificación:

CC = gcc
CFLAGS = -Wall -Wextra -O2

programa: main.o sensor.o
    $(CC) main.o sensor.o -o programa -lm

main.o: main.c sensor.h
    $(CC) $(CFLAGS) -c main.c

clean:
    rm -f *.o programa

Cada regla tiene un objetivo, sus dependencias y las órdenes para construirlo, que van indentadas con un tabulador de verdad. Si sensor.h cambia, make sabe que main.o quedó viejo y lo rehace; si no cambió nada, no hace nada.

Banderas que conviene usar siempre

-Wall -Wextra encienden las advertencias, y hay que leerlas: la mayoría de los errores en tiempo de ejecución fueron antes una advertencia ignorada. -g agrega información para el depurador, -O2 optimiza, y -std=c11 fija la versión del lenguaje para que el programa se comporte igual en otra máquina.

04Control de versiones

Un sistema de control de versiones guarda la historia del proyecto: qué cambió, cuándo y por qué. Con git, el ciclo básico es corto:

OrdenQué hace
git initCrea el repositorio
git statusQué cambió desde el último commit
git add archivoMarca lo que va a entrar en el próximo commit
git commit -m "…"Guarda una versión con su explicación
git logLa historia
git diffLas diferencias exactas, línea por línea
git checkout -b ramaAbre una línea de trabajo paralela

Tres criterios que valen más que la lista de órdenes: commits chicos que hagan una sola cosa, mensajes que expliquen el porqué y no el qué —el diff ya dice qué cambió—, y un .gitignore que deje afuera lo generado: los .o, el ejecutable y los archivos temporales no se versionan.

05Depurar

  • printf. Rudimentario pero efectivo, sobre todo en sistemas embebidos donde no hay depurador.
  • gdb. Compilando con -g: break para poner un punto de parada, run, next y step para avanzar, print para ver una variable y backtrace para saber cómo se llegó hasta ahí. Con un programa que se cayó, backtrace sobre el volcado de memoria suele resolver el caso en un minuto.
  • valgrind. Detecta accesos fuera de rango, uso de memoria sin inicializar y pérdidas de memoria. Un programa que parece andar bien puede estar lleno de errores que valgrind muestra.
  • Sanitizers. -fsanitize=address instrumenta el programa para que aborte en el momento exacto del acceso indebido, con la línea señalada.
El error que no se reproduce

Un programa que falla «a veces» casi siempre tiene memoria mal usada: un arreglo desbordado o un puntero colgante. El síntoma aparece lejos de la causa, y por eso las herramientas de análisis valen más que horas de lectura del código.

06En el laboratorio

Actividad 1 · Ver cada etapa

Con un programa de diez líneas, correr gcc -E, -S y -c y mirar los archivos intermedios. Contar cuántas líneas tiene el .i después de incluir stdio.h.

Actividad 2 · Un Makefile propio

Armar un proyecto de tres archivos con su Makefile, con objetivos all, clean y debug. Verificar que al tocar un solo .c se recompila sólo ese.

Actividad 3 · Provocar y leer errores

Generar a propósito un error de cada etapa: un #include inexistente, un punto y coma faltante y una función declarada pero no definida. Anotar qué dice exactamente cada mensaje y aprender a reconocerlos.

07Errores frecuentes

  • Confundir error de compilación con error de enlazado.
  • Olvidar -lm al usar funciones matemáticas.
  • Indentar las órdenes de un Makefile con espacios en lugar de un tabulador.
  • Ignorar las advertencias del compilador.
  • Versionar archivos generados —objetos, ejecutables, carpetas de compilación— y ensuciar la historia.
  • Depurar un binario compilado sin -g y no ver ni nombres de variables.

08Autoevaluación

¿Qué hace exactamente el preprocesador?

Manipula texto: pega los archivos incluidos, reemplaza las macros y resuelve la compilación condicional. No entiende C.

Aparece undefined reference to 'sqrt'. ¿Qué etapa falló?

El enlazado. Falta agregar -lm: el prototipo estaba en math.h, pero el cuerpo de la función está en la biblioteca matemática.

¿Para qué sirven los guardas #ifndef en un encabezado?

Para que el contenido no se incluya dos veces en la misma unidad de compilación, lo que provocaría definiciones duplicadas.

¿Cómo sabe make qué recompilar?

Comparando fechas de modificación: si una dependencia es más nueva que el objetivo, rehace ese objetivo.

¿Qué diferencia hay entre una biblioteca estática y una dinámica?

La estática se copia dentro del ejecutable al enlazar; la dinámica se carga al ejecutar y se comparte entre programas.

¿Qué conviene poner en un mensaje de commit?

El porqué del cambio. El qué ya lo muestra el diff.

09Para ampliar

  • Brian W. Kernighan y Rob Pike. The Practice of Programming. Addison-Wesley, 1999. Depuración, pruebas y herramientas, con el criterio de dos autores que escribieron Unix.
  • Scott Chacon y Ben Straub. Pro Git. 2.ª ed., Apress, 2014. Disponible en línea y en castellano. El libro de git, de lo básico al flujo de trabajo en equipo.
  • John Graham-Cumming. The GNU Make Book. No Starch Press, 2015. Para cuando el Makefile crece y empieza a tener lógica propia.
  • Randal E. Bryant y David R. O'Hallaron. Computer Systems: A Programmer's Perspective. 3.ª ed., Pearson, 2015. El capítulo 7 explica el enlazado con un detalle que no está en ningún otro lado.
Desarrollo del contenido «Herramientas de construcción de software» de Informática II (segundo nivel), según el diseño curricular de Ingeniería Electrónica, Plan 2023 — Ordenanza N° 1849 del Consejo Superior de la UTN. Volver al Mapa de Temas · catto.ar