Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: include в хидере - всё таки это доро или зло?
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
Cosmojam
Речь идёт про Си. Си++ не трогаем, хотя догадываюсь что аргументы там те же.

Существует мнение что писать в .h все необходимые инклюды - это плохо. Единственное подтверждение почему это плохо что мне удалось найти - время компиляции увеличивается. Многократные инклюды одного хидера лечатся очень просто с помощью ifndef/define и как аргумент не рассматриваются т.к. без защиты от множественных инклюдов в любом проекте где один хидер используется более чем в 1 .c файле будут проблемы.

Мой аргумент почему это стоит делать: Хидер - это заголовок модуля, определяющий его публичный интерфейс. Логично расположить в нём всё необходимое для работы модуля, а в .с файле инклюдить только один этот заголовок т.о. отделив реализацию от интерфейса. Улучшается наглядность т.к. сразу в хидере видно зависимости от других модулей.
Погуглив можно найти мнение со ссылкой на NASA C coding standard где написано
Цитата
(4)
The unit header file shall contain #include statements for all other headers required by the unit
header. This lets clients use a unit by including a single header file.


Так как же "правильнее"? NASA не дураки ведь. Так почему существуют противоположные мнения?
AlexandrY
Цитата(Cosmojam @ Oct 11 2013, 01:00) *
Существует мнение что писать в .h все необходимые инклюды - это плохо.
Единственное подтверждение почему это плохо что мне удалось найти - время компиляции увеличивается...


В мелких проектах (десятки файлов) не имеет никакого значения.

Но в в больших проектах (сотни и тысячи файлов) структура хидеров самая большая проблема.
Тут никакие мнения не работают, все делается по обстановке.

Хидеры разных сторонних модулей могут содержать повторяющиеся имена (стабильно все переопределяют TRUE, FALSE, ERROR, OK и т.п. ), т.е. их просто невозможно разместить в одном общем хидере.
Часто переопределяются стандартные C-и функции, которые в основном приложении может переопределять нежелательно.
Сторонние модули уже могут идти со своей распределенной структурой хидеров и собрать из них один общий не представляется возможным.
В больших проектах также стоит отделять хидеры отлаженных сторонних модулей как например RTOS, GUI, FS от хидеров приложения.
Поскольку приложение постоянно рефакторится и любое изменение в приложение приводит к перекомпиляции всех модулей в случае общего хидера.
И т.д.
Вообщем все определяет технология.

halfdoom
Цитата(AlexandrY @ Oct 11 2013, 08:47) *
В мелких проектах (десятки файлов) не имеет никакого значения.

Но в в больших проектах (сотни и тысячи файлов) структура хидеров самая большая проблема.
Тут никакие мнения не работают, все делается по обстановке.


+1. Очень простой пример: есть большой модуль, с парой десятков интерфейсных функций. Среди них есть одна, редко используемая функция, которая получает в качестве аргумента указатель на структуру foo: "void fn22(struct foo *);". Ради компилируемости, можно добавить include "foo.h", но если этот foo.h тянет за собой еще десяток инклюдов, то лучше просто объявить структуру как "struct foo;" и возложить необходимость включения foo.h на пользователя, которому эта структура понадобится.

Кроме того, следует руководствоваться правилом наименьшего замусоривания пространства имен, т.к. чем больше будет включено заголовков, тем больше вероятность конфликта определений из них, с определениями пользователя, поскольку он может использовать лишь часть функционала предоставляемого в вашем заголовочном файле.
demiurg_spb
Немного offtopic...
Чтобы не слишком много переживать о времени компиляции стоит применять "библиотечный подход"
и все более-менее крупные модули собирать в виде отдельных библиотек и линковать их к результирующему проекту.
Сергей Борщ
QUOTE (Cosmojam @ Oct 11 2013, 01:00) *
Хидер - это заголовок модуля, определяющий его публичный интерфейс. Логично расположить в нём всё необходимое для работы модуля, а в .с файле инклюдить только один этот заголовок
Да, действительно, заголовочный файл - интерфейс модуля. В него нужно включать через #include только те файлы, которые необходимы для этого интерфейса, для обращений к этому модулю. Чтобы в пустом .c - файле сделать #include "my_header.h" и это откомпилилось. Те заголовочные файлы, которые необходимы для реализации вашего модуля - его внутреннее дело, для обращений к модулю они не нужны и вытаскивать их в заголовочный файл не нужно и даже вредно (по причине увеличения времени компиляции и просто замусоривания заголовочного файла). А вы смешали эти две группы заголовочных файлов в одну.
_Pasha
Инклюды в хедере - вне добра и зла и их применение диктуется необходимостью минимизации зависимостей между единицами трансляции.
XVR
Цитата(Cosmojam @ Oct 11 2013, 02:00) *
Существует мнение что писать в .h все необходимые инклюды - это плохо.
Нет такого мнения. Или это мнение закоренелых лентяев (со стороны тех, кто пишет), или мазохистов (со стороны тех, кто это потом использует) rolleyes.gif

Есть мнение, что 'необходимых инклюдов' должно быть как можно меньше (как это делается, вам тут уже писали)

Хочу только добавить еще один прием - при написании программы (не библиотеки, это важно!) можно часто используемые независимые инклюды (и не только их), собрать в один общий инклюд и включать его во все единицы компиляции первым (например это обычная практика в VS с файлом StdAfx.h, хотя и по другой причине laughing.gif ). В этом случае большое количество однотипных инклюдов в конкретные *.h конкретных модулей можно не включать.

AlexandrY
Цитата(_Pasha @ Oct 11 2013, 10:01) *
Инклюды в хедере - вне добра и зла и их применение диктуется необходимостью минимизации зависимостей между единицами трансляции.


Загадочно сказано. wink.gif
О каких зависимостях идет речь?
Допустим весь код уже написан и надо скомпоновать хидеры. Как мы будем тут "минимизировать зависимости"?
Сергей Борщ
QUOTE (AlexandrY @ Oct 11 2013, 11:04) *
Загадочно сказано. wink.gif

Нормально вполне. Подключение заголовочного файла должно тянуть все, что необходимо для этого заголовочного файла и ничего более.
QUOTE (AlexandrY @ Oct 11 2013, 11:04) *
Допустим весь код уже написан и надо скомпоновать хидеры.
А вот это как? Как код может быть написан без заголовочных файлов? Как же он тестировался?
AlexandrY
Цитата(Сергей Борщ @ Oct 11 2013, 11:17) *
Нормально вполне. Подключение заголовочного файла должно тянуть все, что необходимо для этого заголовочного файла и ничего более.

А вот это как? Как код может быть написан без заголовочных файлов? Как же он тестировался?


Я вообще-то обсуждаю только крупные проекты.

Тогда скажем взяв какой-нибудь файл размеров с пару сотен килобайт даже если его сами написали все равно не сможете по памяти сказать, что ему необходимо из заголовочных файлов.
Неминуемо захочется создать один .h файл куда засунуть все что ни попадя, лишь бы после десятка минут компиляции, в самом конце причем, не узнать о недостающих объявлениях.

Код не пишется, а компонуется.
Пишется там десяток файлов целевого приложения, меньше процента от общего числа уже созданных исходников.
Хорошая компоновка(не написание!) хидеров значительно сокращает время отладки (которая сопровождается постоянными перекомпиляциями и рефакторингом).

Кстати еще есть такая тема как приватные и публичные хидеры. Не раз такое обнаруживал в разных GUI, FS и проч. Как правило это деление приходится нарушать в процессе портирования. Вот тут просто раздолье для маневров с хидерами.

Но вот про зависимости я не понял.
Cosmojam
Цитата(Сергей Борщ @ Oct 11 2013, 09:53) *
Да, действительно, заголовочный файл - интерфейс модуля. В него нужно включать через #include только те файлы, которые необходимы для этого интерфейса, для обращений к этому модулю. Чтобы в пустом .c - файле сделать #include "my_header.h" и это откомпилилось. Те заголовочные файлы, которые необходимы для реализации вашего модуля - его внутреннее дело, для обращений к модулю они не нужны и вытаскивать их в заголовочный файл не нужно и даже вредно (по причине увеличения времени компиляции и просто замусоривания заголовочного файла). А вы смешали эти две группы заголовочных файлов в одну.

Ок, а если, например, есть модуль A, для реализации которого (именно реализации) требуется модуль B. Но для использования A не обязательно инключить B.h в A.h (нет зависимости от объявленных в B.h типов и дефайнов). В этом случае получается include "B.h" надо писать в A.c. Но тогда взглянув на A.h нельзя точно сказать от каких модулей он зависит. Т.е. получает компромисс между читаемостью и захламлением глобального пространства имён.

А для решения проблем с переопределением одинаковых символов/функций можно к имени всех глобальных функций/символов добавлять префикс с именем модуля. Например, во FreeRTOS так сделано.
С точки зрения крупных проектов это нормально или тоже есть какие-то подводные камни?
Сергей Борщ
QUOTE (Cosmojam @ Oct 11 2013, 13:34) *
Ок, а если, например, есть модуль A, для реализации которого (именно реализации) требуется модуль B. Но для использования A не обязательно инключить B.h в A.h (нет зависимости от объявленных в B.h типов и дефайнов). В этом случае получается include "B.h" надо писать в A.c.
Да, именно так.

QUOTE (Cosmojam @ Oct 11 2013, 13:34) *
Но тогда взглянув на A.h нельзя точно сказать от каких модулей он зависит.
Для этого надо заглянуть в A.c Ведь это тонкости реализации. Сегодня он зависит от Ц.h, завтра вы его перепишете используя Д.h - никого из пользователей модуля A это по большому счету не интересует и на использовании модуля A это никак не повлияет. И нет никакой необходимости перекомпилировать всех пользователей модуля A. А вот если вы включили Ц.h в A.h - то придется перекомпилировать и всех пользователей, хотя это совершенно не изменит их объектный код.
kolobok0
Цитата(Cosmojam @ Oct 11 2013, 02:00) *
..Существует мнение что писать в .h все необходимые инклюды - это плохо. Единственное подтверждение...


ышо пять копеек:
не только это. например 4,6 (дальше не натыкался - может подправили косяк - хз) студия от мелокмягких не видела изменения в хедарах, если они были включены не в си файл (либо ниже чем 2 вложенность - уже стал забывать эту траблу sm.gif ). правда это си плас плас было, но думаю на сях то же самое хромало у них...

сам обычно следую такой практики: слабоменяющиеся хедера (внешнии по отношению к данному проекту) - можно куда угодно. а свои - только в текущий файл с кодом.
igorle
У нас на фирме приняты правила, совпадающие с НАСАвскими.
1. Любой хедер может быть использован без предварительного инклюда
2. Если хедер зависит от разных файлов в различных компиляциях - то эта зависимость должна быть собрана в файле. Например, мой фал может быть использован и в Кернеле, и в Юзер моде. Тогда делаем что-то вроде:
Код
#ifdef __KERNEL__
#include <kernel_types.h>
#else
#include <user_types.h>
#end

u32 foo(s16);

3. Исключения допускаются, но программист должен быть в состоянии объяснить, для чего он вводит эту зависимость.
Сергей Борщ
QUOTE (igorle @ Oct 11 2013, 15:12) *
У нас на фирме приняты правила, совпадающие с НАСАвскими.
1. Любой хедер может быть использован без предварительного инклюда
2. Если хедер зависит от разных файлов в различных компиляциях - то эта зависимость должна быть собрана в файле.

Вот именно, если этот заголовочный файл от чего-то зависит - все должно быть в нем. А Cosmojam все норовит засунуть туда заголовочные файлы, от которых зависит описываемый этим заголовочным файлом файл исходного кода.
Cosmojam
Ок, это я не правильно понял тот абзац из стандарта. Спасибо!
ar__systems
Цитата(Сергей Борщ @ Oct 11 2013, 01:53) *
Да, действительно, заголовочный файл - интерфейс модуля. В него нужно включать через #include только те файлы, которые необходимы для этого интерфейса, для обращений к этому модулю. Чтобы в пустом .c - файле сделать #include "my_header.h" и это откомпилилось.

+1
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.