Regla 0x5012h: Detección de inclusiones cíclicas entre archivos de cabecera
Compilacion, preprocesador y seguridad (0x50XX)
0x5012h: Detección de inclusiones cíclicas entre archivos de cabecera¶
Enunciado normativo¶
NO DEBEN incluirse mutuamente dos cabeceras. Si dos tipos se necesitan recíprocamente, se usa una declaración adelantada (
struct nodo;) o se extrae el tipo común a una tercera cabecera.
¿Por qué existe esta regla?¶
El problema¶
Cuando a.h incluye b.h y b.h incluye a.h, el preprocesador entra en un
ciclo. La guarda lo corta, pero deja a una cabecera procesada a medias:
a.h empieza, incluye b.h, y b.h vuelve a a.h cuyo macro ya está definido,
así que no vuelve a entrar y sigue con declaraciones que aún no existían. El
tipo de a.h no está disponible dentro de b.h y el compilador reporta «tipo
incompleto» en un archivo que parece correcto. La guarda evita la recursión
infinita, pero no resuelve la dependencia circular: sólo la esconde.
Consecuencias de violarla¶
| Tipo de consecuencia | Efecto concreto |
|---|---|
| Compilación | incomplete type / unknown type name en una de las dos cabeceras. |
| Fragilidad | El resultado depende del orden de los #include del cliente. |
| Mantenibilidad | Los módulos quedan acoplados en ambas direcciones sin mapa claro. |
Fundamento en el estándar y en la cátedra¶
ISO/IEC 9899:2011 §6.2.5 distingue los tipos completos de los incompletos y
§6.7.2.3 permite declarar una struct sin definirla (struct nodo;), lo que
habilita punteros a ella antes de tener el cuerpo. §6.10.2 describe la inclusión
como copia textual, origen del ciclo. La cátedra exige romperlo con
declaraciones adelantadas o un tipo compartido: no dependen del orden.
Alcance y excepciones¶
Aplica a ciclos directos (a.h ↔ b.h) e indirectos (a.h → b.h → c.h →
a.h). Excepción: un ciclo que sólo involucra punteros se resuelve con
una declaración adelantada y no es una violación. Se prohíbe necesitar el tipo
completo (por valor, para sizeof o para acceder a campos) de una cabecera que
a su vez depende de la primera.
Ejemplos exhaustivos¶
❌ Contraejemplo 1 — Ciclo directo¶
/* a.h */
#include "b.h"
struct a { struct b *puntero_b; };/* b.h */
#include "a.h"
struct b { struct a *puntero_a; };Por qué falla: al procesar a.h se incluye b.h, que vuelve a a.h; la guarda
la saltea y b.h intenta usar struct a cuando todavía no fue definida. Según
cuál se incluya primero, una de las dos estructuras queda incompleta.
❌ Contraejemplo 2 — Dependencia por valor¶
/* fecha.h */
#include "evento.h"
struct fecha { struct evento evento; };
/* evento.h */
#include "fecha.h"
struct evento { struct fecha fecha; };Por qué falla: además del ciclo, cada estructura contiene a la otra por valor, algo imposible en C porque el tamaño sería infinito. El compilador no puede determinarlo y aborta.
✅ Ejemplo conforme 1 — Declaración adelantada¶
/* b.h */
#ifndef B_H
#define B_H
struct a;
struct b { struct a *puntero_a; };
#endifstruct a; declara el tipo incompleto y basta para guardar un puntero. Para
acceder a los campos se incluye a.h en el .c, no en la cabecera.
✅ Ejemplo conforme 2 — Tipo común extraído¶
/* nodo.h */
#ifndef NODO_H
#define NODO_H
struct nodo { int dato; struct nodo *sig; };
#endiflista.h y pila.h incluyen nodo.h y ninguno incluye al otro. El
acoplamiento es en estrella hacia el tipo compartido, sin ciclos.
⚠️ Casos límite¶
Sólo punteros: el ciclo se rompe con
struct tag;; conviene igual extraer el tipo si la dependencia es recíproca.typedefy declaración adelantada: en C11 no se puede adelantar untypedefanónimo; usástruct tagy eltypedefdespués.Guarda presente: no elimina la violación, sólo el bucle infinito.
Cómo detectarla¶
| Herramienta | Comando | Señal |
|---|---|---|
gaff | gaff check *.h | Reporta 0x5012h al detectar el ciclo en el grafo de inclusiones. |
gcc | gcc -H -E archivo.c | Se repite la ruta de cabeceras y falta procesar una parte. |
Checklist de autocontrol¶
¿Alguna cabecera incluye a otra que la incluye de vuelta?
¿Puedo reemplazar esa dependencia por
struct tag;más punteros?¿Conviene extraer el tipo común a una tercera cabecera?
¿Probé compilar el módulo incluyendo las cabeceras en distinto orden?
Reglas relacionadas¶
0x5003h: Utilizá guardas de inclusión en todos los archivos de cabecera — la guarda corta la recursión, pero no arregla el diseño circular.
0x500Fh: Colisión de nombres de macroguardas entre archivos de cabecera distintos — una colisión de guardas puede simular un ciclo.
0x500Ch: Prohibición de inclusión directa de archivos de código fuente C (.c) — los
.cse compilan aparte y no se incluyen entre sí.