Что то тут все у вас смешалось... Начать, наверное, стоит с общих моментов, без специфики С/С++. Сначала надо понять, что есть определение и есть описание. Описание не создает собственно переменной, а лишь описывает ее. Это еще называется декларированием. Сама переменная создается при определении. Вот например описание
- Код:
struct smp_struct {
int a;
char b;
};
real pi();
Тут ничего в памяти не размещается. А вот определение, основанное на этом описании
- Код:
struct smp_str var;
real pi() {
return 3.14;
}
Тут компилятор уже выделит память для переменной и функции. В заголовочных файлах (h, hpp, h++) обычно находятся описания типов, переменных, функций. Причем и для других языков программирования. Основная идея тут в облегчении написания программ состоящих из большого количества исходных файлов, разрабатываемых несколькими программистами, и тому подобного. Так в заголовочных файлах описаны системные вызовы и переменные, что бы не надо было вручную вписывать их в каждый файл. Включение (include) других заголовочных файлов из заголовочного файла (вложенное включение) вполне нормально и часто используется. Что бы избежать многократных определений и переопределений используют директивы препроцессора (в частности, условной компиляции), который собственно и работает с этими заголовочными файлами.
В модульном программировании, как и в функциональном, и модном сейчас структурном, дополнительно выделяется понятие модуля (может состоять из нескольких исходных файлов). Модуль имеет интерфейс и реализацию. Вот описание интерфейса, то есть того, как взаимодействовать с модулем, выносится в заголовочный файл (описание функций и глобальных переменных модуля). При этом кроме заголовочного файла с описанием интерфейса вполне себе могут существовать заголовочные файлы описывающие "внутренний мир" модуля. Они не нужны программисту использующему модуль, но нужны разработчику модуля.
Как происходит превращение исходных файлов в готовый двоичный файл... Сначала препроцессор обрабатывает исходный файл С обрабатывая директивы, в частности, заменяя include на содержимое собственно заголовочного файла. Естественно, отрабатываются и другие директивы, например, условной компиляции. На выходе получается С файл который передается уже собственно компилятору. Выходом компилятора является объектный файл. В нем уже нет текста, но есть описания программных секций, фрагменты двоичного кода, описания переменных (адрес, длина, имя секции). Линкер читает этот объектный файл и библиотечные файлы (наборы объектных модулей), собирает из секций кода и данных двоичный файл замещая созданные компилятором ссылки на переменные их фактическими адресами. Каждая секция кода и данных после компиляции содержит адреса и ссылки относительно начального адреса секции (на этом этапе 0) и только внутри секции. Линкер заменяет эти относительные адреса на фактические. При этом, линкер знает, где находятся все переменные и функции, так что может разобраться и с глобальными переменными.
Файл перекрестных ссылок содержит информацию о том, где какое имя (функция или переменная, не важно) определено, то есть, размещено в памяти, и где на это имя ссылаются (использование переменной, вызов функции, переход по адресу). Используется линкером в процессе сборки двоичного файла. Программисту может быть интересно, что бы узнать фактические адреса (включая системные переменные, не только в самой программе). Кроме того, используется для построения оверлейных структур (перекрытий) больших программ, которые нельзя загрузить в память целиком. Для микроконтроллеров, того же самого PIC, компилятор C, например XC, может разместить несколько переменных по одному и тому же адресу, если эти переменные не используются одновременно. Так что структуры перекрытий позволяют экономить и память кода, и память данных.
Извиняюсь за столь длинное и нудное описание основ, но автор, судя по всему, начинающий и для него важно понять, как это все устроено. Если перейти к конкретным советам и практике, то:
1. Заголовочный файл должен содержать только описания данных и кода. В нем не должно быть никаких определений! Исключением являются inline функции. Причем это верно и для С и для С++
2. Локальные и публичные переменные и функции модуля (файла) должные определяться в самом этот исходном файле модуля на основании описаний заголовочного файла, который подключается директивой include.
3. Вложенное использование директив include (включение одного заголовочного файла внутри другого) вполне допустимо и часто используется.
4. Для исключения переопределений и повторных описаний нужно использовать директивы условной компиляции.
5. Любая переменная должна быть описана только один раз (хотя некоторые компиляторы допускают повторные описания, при этом используя последнее) и определена только один раз. Если переменная должна быть доступна из других исходных файлов, то она описывается как глобальная. Для C любая переменная определенная вне тела функции считается глобальной, но в С++ может потребоваться дополнительный атрибут для членов классов. В других исходных файлах эта переменная должна быть описана как внешняя (extern), если требуется к ней доступ. Это все верно и для функций.