En algunos entornos, la compilación será más rápida si solo se incluyen los archivos de encabezado que se necesitan. En otros entornos, la compilación se optimizará si todos los archivos de origen pueden usar la misma colección primaria de encabezados (algunos archivos pueden tener encabezados adicionales más allá del subconjunto común). Idealmente, los encabezados deben construirse para que múltiples operaciones #include no tengan ningún efecto. Puede ser bueno rodear las declaraciones #include con comprobaciones de la protección de inclusión del archivo a incluir, aunque eso crea una dependencia en el formato de esa protección. Además, dependiendo del comportamiento de almacenamiento en caché de archivos de un sistema, un #include innecesario cuyo objetivo termine siendo completamente # ifdef'ed away puede no tardar mucho.
Otra cosa a considerar es que si una función toma un puntero a una estructura, se puede escribir el prototipo como
void foo (struct BAR_s * bar);
sin una definición de BAR_s que tenga que estar dentro del alcance. Un enfoque muy útil para evitar inclusiones innecesarias.
PD: en muchos de mis proyectos, habrá un archivo que se espera que cada módulo #incluya, que contiene cosas como typedefs para tamaños enteros y algunas estructuras y uniones comunes [p. Ej.
typedef union {
unsigned long l;
lw corto sin firmar [2];
carácter sin firmar lb [4];
} U_QUAD;
(Sí, sé que tendría problemas si me cambiara a una arquitectura big-endian, pero dado que mi compilador no permite estructuras anónimas en uniones, el uso de identificadores con nombre para los bytes dentro de la unión requeriría que se acceda a ellos como theUnion.b.b1 etc.que parece bastante molesto.