Archivos de cabecera y múltiples .c (parte 1)

Cuando un proyecto es grande, es más que apropiado estructurarlo en múltiples archivos .c para un tratamiento más cómodo, sobre todo si trabajan muchas personas en ese proyecto. En el caso de C, cuando empecemos a trocear un archivo habrá que prestar atención a las declaraciones compartidas de tipos y funciones, que son los typedefs, y que tendremos que declarar en archivos de cabecera.

Cuando trabajamos en proyectos en C, pronto nos damos cuenta de que manejar todo el código en un único archivo puede convertirse en un caos. Imaginemos que tenemos un programa que gestiona empleados, con estructuras y funciones específicas para ellos. Si solo tenemos una función, como printempleado, y todo está en un solo archivo, la cosa es sencilla. Pero, ¿qué pasa cuando el proyecto crece y necesitamos gestionar sueldos, ventas, facturas o cualquier otro módulo? Abrir un archivo de 700 líneas para buscar una función específica puede ser una tarea agotadora.

La solución más natural es dividir el proyecto en varios archivos .c, agrupando las funciones y estructuras relacionadas. Por ejemplo, podríamos tener empleados.c para todo lo relacionado con empleados, ventas.c para ventas, y así sucesivamente. Los entornos de desarrollo integrados (IDE) como CodeBlocks facilitan mucho esta gestión, permitiéndonos trabajar con múltiples archivos sin complicaciones aparentes.

Sin embargo, al compilar, nos encontramos con un detalle importante: el compilador procesa cada archivo .c de forma independiente. Esto significa que si en main.c intentamos usar la estructura empleado o la función printempleado definidas en empleados.c, el compilador no las reconocerá porque no comparten información entre sí. Por ejemplo, si intentamos compilar main.c sin más, obtendremos errores indicando que el tipo empleado no está definido.

Una idea que podría surgir es incluir directamente el archivo empleados.c dentro de main.c usando #include "empleados.c". Pero esto no es recomendable. El preprocesador simplemente copia y pega el contenido, lo que provoca que al compilar y enlazar el proyecto, el enlazador encuentre definiciones duplicadas de funciones como printempleado. Esto genera errores porque no podemos tener dos funciones con el mismo nombre en el proyecto.

Para resolver este problema, necesitamos una forma de compartir las definiciones de tipos y los prototipos de funciones sin duplicar implementaciones. Aquí es donde entran en juego los archivos de cabecera, con extensión .h. Estos archivos contienen las declaraciones necesarias para que el compilador conozca la existencia de tipos y funciones, pero sin incluir su implementación.

Un prototipo de función es simplemente la declaración de la función sin su cuerpo, terminada en punto y coma. Por ejemplo, para la función printempleado que recibe un parámetro de tipo empleado, el prototipo sería:

void printempleado(empleado e);

Esto le indica al compilador que existe una función con ese nombre y esa firma, aunque su implementación se encuentre en otro archivo.

Así, creamos un archivo empleados.h donde colocamos el typedef de la estructura empleado y los prototipos de las funciones relacionadas. Luego, en ambos archivos main.c y empleados.c, incluimos este archivo de cabecera con:

#include "empleados.h"

De esta forma, tanto main.c como empleados.c conocen la estructura y las funciones, pero la implementación real de las funciones solo está en empleados.c. Esto permite que cada archivo se compile de forma independiente y que el enlazador pueda unirlos correctamente sin conflictos.

Por ejemplo, el archivo empleados.h podría tener:

#ifndef EMPLEADOS_H
#define EMPLEADOS_H

typedef struct {
    char nombre[50];
    int num;
} empleado;

void printempleado(empleado e);

#endif

Y en empleados.c implementamos la función:

#include <stdio.h>
#include "empleados.h"

void printempleado(empleado e) {
    printf("Nombre: %s\n", e.nombre);
    printf("Número: %d\n", e.num);
}

Mientras que en main.c usamos:

#include "empleados.h"

int main() {
    empleado e = {"Pepito Pérez", 1};
    printempleado(e);
    return 0;
}

Con esta organización, el proyecto es mucho más modular y legible. Podemos añadir más módulos, como ventas o sueldos, cada uno con su propio .c y .h, manteniendo el código ordenado y fácil de mantener.

Eso sí, aunque los archivos de cabecera son muy útiles, también pueden complicar la compilación si no se gestionan bien, especialmente cuando hay múltiples dependencias entre ellos. Por eso, es importante seguir buenas prácticas y entender bien cómo funcionan para evitar problemas en proyectos más grandes. Pero eso lo dejaremos para una próxima ocasión.

Lista de reproducción
  1. 1
    Instalar CodeBlocks
    15 minutos
  2. 2
    Funciones y hola mundo
    17 minutos
  3. 3
    Variables y tipos de datos
    17 minutos
  4. 4
    Condicionales y operadores lógicos
    16 minutos
  5. 5
    Bucles
    11 minutos
  6. 6
    Punteros
    12 minutos
  7. 7
    Arrays
    14 minutos
  8. 8
    Estructuras
    12 minutos
  9. 9
    Otras construcciones de C
    9 minutos
  10. 10
    Memoria dinámica
    8 minutos
  11. 11
    El preprocesador (parte 1)
    10 minutos
  12. 12
    El preprocesador (parte 2)
    9 minutos
  13. 13
    Archivos de cabecera y múltiples .c (parte 1)
    8 minutos
  14. 14
    Archivos de cabecera y múltiples .c (parte 2)
    9 minutos
  15. 15
    C desde la línea de comandos (parte 1)
    8 minutos
  16. 16
    C desde la línea de comandos (parte 2)
    9 minutos
  17. 17
    Break y continue
    10 minutos
  18. 18
    Goto
    13 minutos
  19. 19
    Manipulación de bits
    15 minutos
  20. 20
    Máscaras de bit
    19 minutos
  21. 21
    Archivos (1): fopen y fclose
    13 minutos
  22. 22
    Archivos (2): leer con fgetc
    9 minutos
  23. 23
    Archivos (3): fseek y ftell
    11 minutos
  24. 24
    Archivos (4): leer con fgets
    9 minutos
  25. 25
    Archivos (5): fputc y fputs
    7 minutos
  26. 26
    Archivos (6): volcar en archivos con fwrite
    10 minutos
  27. 27
    Archivos (7): fread, fwrite y los arrays
    10 minutos
  28. 28
    Archivos (8): entrada estándar y salida estándar
    9 minutos
  29. 29
    Archivos (9): buffers
    14 minutos
  30. 30
    Archivos (y 10): otras funciones útiles con archivos
    5 minutos
  31. 31
    printf (1)
    18 minutos
  32. 32
    printf (parte 2)
    12 minutos
  33. 33
    scanf (parte 1)
    17 minutos
  34. 34
    scanf (parte 2)
    17 minutos
  35. 35
    fprintf, sprintf y snprintf
    8 minutos
  36. 36
    Tipos de datos opacos
    13 minutos
  37. 37
    Bibliotecas estáticas
    13 minutos
  38. 38
    Bibliotecas dinámicas
    15 minutos
  39. 39
    Más flags: i mayúscula (include), wall, werror, pedantic...
    12 minutos
  40. 40
    pkg-config
    12 minutos
  41. 41
    Make
    17 minutos
  42. 42
    GDB
    21 minutos
  43. 43
    Variables globales
    6 minutos
  44. 44
    extern
    9 minutos
  45. 45
    Funciones variádicas
    12 minutos
  46. 46
    El optimizador de GCC y la opción -O
    12 minutos
  47. 47
    Volatile
    6 minutos