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

Si nos limitamos a crear archivos de cabecera sin ningún tipo de medida de control, podrá ocurrir que un archivo de cabecera se incluya múltiples veces en una misma unidad de compilación. Vamos a ver cómo usar los guards para evitar que eso pase y que tengamos problemas de redefinición de elementos.

Cuando trabajamos con programas en C que se dividen en múltiples archivos, es fundamental entender cómo compartir correctamente las declaraciones entre ellos para evitar problemas de compilación. En este contexto, los archivos de cabecera .h juegan un papel crucial, ya que nos permiten exportar la interfaz de un módulo para que otros puedan utilizar sus funciones sin necesidad de conocer su implementación interna.

Sin embargo, un detalle muy importante que debemos tener en cuenta al crear estos archivos de cabecera es la protección contra inclusiones múltiples, que es donde entran en juego los llamados guards. Estos guards evitan que una misma región de código se compile más de una vez, lo cual es un problema común cuando un archivo .h es incluido varias veces, directa o indirectamente, en un proyecto.

Imaginemos que tenemos un módulo para manejar empleados, con su archivo empleados.h donde definimos, por ejemplo, un enum para cargos y una estructura para representar a un empleado. Ahora, si creamos otro módulo para gestionar sueldos, con sus propios archivos sueldos.c y sueldos.h, y en este último incluimos empleados.h para conocer la definición de empleado, podemos encontrarnos con un problema. Cuando en el archivo principal main.c incluimos tanto empleados.h como sueldos.h, el preprocesador copia literalmente el contenido de estos archivos en el lugar de los #include. Esto puede provocar que las definiciones de cargo_t, empleado_t y funciones como print_empleado se dupliquen, generando errores de redeclaración durante la compilación.

Para evitar este problema, utilizamos los guards mediante directivas del preprocesador como #ifndef, #define y #endif. La idea es envolver todo el contenido del archivo .h dentro de una condición que compruebe si un identificador único ha sido definido previamente. Si no lo ha sido, se define y se incluye el contenido; si ya está definido, el preprocesador ignora el contenido, evitando así las redefiniciones.

Por ejemplo, en empleados.h podemos hacer lo siguiente:

#ifndef EMPLEADOS_H
#define EMPLEADOS_H

typedef enum {
    DIRECTOR,
    GERENTE,
    ADMINISTRATIVO
} cargo_t;

typedef struct {
    char nombre[50];
    char apellido[50];
    cargo_t cargo;
} empleado_t;

void print_empleado(empleado_t e);

#endif // EMPLEADOS_H

De esta forma, cuando el preprocesador procese múltiples inclusiones de empleados.h, solo la primera incluirá el contenido, y las siguientes serán ignoradas, evitando errores de compilación.

Podemos aplicar la misma técnica en sueldos.h para proteger su contenido. Así, al compilar, el preprocesador manejará correctamente las inclusiones y el compilador no encontrará definiciones duplicadas.

Existe también una alternativa más moderna y sencilla llamada #pragma once. Esta directiva indica al preprocesador que solo debe incluir ese archivo una vez, sin necesidad de definir identificadores únicos ni envolver el contenido en condiciones. Su uso es tan simple como colocarla al inicio del archivo .h:

#pragma once

typedef void (*pagar_sueldo_func)(empleado_t e);

void pagar_sueldo(empleado_t e);

Aunque #pragma once es soportada por la mayoría de compiladores modernos como GCC y Visual C++, no es parte del estándar oficial de C, por lo que su compatibilidad puede variar. Por eso, es recomendable verificar si el compilador que usamos la soporta antes de adoptarla.

En resumen, para manejar correctamente proyectos en C con múltiples archivos y evitar problemas de redefinición, debemos proteger nuestros archivos de cabecera usando guards con #ifndef/#define/#endif o, si el compilador lo permite, con #pragma once. Esto garantiza que cada archivo .h se incluya una única vez durante la compilación, facilitando la modularidad y el mantenimiento del código.

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