Перейти к содержанию
    

Заголовочные файлы и модули.

Добрый день. Всегда размещал специфические функции в заголовочных файлах .h. Но вот знающие люди предлагают в заголовочных файлах только объявлять переменные и функции а сами функции размещать в отдельном модуле с расширением .с. Сделал я такую попытку и компилятор сразу же потерял эти функции. В модуле и в основном файле проекта строка #include на заголовочный файл .h была объявлена. Как правильно поступать в таком случае и стоит ли это делать?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Думаю что эти функции потерял не компилятор, а линкер.

Очевидно эти .c файлы вы забыли добавить в проект(или в makefile) или в командную строку компилятора... нужно больше подробностей о том какой компилятор используется и как происходит сборка проекта.

 

Реализацию функций делать в .c файлах, а описание прототипов в .h - это стандартный, можно сказать канонический способ написания программ на Си.

Более того - если в .h файле сделать реализацию функции, а потом включить(#include) этот .h файл в двух разных .c файлах одного проекта то линкер потом будет возмущен наличием двух функций с одинаковыми именами в двух разных .obj файлах.

 

Таки да, иногда, для определенных целей делают реализации в .h но это всё, как правило, разные оптимизации, хаки, и прочие тонкости. Каждый такой случай рассматривать надо отдельно и скорее всего его можно будет оспорить.

 

Единственным правильным случаем включения реализации в .h файл является случай описания шаблона(template) в языке С++. Но это тоже отдельная и ОЧЕНЬ большая тема....

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Пару раз встречал расположение функций в файлах .h, но это точно были исключительные случаи.

Больше типа функции-макросы или weak-шаблоны функций.

 

Стандартная практика:

Пары файлов xxx.c и xxx.h

где в .с объявляются глобальные и статические переменные и функции модуля

и прототипы статических функций.

.c файлы в явном виде включаются в проект.

 

а в .h файле - дефайны, typedef-ы, extern глобальных переменных и прототипы глобальных функций.

.h файлы включаются при помощи #include во все другие файлы, где применяются глобальные определения из этого модуля.

 

Чтобы .h файл не включался больше одного раза в другие файлы, применяется условная трансляция.

#ifndef __XXX_H
#define __XXX_H
тело файла
#endif  /* __XXX_H */

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Думаю что эти функции потерял не компилятор, а линкер.

Очевидно эти .c файлы вы забыли добавить в проект(или в makefile) или в командную строку компилятора... нужно больше подробностей о том какой компилятор используется и как происходит сборка проекта.

 

Использую AVRStudio 4.19, вернулся к ней после годового использования Atmel Studio 7.0, нет смысла использовать столь громоздкую систему менее чем на 10%. На AVRStudio имеется, хоть и краткое, руководство без объяснения таких тонкостей как подключение .с файлов. Был бы очень признателен за информацию об их подключении к проектам, ибо кроме директивы #include мне не известно. Спасибо.

 

Чтобы .h файл не включался больше одного раза в другие файлы, применяется условная трансляция.

#ifndef __XXX_H
#define __XXX_H
тело файла
#endif  /* __XXX_H */

 

Спасибо, теперь стало ясно для чего используют эти директивы препроцессора в .h файлах.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Помещение определений встраиваемых функций (inline) в заголовочные файлы - стандартная практика. Определения обычных функций помещать в заголовочные файлы нельзя, иначе будет ошибка сборки типа redeclaration error, что логично и понятно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Помещение определений встраиваемых функций (inline) в заголовочные файлы - стандартная практика. Определения обычных функций помещать в заголовочные файлы нельзя, иначе будет ошибка сборки типа redeclaration error, что логично и понятно.

Именно определения (без реализации) и надо помещать, иначе получите implicit declaration

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Именно определения (без реализации) и надо помещать,
Определение функции содержит ее реализацию. То, что вы имеете ввиду, называется "объявление функции".

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Определение функции содержит ее реализацию. То, что вы имеете ввиду, называется "объявление функции".

Согласен, неправильно выразился.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Разобрав несколько примеров сделал попытку переделать один из своих проектов, разделив свои заголовочные файлы на .c и .h. Как только я это сделал с одним файлом компилятор сразу же "потерял" функции в других заголовочных файлах, которые я не изменял. По "пыжившись" без результатов вернул всё в исходное состояние, вероятно нужно начинать эти "эксперименты" в новых учебных проектах. И всё же, может не стоит это делать, особенно самоучкам? Размещая функции в заголовочных файлах директива #include на них объявляется только в основном файле .c проекта. При разделении на модули приходится гадать, где и какие объявлять директивы #include, при разборе примеров я, честно говоря, не смог постичь логику их объявления.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Разобрав несколько примеров сделал попытку переделать один из своих проектов, разделив свои заголовочные файлы на .c и .h. Как только я это сделал с одним файлом компилятор сразу же "потерял" функции в других заголовочных файлах, которые я не изменял.

Лучше приложите файлы, тогда можно подсказать чего не хватает.

 

В общем случае, надо понимать, что .h-файлы сделаны для так называемой "изоляции кода", т.е. они описывают интерфейс доступа к вашему .c-файлу. Чтобы пользователь .c-файла (или скомпилированной библиотеки функций в виде .dll или .ld) не копался в вашем исходном коде(если он есть), а открыв .h-файл узнал какие функции есть и как ими пользоваться, а компилятор знал как на эти функции сослаться.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Лучше приложите файлы, тогда можно подсказать чего не хватает.

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Так вы логики в .h файлах не видите потому не понимаете процесс компиляции и сборки проекта.

 

В Си вам можно обойтись и без .h файлов, но не наоборот. Так вот то то вы делаете - это такой извращенный способ сформирвать один большой .с файл.

Препроцессор же вместо #include "header.h" просто вставляет текст из файла header.h. Ни больше ни меньше. Вы щас получаете большой .c файл, который отправляется на копиляцию.

 

По-пробую как-то сжато передать суть...

Для упрощения пока не рассматриваем данные и скажем, что по сути каждый .c файл это тупо набор функций - кусков исполняемого кода который можно вызвать по имени.

Для того чтобы вызвать этот кусок кода надо знать имя, типы возвращаемого функцией значения и типы аргументов функции(плюс их кол-во).

В общем случае мы имеем несколько .с файлов, которые вызывают функции друг друга.

Но компиляция этих файлов происходит независимо!

Допустим, у вас в проекте три .с файла. 1.с 2.с 3.с Это будет означать, что надо ТРИ раза вызвать компилятор и на выходе мы получим три объектных модуля 1.obj 2.obj 3.obj

Эти объектные модули - полуфабрикаты, в которых указано имя функции и исполняемый код ей соответствующий. Данные мы пока не рассматриваем, как я уже и говорил.

 

Вы же помните да, что чтобы вызвать функцию надо знать её имя, тип результата и аргументов? Это необходимо чтобы сгенерировать код вызова этой фнкции.

Так вот это и есть тот самый прототип функции.

Мы в файле 1.с хотим использовать фукнцию void func(int a); которая реализована в файле 2.с и магия в том, что нам для этого нужно знать только прототип функции.

Помните же да, что компилятор компилирует один .с файл за один раз. Может быть файл 1.с поступил на компиляцию раньше и машинного кода для 2.с еще вовсе нет! Это не проблема, нам достаточно прототипа функции. Дальше линкер разберется.. Но об этом позднее.

 

Так вот мы могли бы в файле 1.с ПЕРЕД вызовом func(1); объявить её прототип. Написать void func(int a);

Тогда не возникнет никаких проблем и компилятор будет знать что func(1) соответствует прототипу и сможет с генерировать машинный код для вызова. Как видите, никаких #include и .h файлов вообще не нужно!

 

Однако это очень неудобно, потому что функция func() может потребоваться нам и в файле 1.с и в файле 10.с и еще в многих местах! Копипаситить прототипы теперь везде? Было бы глупо.

Выход простой: Функция func() реализована в файле 2.с, поэтому очень логично сделать файл 2.h и поместить туда прототипы всех функций реализованных в файле 2.с.

 

Теперь любой .с файл, который хочет использовать функции реализованные в 2.с просто делает #include 2.h и таким образом в начале этого .с файла препроцессор подставляет все прототипы всех функций из файла 2.с и мы можем свободно их вызывать из любых других .с файлов. Прототипы теперь описаны один раз и хранятся централизовано. Этот файл еще называют интерфейсом модуля. В этом-же .h файле могут хранится описания типов данных, но этого мы пока не касаемся для упрощения понимания.

 

 

Также стоит отметить, что нет такого понятия как "основной .с файл проекта", которое вы тут используете.

Есть просто требование, что где-то должна обязательная быть функция main(), которая будет вызвана из стартапа, после того как будет подготовлено Си окружение(инициализирована память, указатель стека и т.д.)

 

 

 

Короче после того как компилятор был вызван 100500 раз, ОТДЕЛЬНО для каждого .с файла и на выходе получены .obj файлы

https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%...%83%D0%BB%D1%8C

Начинается компоновка(линкинг) вызывается линкер, которому скармливаются все эти объектные модули(и не только, но это щас не важно).

Линкер размещает каждый .obj файл по определенному адресу и таким образом знает какая функция из какого .obj расположена по какому адресу в памяти.

Я сейчас намеренно всё очень упрощаю и не касаюсь темы данных, секци и т.д...

 

Далее линкер проходится по всем вызовам функций и заменяет вызов по имени на конкретный адрес! Ведь он теперь знает адреса всех функций из всех .obj файлов.

Например 1.obj вызывается функция func(). Линкер находит ее реализацию в модуле 2.obj и в то место подставляет уже АДРЕС. Либо выдаст ошибку если нигде ни в одном из .obj которые ему передали он эту функкцию не нашел. Либо если в двух разных .obj есть две функции с одинаковым именем.

 

 

 

Так вот вы разделите свой проект исходя из вышеописанной логики и обязательно добавьте все свои .c файлы в проект.

IDE AVRStudio для вас сделает всю магию - т.е. будет вызывать компилятор отдельно для каждого .c файла, потом все полученные результаты передаст линкеру и на выходе у вас получится один исполняемый файл. Это всё произойдет автоматически.

 

Во всем этом разделении есть очень много логики и смысла, я лишь ОЧЕНЬ упрощенно показал вам процесс.

Без дальнейшего изучения тут не обойтись. Есть static функции которые из других .c файлов вызвать нельзя. Есть inline функции которые вместо вызова сразу встраиваются в точку вызова... Есть много тонкостей разных которые требуют отдельного освещения и имеют свои тонкости.

 

С Новым Годом всех, успехов в разработках и изучении Си )))

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

...

 

По-пробую как-то сжато передать суть...

 

...

 

ОФФТОП.

Спасибо за изложение. Что-то встало на свое место в голове. Я просто мимо крокодил)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Так вы логики в .h файлах не видите потому не понимаете процесс компиляции и сборки проекта.

 

С Новым Годом всех, успехов в разработках и изучении Си )))

 

Логику использования .h файлов я понял, я не уловил в примерах закономерности директив #include в модулях .c. Всё что вы изложили было очень полезным, спасибо. Ну и конечно же с Новым Годом!

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Логику использования .h файлов я понял, я не уловил в примерах закономерности директив #include в модулях .c.

Ну тогда надо начинать с рассмотрения этих конкретных примеров и искать логику. Может быть ее там и действительно нет )

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...