organizar proyectos en varios ficheros y arreglar programas que "compilan pero fallan".
Incluye 3 ejercicios con pistas y solución y un quiz de 5 preguntas. Tiempo estimado: 80 minutos.
1. El preprocesador
Se ejecuta antes de compilar y trabaja con texto. Todas sus líneas empiezan por #.
#include <stdio.h> // busca en las carpetas del sistema (librerías estándar)
#include "mates.h" // busca primero en tu carpeta (tus headers)
#define MAX_ALUMNOS 30 // constante: se sustituye el texto
#define MAX(a, b) (((a) > (b)) ? (a) : (b)) // macro con parámetros
#ifdef DEBUG // compilación condicional
printf("depuracion: x=%d\n", x);
#endif
Compila con depuración activa: gcc -DDEBUG prog.c.
Macros: la trampa del texto
#define CUADRADO(x) x*x
CUADRADO(a+b) // → a+b*a+b ¡mal!
#define CUADRADO(x) ((x) * (x)) // parentesis en cada parametro y en el total
Y aún así: CUADRADO(i++) evalúa i++ dos veces. Para funciones simples prefiere static inline.
2. Dividir en ficheros: .h y .c
| Fichero | Contiene | Se incluye / compila |
|---|---|---|
mates.h |
declaraciones: prototipos, tipos, macros, extern |
se #include donde se use |
mates.c |
definiciones: el código de las funciones | se compila una vez |
main.c |
usa la librería |
mates.h
#ifndef MATES_H // include guard
#define MATES_H
int cuadrado(int x);
#endif
mates.c
#include "mates.h" // comprueba que firma y definición coinciden
int cuadrado(int x) { return x * x; }
main.c
#include <stdio.h>
#include "mates.h"
int main(void) { printf("%d\n", cuadrado(5)); }
Compilar:
gcc -Wall -Wextra main.c mates.c -o prog
Los include guards
#ifndef X / #define X / #endif evitan que un header se procese dos veces si lo incluyen varios ficheros (daría errores de "redefinición").
Compilar por separado (proyectos grandes)
gcc -c main.c # → main.o (solo compila)
gcc -c mates.c # → mates.o
gcc main.o mates.o -o prog # enlaza
Así, si solo cambias mates.c, solo recompilas ése. Eso automatiza make:
prog: main.o mates.o
gcc main.o mates.o -o prog
%.o: %.c
gcc -Wall -Wextra -c $< -o $@
(Las líneas de comandos empiezan con TAB.)
static y extern
staticen una función/variable global: privada de ese .c.extern int contador;en el .h +int contador;en un solo .c: variable global compartida (úsalo con moderación).
Tipos opacos (TAD)
En el header: typedef struct Pila Pila; y solo las funciones. El struct Pila {...} completo se define en pila.c. Los usuarios no pueden tocar los campos → puedes cambiar la implementación sin romper nada (lo harás en el módulo 12).
3. Errores típicos de enlace
| Mensaje | Causa |
|---|---|
undefined reference to 'foo' |
declaraste foo pero no compilaste/enlazaste el .c que la define |
multiple definition of 'foo' |
definiste (no solo declaraste) algo en un .h incluido por varios .c |
implicit declaration of function |
falta #include o prototipo |
4. Depuración: el método científico
Cuando un programa compila pero da resultados erróneos:
- Reproduce el fallo con el ejemplo más pequeño posible.
- Predice a mano qué debería pasar, paso a paso.
- Observa: pon
printf(astderr) en puntos clave y compara lo previsto con lo real. El primer punto donde difieren acota el bug. - Una hipótesis cada vez: cambia una cosa, prueba, repite.
- Explícaselo a un patito de goma 🦆: dicho en voz alta, el error salta solo.
Herramientas:
* Warnings: -Wall -Wextra (¡léelos!).
* Sanitizers: -fsanitize=address,undefined -g detecta desbordamientos, use-after-free, UB aritmético.
* Depurador gdb: gcc -g prog.c -o prog && gdb ./prog → break main, run, next, step, print x, bt (traza tras un crash), watch x, quit.
* assert: #include <assert.h>; assert(n > 0); aborta con el sitio exacto si se rompe una suposición.
Bugs típicos de lógica
Off-by-one en bucles · confundir = y == · división entera · índices desde 0 vs 1 · variable sin inicializar · olvidar el \0.
Prueba esto
- Convierte uno de tus ejercicios previos en proyecto de 3 ficheros.
- Escribe un
Makefiley ejecutamake. - Mete a propósito un
a[10]en un array de 5 y compila con-fsanitize=addresspara ver el informe.
Ejercicios prácticos del módulo 11
Programar se aprende programando: intenta cada ejercicio antes de mirar las pistas, y usa la solución solo para comparar. Marca los que resuelvas y tu progreso se guarda en este navegador.
11.1 Tu primera libreria (.h + .c)
Entrada de ejemplo
5
Salida esperada
cuadrado=25 cubo=125 abs=5
Tu punto de partida
mates.h
#ifndef MATES_H
#define MATES_H
long long cuadrado(int x);
long long cubo(int x);
int valor_abs(int x);
#endifmates.c
#include "mates.h"
long long cuadrado(int x) {
// TODO
return 0;
}
long long cubo(int x) {
// TODO
return 0;
}
int valor_abs(int x) {
// TODO
return 0;
}main.c
#include <stdio.h>
#include "mates.h"
int main(void) {
int n;
scanf("%d", &n);
printf("cuadrado=%lld cubo=%lld abs=%d\n", cuadrado(n), cubo(n), valor_abs(n));
return 0;
}Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.
Pista 1
mates.c incluye su propio header ("mates.h", con comillas) para que el compilador compruebe que las firmas coinciden.
Pista 2
Haz el cast a long long ANTES de multiplicar para evitar desbordar el int.
Ver solución (inténtalo antes)
mates.c
#include "mates.h"
long long cuadrado(int x) {
return (long long)x * x;
}
long long cubo(int x) {
return (long long)x * x * x;
}
int valor_abs(int x) {
return x < 0 ? -x : x;
}11.2 Macros sin trampas
Entrada de ejemplo
3 5
Salida esperada
max=5 min=3 doble_max=10 doble_min=6 cuadrado=64
Tu punto de partida
macros.h
#ifndef MACROS_H
#define MACROS_H
// TODO: #define MAX(a, b) ...
// TODO: #define MIN(a, b) ...
// TODO: #define CUADRADO(x) ...
#endifmain.c
#include <stdio.h>
#include "macros.h"
int main(void) {
int a, b;
scanf("%d %d", &a, &b);
printf("max=%d min=%d\n", MAX(a, b), MIN(a, b));
printf("doble_max=%d doble_min=%d\n", 2 * MAX(a, b), 2 * MIN(a, b));
printf("cuadrado=%d\n", CUADRADO(a + b));
return 0;
}Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.
Pista 1
Una macro es SUSTITUCION DE TEXTO: CUADRADO(a+b) con x*x se convierte en a+b*a+b.
Pista 2
Parentesis en los parametros: ((x) * (x)); y en la expresion completa para que 2 * MAX(...) no se rompa.
Ver solución (inténtalo antes)
macros.h
#ifndef MACROS_H
#define MACROS_H
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define CUADRADO(x) ((x) * (x))
#endif11.3 Depura: caza los bugs logicos
Entrada de ejemplo
6 0 5 -3 8 -1 9
Salida esperada
positivos=3 suma_neg=-4 pos_max=6
Tu punto de partida
#include <stdio.h>
int main(void) {
int v[100], n;
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &v[i]);
int positivos = 0, suma_neg = 0, pos_max = 0;
for (int i = 0; i < n - 1; i++) {
if (v[i] >= 0) positivos++;
if (v[i] < 0) suma_neg += v[i];
if (v[i] > v[pos_max]) pos_max = i;
}
printf("positivos=%d suma_neg=%d pos_max=%d\n", positivos, suma_neg, pos_max);
return 0;
}Cópialo en tu editor, complétalo, compílalo (gcc -Wall -Wextra programa.c -o programa) y prueba la entrada de ejemplo. Para que se corrija solo con varios casos de prueba, descarga el curso.
Pista 1
Bug 1: el bucle principal se deja un elemento sin procesar (mira la condicion).
Pista 2
Bug 2: el enunciado dice 'estrictamente mayor que 0'; el 0 no es positivo.
Pista 3
Bug 3: los indices en C empiezan en 0, pero la salida pide posiciones desde 1.
Ver solución (inténtalo antes)
#include <stdio.h>
int main(void) {
int v[100], n;
scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &v[i]);
int positivos = 0, suma_neg = 0, pos_max = 0;
for (int i = 0; i < n; i++) {
if (v[i] > 0) positivos++;
if (v[i] < 0) suma_neg += v[i];
if (v[i] > v[pos_max]) pos_max = i;
}
printf("positivos=%d suma_neg=%d pos_max=%d\n", positivos, suma_neg, pos_max + 1);
return 0;
}Quiz del módulo 11
5 preguntas para comprobar que has fijado los conceptos. Las opciones se barajan cada vez.