Modularidad, headers y linkage
Cómo se parte un programa en varios archivos: unidades de traducción, la separación header/implementación, y el linkage interno vs externo que controla la visibilidad.
Un programa real no cabe en un archivo. Dividirlo bien —qué va en el header, qué en el .c, qué se exporta y qué se oculta— es lo que hace un código mantenible. Y conecta directamente con el proceso de compilación: cada archivo es una unidad de traducción que el linker luego une.
- Unidades de traducción y compilación separada.
- La separación header (.h) / implementación (.c).
- Linkage interno vs externo.
- Buenas prácticas de headers.
Unidades de traducción
Cada archivo .c es una unidad de traducción: se compila de forma independiente a un .o (recuerda el nivel 3). El linker luego une todos los .o. Esto permite compilar solo lo que cambió, no todo el programa.
gcc -c mates.c -o mates.o # compila una unidad
gcc -c main.c -o main.o # otra, independiente
gcc mates.o main.o -o programa # el linker las une
Header e implementación
La convención universal: el header (.h) declara la interfaz (qué existe); el .c define la implementación (cómo funciona). Los demás archivos incluyen el header, no el .c.
// mates.h — la interfaz pública
#pragma once
int sumar(int a, int b); // declaración
extern int contador_global; // variable compartida (declarada)
// mates.c — la implementación
#include "mates.h"
int contador_global = 0; // definición (una sola)
int sumar(int a, int b) { return a + b; }
// main.c — el consumidor
#include "mates.h" // ve las declaraciones, no la implementación
int main(void) { return sumar(2, 3); }
El error mental de novato es pensar que #include "x.h" “trae el código de x”. No: trae declaraciones — promesas de que esas funciones y variables existen en algún .o. El header es el contrato entre módulos; el .c lo cumple; el linker verifica que cada promesa tiene su definición. Por eso un header debe contener declaraciones (prototipos, tipos, macros) y casi nunca definiciones (código o variables globales), que provocarían “definición múltiple” al incluirse en varios sitios. Entender esta separación es entender cómo se ensambla un programa grande — y por qué el famoso “undefined reference” (nivel 3) significa “prometiste algo en un header que ningún .c definió”.
Linkage: qué se ve desde fuera
Cada símbolo global tiene un linkage que decide su visibilidad entre unidades de traducción:
| Declaración | Linkage | Significado |
|---|---|---|
int x; (global) |
externo | visible desde otros .c |
static int x; (global) |
interno | privado de este .c |
extern int x; |
externo | “definido en otro .c” |
| función normal | externo | exportada |
función static |
interno | privada del módulo |
// utils.c
static int estado = 0; // privado: ningún otro .c lo ve
static void ayudante(void) { } // función interna del módulo
int api_publica(void) { return 0; } // exportada
La regla de oro de la modularidad en C: marca static todo lo que no forme parte de la API pública de tu módulo — funciones auxiliares, variables de estado interno. Esto tiene tres beneficios: evita colisiones de nombres entre archivos, deja clarísimo qué es la interfaz pública (lo que NO es static), y permite al compilador optimizar mejor (sabe que nadie externo llama a esa función). Un módulo bien hecho expone poco (unas pocas funciones no-static en su header) y oculta mucho (todo lo demás, static). Menos superficie pública = menos acoplamiento = código más fácil de cambiar.
Higiene de headers
Guard siempre
#pragma once o include guards para evitar inclusión doble.
Incluye lo que usas
Cada archivo incluye los headers de lo que usa, sin depender de inclusiones transitivas.
Headers ligeros
En el header, declaraciones y tipos; nada de código pesado. Menos dependencias, compilación más rápida.
Oculta con opacos
Expón tipos opacos (nivel 12) cuando puedas: el header no revela la estructura interna.
- Separa un programa en
mates.h/mates.c/main.cy compílalo por unidades. - Compila solo un
.otras cambiar un archivo, y reenlaza; observa que no recompilas todo. - Marca
staticuna función auxiliar y comprueba que otro.cno puede llamarla. - Provoca un “undefined reference” declarando algo en el header que ningún
.cdefine, y relaciónalo con el linker.