реклама на сайте
подробности

 
 
> include в хидере - всё таки это доро или зло?, Есть противоположные мнения, хочется понять их
Cosmojam
сообщение Oct 10 2013, 22:00
Сообщение #1


Местный
***

Группа: Свой
Сообщений: 311
Регистрация: 12-01-11
Из: Калининград (Koenigsberg)
Пользователь №: 62 182



Речь идёт про Си. Си++ не трогаем, хотя догадываюсь что аргументы там те же.

Существует мнение что писать в .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 не дураки ведь. Так почему существуют противоположные мнения?


--------------------
typedef enum { no, yes, maybe } bool; | блог тут
Go to the top of the page
 
+Quote Post
2 страниц V   1 2 >  
Start new topic
Ответов (1 - 14)
AlexandrY
сообщение Oct 11 2013, 05:47
Сообщение #2


Ally
******

Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050



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


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

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

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

Go to the top of the page
 
+Quote Post
halfdoom
сообщение Oct 11 2013, 06:12
Сообщение #3


Профессионал
*****

Группа: Свой
Сообщений: 1 003
Регистрация: 20-01-05
Пользователь №: 2 072



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

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


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

Кроме того, следует руководствоваться правилом наименьшего замусоривания пространства имен, т.к. чем больше будет включено заголовков, тем больше вероятность конфликта определений из них, с определениями пользователя, поскольку он может использовать лишь часть функционала предоставляемого в вашем заголовочном файле.
Go to the top of the page
 
+Quote Post
demiurg_spb
сообщение Oct 11 2013, 06:34
Сообщение #4


неотягощённый злом
******

Группа: Свой
Сообщений: 2 746
Регистрация: 31-01-08
Из: Санкт-Петербург
Пользователь №: 34 643



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


--------------------
“Будьте внимательны к своим мыслям - они начало поступков” (Лао-Цзы)
Go to the top of the page
 
+Quote Post
Сергей Борщ
сообщение Oct 11 2013, 06:53
Сообщение #5


Гуру
******

Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095



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


--------------------
На любой вопрос даю любой ответ
"Write code that is guaranteed to work, not code that doesn’t seem to break" (C++ FAQ)
Go to the top of the page
 
+Quote Post
_Pasha
сообщение Oct 11 2013, 07:01
Сообщение #6


;
******

Группа: Участник
Сообщений: 5 646
Регистрация: 1-08-07
Пользователь №: 29 509



Инклюды в хедере - вне добра и зла и их применение диктуется необходимостью минимизации зависимостей между единицами трансляции.
Go to the top of the page
 
+Quote Post
XVR
сообщение Oct 11 2013, 07:43
Сообщение #7


Гуру
******

Группа: Свой
Сообщений: 3 123
Регистрация: 7-04-07
Из: Химки
Пользователь №: 26 847



Цитата(Cosmojam @ Oct 11 2013, 02:00) *
Существует мнение что писать в .h все необходимые инклюды - это плохо.
Нет такого мнения. Или это мнение закоренелых лентяев (со стороны тех, кто пишет), или мазохистов (со стороны тех, кто это потом использует) rolleyes.gif

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

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

Go to the top of the page
 
+Quote Post
AlexandrY
сообщение Oct 11 2013, 08:04
Сообщение #8


Ally
******

Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050



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


Загадочно сказано. wink.gif
О каких зависимостях идет речь?
Допустим весь код уже написан и надо скомпоновать хидеры. Как мы будем тут "минимизировать зависимости"?
Go to the top of the page
 
+Quote Post
Сергей Борщ
сообщение Oct 11 2013, 08:17
Сообщение #9


Гуру
******

Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095



QUOTE (AlexandrY @ Oct 11 2013, 11:04) *
Загадочно сказано. wink.gif

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


--------------------
На любой вопрос даю любой ответ
"Write code that is guaranteed to work, not code that doesn’t seem to break" (C++ FAQ)
Go to the top of the page
 
+Quote Post
AlexandrY
сообщение Oct 11 2013, 08:38
Сообщение #10


Ally
******

Группа: Модераторы
Сообщений: 6 232
Регистрация: 19-01-05
Пользователь №: 2 050



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

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


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

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

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

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

Но вот про зависимости я не понял.
Go to the top of the page
 
+Quote Post
Cosmojam
сообщение Oct 11 2013, 10:34
Сообщение #11


Местный
***

Группа: Свой
Сообщений: 311
Регистрация: 12-01-11
Из: Калининград (Koenigsberg)
Пользователь №: 62 182



Цитата(Сергей Борщ @ 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 так сделано.
С точки зрения крупных проектов это нормально или тоже есть какие-то подводные камни?


--------------------
typedef enum { no, yes, maybe } bool; | блог тут
Go to the top of the page
 
+Quote Post
Сергей Борщ
сообщение Oct 11 2013, 10:58
Сообщение #12


Гуру
******

Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095



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 - то придется перекомпилировать и всех пользователей, хотя это совершенно не изменит их объектный код.


--------------------
На любой вопрос даю любой ответ
"Write code that is guaranteed to work, not code that doesn’t seem to break" (C++ FAQ)
Go to the top of the page
 
+Quote Post
kolobok0
сообщение Oct 11 2013, 12:11
Сообщение #13


практикующий тех. волшебник
*****

Группа: Участник
Сообщений: 1 190
Регистрация: 9-09-05
Пользователь №: 8 417



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


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

сам обычно следую такой практики: слабоменяющиеся хедера (внешнии по отношению к данному проекту) - можно куда угодно. а свои - только в текущий файл с кодом.
Go to the top of the page
 
+Quote Post
igorle
сообщение Oct 11 2013, 12:12
Сообщение #14


Местный
***

Группа: Свой
Сообщений: 338
Регистрация: 14-07-12
Пользователь №: 72 753



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

u32 foo(s16);

3. Исключения допускаются, но программист должен быть в состоянии объяснить, для чего он вводит эту зависимость.
Go to the top of the page
 
+Quote Post
Сергей Борщ
сообщение Oct 11 2013, 13:06
Сообщение #15


Гуру
******

Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095



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

Вот именно, если этот заголовочный файл от чего-то зависит - все должно быть в нем. А Cosmojam все норовит засунуть туда заголовочные файлы, от которых зависит описываемый этим заголовочным файлом файл исходного кода.


--------------------
На любой вопрос даю любой ответ
"Write code that is guaranteed to work, not code that doesn’t seem to break" (C++ FAQ)
Go to the top of the page
 
+Quote Post

2 страниц V   1 2 >
Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 23rd July 2025 - 09:57
Рейтинг@Mail.ru


Страница сгенерированна за 0.0152 секунд с 7
ELECTRONIX ©2004-2016