|
|
  |
Заголовочные файлы и модули., Как правильно? |
|
|
|
Jan 1 2018, 18:47
|

Просто Che
    
Группа: Свой
Сообщений: 1 567
Регистрация: 22-05-07
Из: ExUSSR
Пользователь №: 27 881

|
Цитата(Smoky @ Jan 1 2018, 07:55)  Разобрав несколько примеров сделал попытку переделать один из своих проектов, разделив свои заголовочные файлы на .c и .h. Как только я это сделал с одним файлом компилятор сразу же "потерял" функции в других заголовочных файлах, которые я не изменял. Переделка только одного файла может только запутать все эти перекрестные ссылки. Нужно переделывать все. Обычно это довольно трудоемко. У меня есть опыт перенятия чужих проектов начинающих программистов, которые плохо понимали структуризацию проектов на Си. На "распутывание" проекта из ~20 файлов уходила пара дней  Там еще будут проблемы грамотного ограничения области видимости переменных и функций, но это вам можно оставить на потом, после вникания в это. Цитата(Smoky @ Jan 1 2018, 16:54)  Логику использования .h файлов я понял, я не уловил в примерах закономерности директив #include в модулях .c Для начала, все .с файлы должны быть включены в проект в самой IDE AVRStudio Там должно быть окно с деревом файлов проекта. Файлы обычно включаются командой типа: Add file to project С инклудами .h файлов все просто. Запускаете компиляцию проекта. Например на файл main.c компилятор ругается, что не может найти функцию func(). Вы поиском находите, что эта функция у вас находится в файле uart.c, а в файле uart.h есть описание её прототипа. Значит, в начало файла main.c нужно добавить #include uart.h И так по всем ошибкам компилятора. С Новым Годом!
|
|
|
|
|
Jan 1 2018, 19:40
|

Местный
  
Группа: Свой
Сообщений: 401
Регистрация: 7-05-10
Из: Оренбург
Пользователь №: 57 135

|
Цитата(Baser @ Jan 2 2018, 00:47)  Переделка только одного файла может только запутать все эти перекрестные ссылки. Нужно переделывать все. Обычно это довольно трудоемко. Согласен. Поэтому решил начать сначала. Прошу посмотреть фрагмент программы и сказать что я делаю не так. Этот фрагмент инициализирует LCD 1х20, загружает пользовательские символы, выводит на LCD "Инициализация" и короткий звуковой сигнал 10 мсек. Файлы .c компилируются нормально но вот сборка не получается. Директивы #include вставлял по мере вывода сообщений об ошибках но результата так и не получил...
--------------------
Лень, оттвори дверь, сгоришь - а хоть и сгорю, но не оттворю.
|
|
|
|
|
Jan 1 2018, 20:01
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
Мне пришлось импортировать проект в AS 7 Я правильно понимаю, что вы получаете вот такие ошибки? Код Severity Code Description Project File Line Error ld returned 1 exit status Cloc_Meteo1_2 collect2.exe 0 Error recipe for target 'Cloc_Meteo1_2.elf' failed Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\default\Makefile 129 Error multiple definition of `Smil_char' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\Symbol.c 10 Error multiple definition of `sign_clcdur' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\Pikout.c 30 Error multiple definition of `Sand_char' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\Symbol.c 10 Error multiple definition of `Pres_char' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\Symbol.c 10 Error multiple definition of `Line_message' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\default\Symbol.o 1 Error multiple definition of `insg' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\default\Pikout.o 1 Error multiple definition of `Humi_char' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\Symbol.c 10 Error multiple definition of `Hood_char' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\Symbol.c 10 Error multiple definition of `Home_char' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\Symbol.c 10 Error multiple definition of `Grad_char' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\Symbol.c 10 Error multiple definition of `Bell_char' Cloc_Meteo1_2 D:\Poligon\Cloc_Meteo1_2\Symbol.c 10 Пройдемся по ошибке Sand_char Делаем поиск по проекту, видим, что в файле Symbol.h определено следующее: Код const byte Sand_char[] PROGMEM={0,27,0,4,0,14,17,0}; //Загружаемый символ грусти Ищем какие файлы инклюдят этот Symbol.h #include "Symbol.h" имеется в Base.c и в Symbol.c Вы же помните да, что #include тупо добавляет текст из .h файла в .c файл? Вот мы и имеем ситуацию, когда в двух единицах трансляции(.с файлах) определена ГЛОБАЛЬНАЯ переменная const byte Sand_char[] Тоесть на выходе будут два .obj файла с такой перменной с двумя разными адресами в памяти. Линкер хочет при упоминании Sand_char найти адрес и подставить, но не может т.к. есть два кандидата с одинаковыми именами. И такая ситуация у вас там похоже по всем ошибкам. Как только вы поймете их природу и всё то, что я вам описал по поводу процесса сборки проекта - сможете пофиксить. Ситуацию с Sand_char можно разрулить несколькими способами: 1. добавить static Код static const byte Sand_char[] PROGMEM={0,27,0,4,0,14,17,0}; //Загружаемый символ грусти Таким образом каждый .c файл себе заинклюдит этот .h файл и получит статик, область видимости которого ограничена одной единицей трансляции(т.е. только этим компилируемым .с файлом). 2. У вас уже есть файл Symbol.c. Отлично! Перемещаем вот это Код const byte Sand_char[] PROGMEM={0,27,0,4,0,14,17,0}; //Загружаемый символ грусти в Symbol.c А в Symbol.h пишем Код extern const byte Sand_char[]; Я сейчас сходу не уверен как это скомпилится, возможно надо будет по-танцевать с прогмэмом, но смысл этого всего в следующем: Переменная глобальная переменная Sand_char будет экспортирована только из одного .obj файла(Symbol.obj) а все остальный .c файлы которые сделают #include "Symbol.h" - получат представление о том, что есть такая переменная описанная так-то, но сама эта переменная определена где-то в другом модуле(слово extern). Таким образом у линкера будет только одна копия Sand_char и он сможет подставить ее адрес везде где она используется. Второй способ мне в данном случае нравится больше и кажется более корректным.
--------------------
The truth is out there...
|
|
|
|
|
Jan 1 2018, 20:10
|
Профессионал
    
Группа: Свой
Сообщений: 1 508
Регистрация: 26-06-06
Из: Киев
Пользователь №: 18 364

|
В хидеры включены переменные(и константные переменные в т.ч.), что ошибочно. Всем объявлениям(но не extern-прототипам) переменных, а тем более объявлениям которые используются исключительно в пределах одного файла место только в .с-файле. Если есть желание какую либо переменную сделать глобальной(использовать в нескольких файлах), её прототип в .h-файле надо объявить как extern . Если глобальной хочется сделать структуру, в .h-файле объявляется её typedef, там-же объявляется прототип переменной через extern, а сама переменная структуры и её инициализация объявляется уже в .с-файле.
ЗЫ. Кстати, PROGMEM это макрос указывающий атрибут переменной поэтому как по мне лучше его объявить до имени переменной. const byte PROGMEM Home_char[] ={4,10,17,31,21,17,31,0}; //Загружаемый символ домика или даже так: PROGMEM const byte Home_char[] ={4,10,17,31,21,17,31,0}; //Загружаемый символ домика Тогда меньше непоняток в написанном возникает.
|
|
|
|
|
Jan 1 2018, 20:30
|

Просто Che
    
Группа: Свой
Сообщений: 1 567
Регистрация: 22-05-07
Из: ExUSSR
Пользователь №: 27 881

|
Цитата(Smoky @ Jan 1 2018, 21:40)  Прошу посмотреть фрагмент программы и сказать что я делаю не так. Студии у меня нет, посмотрел глазами. С функциями уже хорошо. Но переменные объявляете не правильно. 1) Все объявления переменных, по которым компилятор выделяет память, должны находится в начале .c файлов. В .h файлах помещаются только разные макросы и определения. То есть, всякие: volatile word sign_clcdur=0; двигаем в .c файл. Если эта переменная используется только в нем, больше ничего не нужно. Если она применяется еще в других файлах, тогда в соответствующий .h добавляется: extern volatile word sign_clcdur; И этот .h файл включается через #include туда, где применяется. 2) Структуры объявляются через typedef в .h файлах typedef volatile struct //Структура индексов { bit level:1; //Индекс текущего уровня звукового порта bit waiting:1; //Индекс ожидания готовности к формированию звукового сигнала } insg_t; extern insg_t insg; Само определение структуры, по которому выделяется память и проводится инициализация, в .c файле: insg_t insg = {0,0}; 3) Порядок нескольких #include имеет значение, т.к. компилятор работает последовательно, он не знает определений, которые будут "ниже", "позже".
|
|
|
|
|
Jan 1 2018, 21:10
|
Гуру
     
Группа: Свой
Сообщений: 2 702
Регистрация: 14-07-06
Пользователь №: 18 823

|
Цитата(sigmaN @ Jan 1 2018, 23:01)  ... Ситуацию с Sand_char можно разрулить несколькими способами: 1. добавить static ... Так можно ошибки разрулить, а ситуацию только усугубить. Наверняка автор предусматривал, что это одна и та же переменная. Первый способ создаст две переменных, ошибок не будет, но программ не будет работать.
--------------------
Уходя, оставьте свет...
|
|
|
|
|
Jan 1 2018, 21:24
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
Цитата Первый способ создаст две переменных, ошибок не будет, но программ не будет работать. Нет, в данном конкретном проекте это не станет проблемой. НО так как этот static способ не совсем правильный я внизу своего сообщения добавил: Цитата Второй способ мне в данном случае нравится больше и кажется более корректным.
--------------------
The truth is out there...
|
|
|
|
|
Jan 2 2018, 17:11
|

Местный
  
Группа: Свой
Сообщений: 401
Регистрация: 7-05-10
Из: Оренбург
Пользователь №: 57 135

|
Цитата(Baser @ Jan 2 2018, 02:30)  Студии у меня нет, посмотрел глазами. С функциями уже хорошо. Но переменные объявляете не правильно.
1) Все объявления переменных, по которым компилятор выделяет память, должны находится в начале .c файлов. В .h файлах помещаются только разные макросы и определения. Коллеги, вы "ломаете" все мои установки! Программировать начал с 2009 г. , оказалось что это лишь мои "вершки". Попробую всё сначала, спасибо за ценные советы. Предлагаю для анализа файл сообщений который выводит компилятор AVRStudio.
--------------------
Лень, оттвори дверь, сгоришь - а хоть и сгорю, но не оттворю.
|
|
|
|
|
Jan 2 2018, 19:18
|

Просто Che
    
Группа: Свой
Сообщений: 1 567
Регистрация: 22-05-07
Из: ExUSSR
Пользователь №: 27 881

|
Цитата(Smoky @ Jan 2 2018, 19:11)  Предлагаю для анализа файл сообщений который выводит компилятор AVRStudio. Коряво как-то Студия лог сборки выводит, ну да и ладно. Там у вас все время мелькает : "multiple definition of ..." Это говорит о том, что у вас есть дубликаты определений переменных, по которым выделяется память, находящиеся в разных файлах. Сверьтесь с моими рекомендациями выше. Для лучшего понимания подсказка: Определения, по которым НЕ выделяется память, и которые могут быть в .h файлах: #define enum typedef extern прототипы функций - типа void func(void); все остальное - в .c файлы .c файлы пока друг в друга через #include не включайте (это можно, но нужно понимать, что вы делаете). з.ы. еще может быть переопределение одних и тех же #define в разных местах, тоже будет ругаться.
|
|
|
|
|
Jan 2 2018, 21:30
|

I WANT TO BELIEVE
     
Группа: Свой
Сообщений: 2 617
Регистрация: 9-03-08
Пользователь №: 35 751

|
Цитата Коллеги, вы "ломаете" все мои установки! Программировать начал с 2009 г А разницы нет с какого года. При правильно подходе я думаю и за год можно знать и применять 98% всех свойств языка Си. В сравнении с С++ там вообще всё просто. Предполагаю, что не с того вы начали... Просто видимо набрали пару примеров, сформировали у себя какие-то представления и решали поставленные задачи исходя из этого представления. Без особого понимания что к чему. Вам бы начать изучение как положено с книжечек по языку. А так то и стандарт языка полистать было бы очень неплохо(но это уже на более поздних этапах будет уместно). Не думаю, что по Си придумали что-то лучшее чем сами авторы языка(за качество перевода не отсечаю, просто нагуглил) http://www.nsu.ru/xmlui/bitstream/handle/nsu/9058/kr.pdfВ понимании процесса сборки вам надо бы полистать документацию компилятора и линкера. Непонятные слова и выражения тут-же гуглить и пополнять свои знания. Ну может быть начните с документации GCC, я не знаю... https://gcc.gnu.org/onlinedocs/А может и что-то более удобоваримое можно нагуглить.. В любом случае вам нужно начинать с основ.
--------------------
The truth is out there...
|
|
|
|
|
Jan 3 2018, 08:00
|

Местный
  
Группа: Свой
Сообщений: 401
Регистрация: 7-05-10
Из: Оренбург
Пользователь №: 57 135

|
Цитата(sigmaN @ Jan 3 2018, 03:30)  Вам бы начать изучение как положено с книжечек по языку. А так то и стандарт языка полистать было бы очень неплохо(но это уже на более поздних этапах будет уместно). Да вы правы, как я уже говорил, всё придётся повторить сначала... Я и начал в 2009 году с изучения "С за 21 ден" Брэдли Л.Джонса и Питера Эйткена. Но моя беда в том что все примеры в этой книге основаны на одном файле .c и stdio.h. Поэтому, я думаю, понятно, почему я всё это время тоже использовал один файл .c и "кучу" заголовочных файлов. Всем спасибо за ценные советы и ссылки. Далее я попробую сам...
--------------------
Лень, оттвори дверь, сгоришь - а хоть и сгорю, но не оттворю.
|
|
|
|
|
Jan 12 2018, 06:13
|
Участник

Группа: Участник
Сообщений: 40
Регистрация: 20-05-12
Из: Санкт-Петербург
Пользователь №: 71 932

|
Для исключения повторной компиляции содержимого h-файла несколько раз еще можно использовать следующие приемы: Код #ifndef DEFINE_H_ #define DEFINE_H_
int i = 0;
#endif /*DEFINE_H_*/ или Код #pragma once
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|