Реклама на сайте English version  DatasheetsDatasheets

KAZUS.RU - Электронный портал. Принципиальные схемы, Datasheets, Форум по электронике

Новости электроники Новости Литература, электронные книги Литература Документация, даташиты Документация Поиск даташитов (datasheets)Поиск PDF
  От производителей
Новости поставщиков
В мире электроники

  Сборник статей
Электронные книги
FAQ по электронике

  Datasheets
Поиск SMD
Он-лайн справочник

Принципиальные схемы Схемы Каталоги программ, сайтов Каталоги Общение, форум Общение Ваш аккаунтАккаунт
  Каталог схем
Избранные схемы
FAQ по электронике
  Программы
Каталог сайтов
Производители электроники
  Форумы по электронике
Помощь проекту

Микроконтроллеры, АЦП, память и т.д Темы касающиеся микроконтроллеров разных производителей, памяти, АЦП/ЦАП, периферийных модулей...

 
Опции темы
Непрочитано 08.05.2018, 23:10  
parovoZZ
Почётный гражданин KAZUS.RU
 
Регистрация: 15.11.2010
Сообщений: 2,374
Сказал спасибо: 338
Сказали Спасибо 328 раз(а) в 253 сообщении(ях)
parovoZZ на пути к лучшему
По умолчанию #include - оптимальное использование директивы

Привет.

У разных авторов в исходниках вижу разный подход к подключению заголовков. Кто-то создаёт main.h и включает его во все с файлы, где он требуется. У кого-то вообще путаница с зависимостями по кольцу. Здесь же на форуме вычитал про такой подход. Файлы заголовки /библиотек/ ничего не подключают, а на них ссылается main.c. При этом сами модули инклюдят одни и те же стандартные библиотеки (ctdint.h, io.h и пр.). К примеру, условный модуль EEPROM.c, в нем работаем с регистром памяти. И пока не подключим io.h, компилятор будет ругаться на неизвестное определение регистра, хотя тот же io.h в проекте есть. И наоборот - пользовательский заголовок, который лежит в папке с проектом и включен в IDE, но на него нет ссылки из main.c, к main подключается, но с предупреждением.
Так вот вопрос - где та грань, которая будет самой правильной?
Реклама:
parovoZZ вне форума  
Непрочитано 09.05.2018, 04:52  
mike-y-k
Модератор
 
Регистрация: 04.08.2010
Адрес: Москва СЗАО
Сообщений: 11,246
Сказал спасибо: 11,165
Сказали Спасибо 3,853 раз(а) в 2,924 сообщении(ях)
mike-y-k на пути к лучшему
По умолчанию Re: #include - оптимальное использование директивы

parovoZZ, собственно такой подход идёт от личных или командных предпочтений.
При смене команды его приходится менять на новый.
А в своих проектах - в любом удобном варианте, но структуру таки стоит определить для себя и ей следовать.
__________________
rtfm forever должно быть основой для каждого. Альтернатива грустна, поскольку метод слепого щенка успешно работает при весьма малом числе вариантов…
mike-y-k вне форума  
Непрочитано 09.05.2018, 09:20  
Easyrider83
Гуру портала
 
Аватар для Easyrider83
 
Регистрация: 27.10.2008
Адрес: ЕС
Сообщений: 10,835
Сказал спасибо: 918
Сказали Спасибо 4,308 раз(а) в 2,573 сообщении(ях)
Easyrider83 на пути к лучшему
По умолчанию Re: #include - оптимальное использование директивы

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

Последний раз редактировалось Easyrider83; 09.05.2018 в 09:23.
Easyrider83 вне форума  
Сказали "Спасибо" Easyrider83
mike-y-k (09.05.2018)
Непрочитано 09.05.2018, 09:48  
dgrishin
Почётный гражданин KAZUS.RU
 
Регистрация: 12.02.2013
Сообщений: 1,008
Сказал спасибо: 43
Сказали Спасибо 271 раз(а) в 212 сообщении(ях)
dgrishin на пути к лучшему
По умолчанию Re: #include - оптимальное использование директивы

Сообщение от parovoZZ Посмотреть сообщение
У кого-то вообще путаница с зависимостями по кольцу.
В каждом правильном .h файле есть директивы препроцессора
#ifndef имя_файла.h
#define имя_файла.h

// содержимое файла

#endif

так что #include можно добавлять сколько душе угодно - это ни на что не влияет.
dgrishin вне форума  
Сказали "Спасибо" dgrishin
mike-y-k (09.05.2018)
Непрочитано 09.05.2018, 10:17  
Исбанни
Прописка
 
Регистрация: 21.04.2018
Сообщений: 174
Сказал спасибо: 1
Сказали Спасибо 66 раз(а) в 53 сообщении(ях)
Исбанни на пути к лучшему
По умолчанию Re: #include - оптимальное использование директивы

Да, кто как привык, тот так и ляпает. Кому-то удобнее навалить всю кучу в один main.h, и затем его подключать везде. Кому-то удобнее иметь пару file.c и file.h и подключать file.h туда, где необходимо, создавая древовидную иерархию.
Тут еще гадит всю картину несовершенство языка Си в плане разделения, изоляции видимости. Любая ф-ция, которая не объявлена как static, будет видна во ВСЕМ проекте, независимо от инклюдов. Максимум - компилятор ругнется на то, что она явно не объявлена. Но имя ф-ции - видно везде и оно должно быть уникальным.
С #define дело попроще - они видны только там, где написаны или явно подключены .

Поэтому, вобщем-то языку Си всё равно, в какую кучу вы будете валить инклюды заголовков и всё равно, че вы будете писать в них.
А чтобы не было казусов с повторным подключением одного и того же заголовка к одному и тому же файлу (который приводит к ошибке переопределения), все заголовочные файлы должны иметь защиту в виде
#ifndef MY_FILE_H
#define MY_FILE_H
.... текст ....
#endif
, где MY_FILE_H - уникальное имя с указанием пути до файла, типа DRIVERS_EPD154_SPIDRV_H. Проги-редакторы (IDE разработчика) обычно умеют самостоятельно генерировать шаблон имени в дефайне.

Лично на мой вкус - мне больше нравится древовидная структура подключений. Хотя раньше пытался и так, и сяк писать.
Древовидная структура подразумевает, что из файла main.c вы все равно не будете напрямую вызывать функции, лежащие на самом нижнем уровне. Из main.c вы будете оперировать какими-то общими ф-циями, которые, в свою очередь, обращаются к низкоуровневым ф-циям.
На примере мигания светодиодом/
Файл main.c:
Код:
#include "signals.h"   // подключен файл сигналов

/* программная задержка */
void Delay(volatile int count)
{  for ( ; count › 0; Count--);  }

/*-----------------------------------*/
void main(void)
{
    int mode = 1;

    if (mode == 1)
        Signal_1();    // вызов ф-ции из файла signals.c
    else 
        Signal_2();   // вызов ф-ции из файла signals.c
}//-------------------------------------
Файл signals.h:
Код:
#ifndef SIGNALS_H
#define SIGNALS_H

/* объявления ф-ций для использования их в других ф-лах */
void Singal_1(void);  
void Singal_2(void);

#endif
Файл signals.c:
Код:
#include "signals.h"     // подключен собственный заголовок
#include "led_control.h"   // подключен файл управления светодиодом

extern void Delay(volatile int count);   //объявлена внешняя ф-ция для

/*-----------------------------------*/
void Singal_1(void)
{
    Blink(5, 100);    // 5 миганий с периодом 100 мс
    Delay(10000);
    Blink(10, 50);    // 10 миганий с периодом 50 мс
}//------------------------------------

void Singal_2(void)
{
    Blink(3, 10);    // 3 мигания с периодом 10 мс
}//-------------------------------------

Файл led_control.h:
Код:
#ifndef LED_CONTROL_H
#define LED_CONTROL_H

void Blink(int num, int period);

#endif
Файл led_control.с:
Код:
#include "stm32f1xx.h" // подключен заголовочник самого МК
#include "led_control.h" 

/*   макросы  */
#define LEDON    GPIOA-›BSRR = 1‹‹5
#define LEDOFF   GPIOA-›BRR = 1‹‹5

extern void Delay(volatile int count);

/*-----------------------------------*/
void Blink(int num, int period)
{
    for (i = 0; i ‹ num; i++)
    {  
        LEDON;
        Delay(period / 2);
        LEDOFF;
        Delay(period / 2);
     }
}//-------------------------------------
ну и так далее.
Видим, что в main.c мы вызываем только ф-ции из signals.c, значит, в main.c нам не надо подключать ничего, кроме #include "signals.h".
А вот extern void Delay(volatile int count) написана как extern и означает, что ее определение есть в каком-то другом файле.
Заголовочник с описанием регистров и битов МК подключается только в led_control.c и не передается выше, поскольку выше по уровням нигде не используются операции непосредственно с регистрами МК.

Хотя, в принципе, в процессе написания, чтобы не лезть глубоко в дерево, можно низкоуровневые подключения вывести в main.c, прописав #include "led_control.h", а так же #include "stm32f1xx.h" и получить доступ к низкоуровневым ф-циям.
(правда, макросы LEDON и LEDOFF нам не будут доступны, поскольку они написаны в led_control.c и не подключаются, но мы можем их перенести в led_control.h).
Это бывает удобно, когда еще сам не знаешь, как бы получше написать тот или иной кусок.
Но оставлять весь неиспользуемый мусор - не очень хорошо.

Что касается подключения драйверов, то лично я подключаю так же только те, которые конкретно используются в данном файле. Хотя конкретно Си не разграничивает видимость, но я просто не люблю навала всего и вся. То есть, в файл вывода на дисплей как-то нет смысла подключать драйвер клавиатуры. Ну так ведь, правда?
Да нет, кончено вы можете свалить все драйверы в кучу, "на всякий случай", это не запрещено законами языка Си. Но чисто для себя, когда разбираешься и думаешь: "ну нафига тут болтается драйвер клавиатуры, драйвер АЦП, да еще и драйвер ЕЕПРОМ, когда реально в этом файле используется всего лишь драйвер светодиода???"
С другой стороны, древовидные подключения более сложны для восприятия. Проще конечно свалить в одну кучу и знать, что в этой куче "как то всё само собой выберется что надо".

PS.
Еще один вариант для разнообразия безобразия. Можно переписать файл signals.c вот так:
Код:
#include "signals.h"
#include "stm32f1xx.h" // подключен заголовочник МК

/* макросы */
#define LEDON    GPIOA-›BSRR = 1‹‹5
#define LEDOFF   GPIOA-›BRR = 1‹‹5

extern void Delay(volatile int count);
static void Blink(int num, int period);    // объявлена статическая ф-ция из этого же файла

/*-----------------------------------*/
void Singal_1(void)
{
    Blink(5, 100);    // 5 миганий с периодом 100 мс
    Delay(10000);
    Blink(10, 50);    // 10 миганий с периодом 50 мс
}//------------------------------------
void Singal_2(void)
{
    Blink(3, 10);    // 3 мигания с периодом 10 мс
}//-------------------------------------

/*===================================*/
/*-- статическая ф-ция, используемая только в этом файще */
static void Blink(int num, int period)
{
    for (i = 0; i ‹ num; i++)
    {  
        LEDON;
        Delay(period / 2);
        LEDOFF;
        Delay(period / 2);
     }
}//--------------------------------------
...и после этого файлы led_control.c (.h) больше не нужны. Статическая функция Blink находится в signals.c и изолирована от других файлов, ее никак нельзя вызвать, кроме как непосредственно из файла signals.c, это тоже принцип языка Си. Так же остаются изолированы макросы LEDON, LEDOFF, и так же #include "stm32f1xx.h" не распространяется за пределы файла signals.c
Вот эти моменты надо помнить, разбираясь с подключениями.

Сообщение от parovoZZ Посмотреть сообщение
не подключают, а на них ссылается main.c
Вот этот момент я дико не понял. Как это - "не подключает, но ссылается"??? В языке Си нет такого понятия как "ссылка на файл". Файл может либо быть подключен (#include), либо не быть подключен.
И если будут вызваны какие-то функции, описанные в другом файле, но они не будут объявлены (прототип ф-ции) в текущем файле (или подключены через #include), то это противоречит концепции языка и будет выдано предупреждение. Ф-ция-то сама по себе и может вызываться, но компилятор не может проверить соответствие списка аргументов ф-ции и вызов может пройти с ошибками. Базовые принципы языка Си нарушать не следует.
Лучше почитать книжки по Си - авторы Керниган и Ритчи.

Последний раз редактировалось Исбанни; 09.05.2018 в 14:56.
Исбанни вне форума  
Непрочитано 09.05.2018, 12:07  
eddy
Почётный гражданин KAZUS.RU
 
Аватар для eddy
 
Регистрация: 27.01.2005
Адрес: Россия, КЧР, Нижний Архыз
Сообщений: 3,576
Сказал спасибо: 115
Сказали Спасибо 805 раз(а) в 582 сообщении(ях)
eddy на пути к лучшему
По умолчанию Re: #include - оптимальное использование директивы

Файла main.h вообще быть не должно, т.к. его никто кроме main.c инклюдить не будет → по принципу Оккама — в топку!
А вот для реализации каждого метода должен быть свой одноименный заголовочный файл. Если пишем для железа, то логично сделать еще один какой-то "общий" файл вроде pins_definition.h, где будут определяться нужные для работы ноги, макросы для работы с таймерами и прочее. В этом случае реализации методов не будут зависеть от конкретного чипа и конкретной распиновки и можно будет перетаскивать их в любое другое место — вот вам и сниппеты в чистом виде.
Понятно, что для элементарщины вроде помигать диодиком вообще не нужны заголовочные файлы, а вот когда у вас там намешано, то вполне может набраться полтора-два десятка файлов исходников с одноименными заголовочными файлами.

И еще: у gcc есть #pragma once, которая позволяет заменить классический неуклюжий include guard (хотя, у меня это генерится макросом в geany и я не парюсь: ставлю сразу #pragma once, а за ней — стандартный include guard).
__________________
Смерть бандеровской мразоте!
eddy вне форума  
Непрочитано 09.05.2018, 13:37  
Исбанни
Прописка
 
Регистрация: 21.04.2018
Сообщений: 174
Сказал спасибо: 1
Сказали Спасибо 66 раз(а) в 53 сообщении(ях)
Исбанни на пути к лучшему
По умолчанию Re: #include - оптимальное использование директивы

Ой, да кому как приятнее, тот так и пишет Языку Си глубоко начхать, можно хоть в одном файле всё написать. У Си всего один уровень вложенности, и для него важно, чтобы соблюдались лишь его принципы: перед вызовом ф-ции или использованием переменной их надо сначала объявить, то есть, указать имя и тип переменной, определить набор параметров у ф-ции. Это - и есть главное требование языка. Остальное - заморочки для удобств программиста.
Я когда-то раньше перепробовал разные варианты. Какие-то мелочи для тестов писал вообще в одном файле с подключением только заголовка МК. Пробовал скидывать в один main.h или позже называл его common.h. Оказалось, это неудобно, лично для меня. Хотя для языка Си - похрен.
Я не думаю, что на первых порах вы будете подключать очень уж много файлов. А потом, по мере накопления опыта, вы сами поймете и найдете свой путь.

"Золотая середина" в том и кроется, чтобы найти ту грань между скидыванием в одну кучу и дроблением на мелкие части. И раз уж языку Си практически похрен, лишь бы базовые принципы соблюдались, то грань "правильности" будет определяться самим программистом, его собственными соображениями удобства.
Исбанни вне форума  
Непрочитано 09.05.2018, 14:22  
eddy
Почётный гражданин KAZUS.RU
 
Аватар для eddy
 
Регистрация: 27.01.2005
Адрес: Россия, КЧР, Нижний Архыз
Сообщений: 3,576
Сказал спасибо: 115
Сказали Спасибо 805 раз(а) в 582 сообщении(ях)
eddy на пути к лучшему
По умолчанию Re: #include - оптимальное использование директивы

Сообщение от Исбанни Посмотреть сообщение
можно хоть в одном файле всё написать
Встречал я такие "однофайловые" утилиты на 15-20 тысяч строк! Читать их — сплошное мучение. А уж если надо править... Иногда проще бывает с нуля сделать, чем такого демона "причесать". Хотя, рефакторинг — он всегда дело неблагодарное, и уж тем более рефакторинг чужого кода.
__________________
Смерть бандеровской мразоте!
eddy вне форума  
Непрочитано 09.05.2018, 14:38  
Исбанни
Прописка
 
Регистрация: 21.04.2018
Сообщений: 174
Сказал спасибо: 1
Сказали Спасибо 66 раз(а) в 53 сообщении(ях)
Исбанни на пути к лучшему
По умолчанию Re: #include - оптимальное использование директивы

Ха. А вы исходник стандартной либы printf видели? Более 2000 строк. И ведь не разобьешь по файлам, ибо незачем, это единый блок, делающий одну работу.

Так что, нужно знать разумные границы - что как куда почем и зачем.
Повторюсь - язык Си не понимает уровней вложенности и не умеет разграничивать видимость модулей, видимость имеет только один уровень вложенности.
Исбанни вне форума  
Непрочитано 09.05.2018, 17:31  
eddy
Почётный гражданин KAZUS.RU
 
Аватар для eddy
 
Регистрация: 27.01.2005
Адрес: Россия, КЧР, Нижний Архыз
Сообщений: 3,576
Сказал спасибо: 115
Сказали Спасибо 805 раз(а) в 582 сообщении(ях)
eddy на пути к лучшему
По умолчанию Re: #include - оптимальное использование директивы

Сообщение от Исбанни Посмотреть сообщение
А вы исходник стандартной либы printf видели?
Не видел, и не увижу — на эту вирусную ссылку firefox ругается.
Но это явно не исходник "стандартной либы", т.к. иначе ссылка была бы на гитхабовскую репу stdlib!
В объем верю (лень смотреть в исходниках), там один только ман чего стоит! Поди-ка все эти модификаторы обработай!!! Вот и издеваюсь над теми, кто, пусть даже и урезанный (но "стандартный"), printf в мелкоконтроллеры пихает, обзывая их абдуринщиками.
__________________
Смерть бандеровской мразоте!
eddy вне форума  
 

Закладки
Опции темы

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.

Быстрый переход

Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
OLED ssd1306 + STM32f030f4 miwutka Песочница (вопросы новичков) 195 07.01.2019 15:38
AVR Studio + CVAVR: директивы #include SwanSwan AVR 2 30.10.2016 18:50
Светодиоды "Straw Hat" - оптимальное использование mikesmith Отвлекитесь, эмбеддеры! 10 09.03.2014 02:28
usb cdc pic18f14k50 gromovi Proteus, KiCAD и другие ECAD 9 21.04.2013 15:31
В какой программе компелить код (подключение #include ) FedorChek Микроконтроллеры, АЦП, память и т.д 4 04.05.2009 20:00


Часовой пояс GMT +4, время: 00:22.


Powered by vBulletin® Version 3.8.4
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd. Перевод: zCarot