Главная arrow Форум  
15.11.2019 г.
Главная
Проекты
Статьи
Начинающим
Архив новостей
Ссылки
Контакты
Поиск
Файлы
Форум
Карта сайта
Авторизация





Забыли пароль?
Ещё не зарегистрированы? Регистрация
Поддержи наш сайт!
Через WebMoney

 R785211844650
 Z210696637574
 E368177590409

Форум ARV Research
Добро пожаловать, Гость
Пожалуйста Вход или Регистрация.
Забыли пароль?
_GEN_GOTOBOTTOM Ответить

TOPIC: Модульная программа

#5855
ValBag (Пользователь)
Новичок
Постов: 8
graphgraph
Модульная программа 05.12.2010 07:12 Репутация: 0  
Начинаю осваивать Си, применительно к МК. Модульное программирование еще не пробовал, т. к. возникли некоторые вопросы теоретического характера. Поэтому попытаюсь изложить их, абстрагируясь от конкретного МК, среды программирования и целесообразности модульного принципа.
В случае единственного файла исходной программы, его структура понятна. Далее, начнем с малого.
Допустим, что добавлен некоторый участок кода, который не включен в основной текст, а составлен в отдельном файле. Тогда, очевидно, необходимо присоединить его директивой #include, а главную программу, где есть функция main, препроцессор найдет самостоятельно? Т. е. уже получается какой-то модуль.
Дальнейшее структурирование предполагает выделение важных частей модуля в заголовочный и исполняемый файлы. Рассматривая, в качестве примера, файлы заголовков среды, содержащих исполняемую часть, можно обнаружить в конце, ссылку на файл библиотеки. Всё хорошо. Смотрю проект с модулями. В каждом из заголовочных файлов есть конструкция с директивами препроцессора, не позволяющая многократно переобъявлять идентификаторы Си. Тоже хорошо. А как включается исполняемая часть модуля – не могу найти ?
Далее, другой момент. Привожу цитату из трудов ув. ARV:Чтобы обеспечить видимость глобальных объявлений модуля из других модулей, необходимо вынести их в заголовочный файл, который подключить директивой #include в нужном модуле. Объявлять переменные в заголовочном файле - неверная практика, т. к. это может привести к многократному переобъявлению одних и тех же идентификаторов в различных модулях, т. е. всюду, где заголовочный файл будет подключен.Что значит «вынести», т. е. объявить их в заголовке? Для этого, в принципе, и нужны «хэшеры», т. е. для объявления прототипов функций, глобальных переменных и идентификаторов. Но: «…Объявлять переменные в заголовочном файле - неверная практика…». Переобъявление ликвидируется вышеприведнной конструкцией. В чем смысл цитируемого утверждения?
  Для добавления сообщений Вы должны зарегистрироваться или авторизоваться.
#5856
ARV (Администратор)
Администратор
Постов: 2384
graph
В ответ на: Модульная программа 05.12.2010 10:03 Репутация: 175  
думаю, надо сделать экскурс в модульность. итак:
1. модуль - это не просто вынесенный в отдельный файл кусок основной программы, модуль должен быть логически завершенным. например, если вы пишите программу с использованием динамической индикации на 7-сегментных индикаторах, то у вас наверняка будут функции "перевода" чисел в 7-сегментный формат, функции реализации динамической индикции. Наверняка так же будут и другие функции, связанные с индикацией, например, отдельными светодиодами или звуковыми сигналами. Было бы не совсем правильным вынести в отдельный модуль функцию перевода чисел в 7-сегментный формт, а остальное оставить в главном модуле - это приводит к логическому разрыву функциональности. Правильнее было бы сосредоточить все, что связано с индикацией, в отдельном модуле: и преобразование, и мигание, и пищание.
2. модуль - это отдаельный вайл с расширением ".c". компилятор все файлы проекта с таким расширением обрабатывает независимо, формируя для каждого отдельный объектный файл. То есть 5 модулей - 5 объектных файлов. внутри объектных файлов имеется информация для компоновщика (линкера), на основании которой он собирает из нескольких раздельных файлов единственный итоговый. в "больших" компьютерах итоговым является исполняемый файл, ну а для МК его переделывают в "прошивку", т.е. то, что умеет понимать программатор - в общем, это уже тонкости.
3. чтобы модуль скомпилировался успешно, необходимо, чтобы он был "вещью в себе", т.е. содержал внутри себя все необходимое: все переменные, константы, функции и т.п. - в общем, все, что ему нужно. если модулю нужно что-то из других модулей, он должен быть как-то информирован о том, где это самое нужное имеется. такое информирование делается при помощи #include - подулючения файла-заголовка или хидера. файл, содержащий главную функцию main - точно такой же равноправный модуль, как и другие - вы же привыкли указыват в его начале несколько подключений хидеров... это как раз и позволяет в главном модуле пользоваться возможностями из других модулей. аналогично и вообще для всех прочих.
Не стыдно не знать, стыдно не учиться
  Для добавления сообщений Вы должны зарегистрироваться или авторизоваться.
#5857
ARV (Администратор)
Администратор
Постов: 2384
graph
В ответ на: Модульная программа 05.12.2010 11:02 Репутация: 175  
4. из вышесказанного приходим к мысли о том, что же именно должно быть в хидере: все то, что может потребоваться ДРУГИМ модулям. если при вычислении синуса нам нужна вспомогательная переменная - нужна ли она в других модулях? нет. Если для интерполирующей функции требуется функция вычисления квадрата и квадратного корня - потребуются ли они "снаружи" - очень может быть. вот так, проанализировав все, что касается нашего модуля мы получим перечень того, что должно быть видно снаружи, а что нет. все видимое должно быть размещено в хидере, все невидимое - внутри файла-модуля.
5. а если требуется сделать видимой глобальную переменную модуля? только что мы пришли в кыводу, что такая переменная должна быть в хидере. а теперь задумайтесь: наш проект состоит из 5 модулей, причем 4 модуля используют возможности пятого, т.е. подключают его хидер. и в этом самом 5-ом модуле имеется та самая глобальная переменная. что же выйдет в итоге? а то, что в теле каждого из всех 5-и модулей окажется определение одной и той же глобальной переменной! это явная ошибка, компилятор такого не пропустит (точнее, линкер). Но выход есть! в хидере надо объявлять эту переменную, как ВНЕШНЮЮ (extern). Тогда каждый модуль с подключением хидера будет знать, что переменная "где-то там имеется" - все нормально. в том модуле, где эта переменная и определяется, так же ничего страшного не произойдет - сначала в модуле будет казано наличие внешней переменной, а потом эта переменная будет объявлена - оказалось, что она не внешнаяя для этого модуля, а локальная - это не ошибка! вот и все.
6. таким образом, резюмируя сказанное, можно получить ряд простых правил построения модульного проекта:
-- каждый модуль должен быть "тематическим"
-- каждый модуль должен сопровождаться файлом-хидером
-- каждый модуль должен подключать свой собственный хидер
-- в хидере надо предпринять меры против многократного его использования при неоднократном подключении (например, если директива #include помещается в хидере другого модуля)
-- в хидере надо определять все вещи, доступные извне модуля, а так же влияющие на модуль извне, все остальное должно определяться внутри модуля (т.е. будет невидимо извне)
-- при необходимости сделать видимой глобальную переменную модуля (т.е. сделать ее глобальной по отношению ко всему проекту), надо обявить ее в хидере как extern, а в теле модуля - как обычно.

хочу только дополнить, что практика глобальных переменных, тем более "интермодульных", - не очень хорошая практика, по мере сил следует избегать ее.
Не стыдно не знать, стыдно не учиться
  Для добавления сообщений Вы должны зарегистрироваться или авторизоваться.
#5876
ValBag (Пользователь)
Новичок
Постов: 8
graphgraph
В ответ на: Модульная программа 06.12.2010 19:04 Репутация: 0  
Спасибо за ответы.

п. 1. Тут и так все интуитивно понятно, в смысле стратегии. По тактике вопросы скорее всего будут, когда проявятся.

п. 2. Как я уже спрашивал: как «добирается» компилятор до файла пресловутого модуля с раширением « .с »? В хидере (или хэдере – header), например: mod_1.h, который определенным образом (директивой) подключен к главной программе с функцией main, нет строки, что нужно еще включить текст программы исполняемой части модуля – mod_1.c. Или он по умолчанию обрабатывет все файлы « .с » в рабочей папке? И что будет в таком случае, если я «положу» туда файл модуля без хидера (точнее текст из mod_1.h напишу сразу в файле mod_1.c непосредственно), убрав #include <mod_1.h> в главном файле?
В случае, когда не создается свой модуль, а привлекается из библиотеки, то там в заголовочном файле, как я уже говорил, есть строка о включении нужной части исполняемого файла библиотеки.

п. 6. Что-то я совсем запутался с extern. Может такие методы и не понадобятся мне, но хотелось бы понять смысл.при необходимости сделать видимой глобальную переменную модуля (т. е. сделать ее глобальной по отношению ко всему проекту), надо объявить ее в хидере как extern, а в теле модуля - как обычно.Т. е. extern означает глобальную переменную модуля, для внешего использования (ну и для внутреннего тоже)?
В одной книге, в примере, в главной программе переменная объявлена как extern в глобальной области, а в присоединяемом хидере просто определена, как глобальная, т. е. объявлена и инициализирована.
Приведу цитату из справочника по Си Шилдта:
«Спецификатор extern играет большую роль в программах, состоящих из многих файлов. В языке С программа может быть записана в нескольких файлах, которые компилируются раздельно, а затем компонуются в одно целое. В этом случае необходимо как-то сообщить всем файлам о глобальных переменных программы. Самый лучший (и наиболее переносимый) способ сделать это — определить (описать) все глобальные переменные в одном файле и объявить их со спецификатором extern в остальных файлах»
В обеих случаях получается другой смысл сказанному вами. Глобальная переменная модуля просто объявляется, инициализируется при необходимости, а остальные модули для её использования внутри себя, объявляют эту же переменную с классом памяти extern. ? ...Если я правильно понял оба эти случая и смысл вашего утверждения.
Может быть приведете простой пример программы, кроме словесного описания?
Да, и попутный вопрос. К функциям не относятся понятия: глобальная, локальная, поэтому они должны быть видны везде?

Содержимое поста отредактировано: ValBag, в: 06.12.2010 19:15

Содержимое поста отредактировано: ValBag, в: 06.12.2010 22:38

Содержимое поста отредактировано: ValBag, в: 06.12.2010 22:40
  Для добавления сообщений Вы должны зарегистрироваться или авторизоваться.
#5877
ARV (Администратор)
Администратор
Постов: 2384
graph
В ответ на: Модульная программа 06.12.2010 23:15 Репутация: 175  
п. 2. Как я уже спрашивал: как «добирается» компилятор до файла пресловутого модуля с раширением « .с »? В хидере (или хэдере – header), например: mod_1.h, который определенным образом (директивой) подключен к главной программе с функцией main, нет строки, что нужно еще включить текст программы исполняемой части модуля – mod_1.c. Или он по умолчанию обрабатывет все файлы « .с » в рабочей папке? И что будет в таком случае, если я «положу» туда файл модуля без хидера (точнее текст из mod_1.h напишу сразу в файле mod_1.c непосредственно), убрав #include <mod_1.h> в главном файле?добирается он просто: компилируются все файлы, включенные в проект. в данном случае проект - это не просто слово, это либо определенным образом составленный makefile, либо (если вы работаете в AVR Studio) - простой список файлов. хидеры никак не влияют на компиляцию сишных исходников! AVR Studio автоматически строит makefile с теми файлами, которые вы вручную добавили в проект, а вот плагин для Eclipse поступет иначе: тупо компилит все подряд файлы с расширениес *.c.
«Спецификатор extern играет большую роль в программах, состоящих из многих файлов. В языке С программа может быть записана в нескольких файлах, которые компилируются раздельно, а затем компонуются в одно целое. В этом случае необходимо как-то сообщить всем файлам о глобальных переменных программы. Самый лучший (и наиболее переносимый) способ сделать это — определить (описать) все глобальные переменные в одном файле и объявить их со спецификатором extern в остальных файлах»

В обеих случаях получается другой смысл сказанному вами.
ничуть! смысл как раз такой, как я и писал! смотрите: в одном модуле вы описываете переменную, а в хидере, который подключаете к другим файлам - указываете для этой же переменной extern - вот и получется, что во все файлы попадает ссылка extern на переменную, но только в одном-единственном будет сама эта переменная! вот пример:
Code:

 // demo.h содержит extern long counter; // главный модуль main.c содержит: #include "demo.h" // модуль demo.c содержит: #include "demo.h" long counter 123;

получается, что строка extern long counter попадает и в модуль main.c и в модуль demo.c, но только в модуле demo.c будет реальное выделение памяти под переменную и присваивание ей начального значения!

К функциям не относятся понятия: глобальная, локальная, поэтому они должны быть видны везде?еще как относится! только чтобы функция была глобальной для всего проекта, не нужно объявлять ее extern - все функции и так по умолчанию являются "видимыми" во всех модулях. чтобы компоновщик безошибочно подключил их в нужные моменты, обязательно надо подключать хидер с прототипами функций!

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

 int global_func(int); // прототип глобальной функции всего проекта static int local_func(int); // прототип функции, видимой только в своем модуле

Не стыдно не знать, стыдно не учиться
  Для добавления сообщений Вы должны зарегистрироваться или авторизоваться.
#5882
ValBag (Пользователь)
Новичок
Постов: 8
graphgraph
В ответ на: Модульная программа 07.12.2010 10:32 Репутация: 0  
ARV писал(а):
добирается он просто: компилируются все файлы, включенные в проект.... ...хидеры никак не влияют на компиляцию сишных исходников!Ну вот, теперь наступает "просветление". Тогда получается, что неразделенный файл модуля (в качестве примера, оставляя в стороне целесообразность), также обработается, без всяких дополнительных директив о включении?
ничуть! смысл как раз такой, как я и писал!Да, действительно, если смотреть под этим углом - все хорошо. А с примером еще понятнее, т. к. словесное описание иногда можно толковать по разному, как в классическом примере расставления запятых - " Казнить нельзя помиловать".
еще как относится! только чтобы функция была глобальной для всего проекта, не нужно объявлять ее extern - все функции и так по умолчанию являются "видимыми" во всех модулях.Я это как раз и хотел спросить - про extern на функции, но неудачно выразился и позабыл про static.
Попутно еще вопрос на эту тему. Немного измененый ваш пример:
Code:

 // demo.h содержит #ifndef demo_H #define demo_H extern long counter; #endif // главный модуль main.c содержит: #include "demo.h" // модуль demo.c содержит: #include "demo.h" long counter 123;

Если присутствуют три указанные директивы в начале, файл скомпилируется один раз, и переменная больше нигде не переобъявится. Тогда вроде и extern не нужен?
  Для добавления сообщений Вы должны зарегистрироваться или авторизоваться.
#5884
ARV (Администратор)
Администратор
Постов: 2384
graph
В ответ на: Модульная программа 07.12.2010 12:18 Репутация: 175  
ValBag писал(а):
Если присутствуют три указанные директивы в начале, файл скомпилируется один раз, и переменная больше нигде не переобъявится. Тогда вроде и extern не нужен?
похоже, вы или не поняли, что компилируется, или не чувствуете разницы между модулем и хидером.
директива #include просто вставляет содержимое указанного файла, поэтому текст исходника модуля как бы расширяется на это самое содержимое, а затем этот модифицированный исходник компилируется. таким образом, в каждый из исходников, где встретится #include "demo.h", попадет содержимое
// demo.h содержит
#ifndef demo_H
#define demo_H
extern long counter;
#endif

компилироваться же будут оба модуля независимо друг от друга. так как определение функции содержится только в одном модуле - оно и будет скомпилировано, а остальные модули просто получат уведомление о существовании переменной.
Не стыдно не знать, стыдно не учиться
  Для добавления сообщений Вы должны зарегистрироваться или авторизоваться.
#5889
ValBag (Пользователь)
Новичок
Постов: 8
graphgraph
В ответ на: Модульная программа 08.12.2010 08:43 Репутация: 0  
ARV писал(а):
похоже, вы или не поняли, что компилируется, или не чувствуете разницы между модулем и хидером.Похоже, что я уже вызываю у вас раздражение. ...Что делает #include в каждом учебнике написано. Вопрос был другой "^". Пока потренируюсь практически, потом если не против, можно вернуться

Содержимое поста отредактировано: ValBag, в: 08.12.2010 08:51
  Для добавления сообщений Вы должны зарегистрироваться или авторизоваться.
#5891
ARV (Администратор)
Администратор
Постов: 2384
graph
В ответ на: Модульная программа 08.12.2010 10:48 Репутация: 175  
комплекс надоеды присущ всем начинающим, опасаться тут нечего не будешь спрашивать - не будешь учиться. проблема в другом: видимо, вы задаете вопрос так, что я не могу понять его смысл по-вашему, а понимаю по-своему, соответственно ответ оказывается не тем - накопление ошибки
если нужно - попытайтесь сформулировать вопрос иначе, возможно, смысл проявится четче.

по поводу #include: мне доводилось встречать статеечки, рекомедующие строить проект путем подключения сишных файлов #include "module2.c" (типа так). это абсолютно законный (в смысле - допустимый синтаксисом языка) метод, но, мягко говоря, нерекомендуемый так что подразумевая всякое, я счел допустимым снова повторить прописные истины...

Содержимое поста отредактировано: ARV, в: 08.12.2010 10:50
Не стыдно не знать, стыдно не учиться
  Для добавления сообщений Вы должны зарегистрироваться или авторизоваться.
#5895
ValBag (Пользователь)
Новичок
Постов: 8
graphgraph
В ответ на: Модульная программа 08.12.2010 16:40 Репутация: 0  
Спасибо за понимание. Попробую по другому. Раз есть конструкция в заголовочном файле:

#ifndef demo_H
#define demo_H
..............
#endif

которая, как утверждается, исключает из компиляции повторный текст (.......), чтобы не было переобъявления переменных, то захотелось проверить её действие. Допустим, нельзя ли для внешней переменной, которая находится как раз там (.......) убрать слово extern. Тогда она не должна бы объявляться более одного раза, в соответствии с назначением этих директив? Попробовал - не получилось. Несмотря на директивы, компилятор говорит, что переменная переобъявлена, т. е. без extern - никуда. Вернул extern на место. Далее, вытащил из (.......) прототип функции модуля за пределы блока конструкции. Все хорошо, все работает, вопросов у комилятора нет. Затем убрал все эти директивы, оставив только определения внешней переменной и прототипа функции - тоже все работает. Теперь получается другая крайность - этой конструкции "до лампочки" двойное объявление: и переменной, и прототипа. Тогда на какие лексемы действует вышеприведенный "столбик"?
  Для добавления сообщений Вы должны зарегистрироваться или авторизоваться.
_GEN_GOTOTOP Ответить
© Copyright 2007 Best of Joomla, Работает на FireBoardполучить последние сообщения прямо на Ваш рабочий стол