В некоторых средах компиляция будет самой быстрой, если в нее включены только те файлы заголовков, которые нужны. В других средах компиляция будет оптимизирована, если все исходные файлы могут использовать одну и ту же основную коллекцию заголовков (некоторые файлы могут иметь дополнительные заголовки помимо общего подмножества). В идеале заголовки должны быть построены так, чтобы несколько операций #include не имели никакого эффекта. Возможно, будет полезно окружить операторы #include проверками для include-guard включаемого файла, хотя это создает зависимость от формата этого guard. Кроме того, в зависимости от поведения системного кэширования файла, ненужный #include, цель которого заканчивается полным удалением # ifdef, может не занять много времени.
Еще одна вещь, которую следует учитывать, это то, что если функция берет указатель на структуру, можно написать прототип как
void foo(struct BAR_s *bar);
без определения того, что BAR_s должен находиться в области видимости. Очень удобный подход для избежания ненужных включений.
PS - во многих моих проектах будет файл, который, как ожидается, каждый модуль будет включать в себя #include, содержащий такие вещи, как typedefs для целых размеров и несколько общих структур и объединений [например,
typedef union {
unsigned long l;
unsigned short lw[2];
unsigned char lb[4];
} U_QUAD;
(Да, я знаю, что у меня будут проблемы, если я перейду на архитектуру с прямым порядком байтов, но поскольку мой компилятор не допускает анонимные структуры в объединениях, использование именованных идентификаторов для байтов в объединении потребует быть доступным как theUnion.b.b1 и т. д., что выглядит довольно раздражающим.