Модульные подходы в целом довольно удобны (переносимы и чисты), поэтому я стараюсь программировать модули как можно более независимо от любых других модулей. Большинство моих подходов основаны на структуре, которая описывает сам модуль. Функция инициализации устанавливает первичные параметры, после чего обработчик (указатель на дескриптивную структуру) передается любой вызываемой функции внутри модуля.
Сейчас мне интересно, каков наилучший подход к распределению памяти для структуры, описывающей модуль. Если возможно, я бы хотел следующее:
- Непрозрачная структура, поэтому структура может быть изменена только с использованием предоставленных интерфейсных функций
- Несколько экземпляров
- память, выделенная компоновщиком
Я вижу следующие возможности, которые все противоречат одной из моих целей:
глобальная декларация
несколько экземпляров, все ссылки на компоновщик, но структура не непрозрачна
(#includes)
module_struct module;
void main(){
module_init(&module);
}
таНос
непрозрачная структура, несколько экземпляров, но все в куче
в module.h:
typedef module_struct Module;
в модуле init.c функция malloc и возврат указателя на выделенную память
module_mem = malloc(sizeof(module_struct ));
/* initialize values here */
return module_mem;
в main.c
(#includes)
Module *module;
void main(){
module = module_init();
}
объявление в модуле
непрозрачная структура, выделенная компоновщиком, только заранее определенное количество экземпляров
храните всю структуру и память внутри модуля и никогда не открывайте обработчик или структуру.
(#includes)
void main(){
module_init(_no_param_or_index_if_multiple_instances_possible_);
}
Есть ли возможность объединить их как-то для непрозрачной структуры, компоновщика вместо выделения кучи и нескольких / любого количества экземпляров?
решение
как предложено в некоторых ответах ниже, я думаю, что лучший способ заключается в следующем:
- резервное пространство для модулей MODULE_MAX_INSTANCE_COUNT в исходном файле модулей
- не определяйте MODULE_MAX_INSTANCE_COUNT в самом модуле
- добавьте #ifndef MODULE_MAX_INSTANCE_COUNT #error в заголовочный файл модулей, чтобы убедиться, что пользователь модулей знает об этом ограничении и определяет максимальное количество экземпляров, требуемых для приложения.
- при инициализации экземпляра вернуть либо адрес памяти (* void) описательной структуры, либо индекс модулей (что вам больше нравится)
Ответы:
Конечно, есть. Однако сначала следует признать, что «любое количество» экземпляров должно быть фиксированным или, по крайней мере, должна быть установлена верхняя граница во время компиляции. Это является обязательным условием для статического размещения экземпляров (то, что вы называете «распределением компоновщика»). Вы можете настроить число без изменения источника, объявив макрос, который его определяет.
Затем исходный файл, содержащий фактическое объявление структуры и все связанные с ним функции, также объявляет массив экземпляров с внутренней связью. Он предоставляет либо массив с внешней связью указателей на экземпляры, либо функцию доступа к различным указателям по индексу. Вариация функции немного более модульная:
module.c
Я предполагаю, что вы уже знакомы с тем, как заголовок затем объявит структуру как неполный тип и объявит все функции (написанные в терминах указателей на этот тип). Например:
module.h
Теперь
struct module
непрозрачен отличные перевод единицmodule.c
, * и вы можете получить доступ и использовать до количества экземпляров , определенных во время компиляции без динамического распределения.* Если, конечно, вы не скопируете его определение. Дело в том, что
module.h
это не так.источник
Я программирую небольшие микроконтроллеры на C ++, которые достигают именно того, что вы хотите.
То, что вы называете модулем, является классом C ++, он может содержать данные (доступные извне или нет) и функции (аналогично). Конструктор (выделенная функция) инициализирует его. Конструктор может принимать параметры времени выполнения или (мой любимый) параметры времени компиляции (шаблон). Функции в классе неявно получают переменную класса в качестве первого параметра. (Или, часто мое предпочтение, класс может действовать как скрытый синглтон, поэтому все данные доступны без этих накладных расходов).
Объект класса может быть глобальным (поэтому вы знаете, что во время компоновки все подойдет) или локальным в стеке, предположительно в основном. (Мне не нравятся глобальные переменные C ++ из-за неопределенного глобального порядка инициализации, поэтому я предпочитаю локальный стек).
Мой предпочтительный стиль программирования заключается в том, что модули являются статическими классами, а их (статическая) конфигурация определяется параметрами шаблона. Это позволяет избежать почти всех перегрузок и позволяет оптимизировать. Объедините это с инструментом, который рассчитывает размер стека, и вы можете спать без забот :)
Мой разговор об этом способе кодирования в C ++: объекты? Спасибо, не надо!
Многим программистам встраиваемых / микроконтроллеров не нравится C ++, потому что они думают, что это заставит их использовать весь C ++. Это абсолютно не обязательно, и было бы очень плохой идеей. (Вы, вероятно, тоже не используете все C! Думайте, куча, с плавающей точкой, setjmp / longjmp, printf, ...)
В комментарии Адам Хаун упоминает RAII и инициализацию. IMO RAII больше связан с деконструкцией, но его смысл верен: глобальные объекты будут создаваться до вашего основного запуска, поэтому они могут работать с неверными предположениями (например, с основной тактовой частотой, которая будет изменена позже). Это еще одна причина НЕ использовать глобальные объекты, инициализированные кодом. (Я использую скрипт компоновщика, который завершится ошибкой, когда у меня будут глобальные объекты, инициализированные кодом.) IMO такие «объекты» должны быть явно созданы и переданы. Это включает в себя объект «объекта ожидания», который предоставляет функцию wait (). В моей настройке это «объект», который устанавливает тактовую частоту чипа.
Говоря о RAII: это еще одна особенность C ++, которая очень полезна в небольших встроенных системах, хотя и не по той причине (освобождение памяти), которая больше всего используется в больших системах (небольшие встроенные системы в основном не используют динамическое освобождение памяти). Подумайте о блокировке ресурса: вы можете сделать заблокированный ресурс объектом-оберткой и ограничить доступ к ресурсу, чтобы он был возможен только через блокировку-обертку. Когда оболочка выходит из области видимости, ресурс разблокируется. Это предотвращает доступ без блокировки и делает гораздо менее вероятным забыть разблокировку. с некоторой (шаблонной) магией это может быть ноль.
В первоначальном вопросе не упоминался C, поэтому мой C ++ -центричный ответ. Если это действительно должно быть C ....
Вы можете использовать трюки с макросами: публично объявлять свои структуры, чтобы они имели тип, и их можно было распределять глобально, но искажать имена их компонентов за пределами удобства использования, если какой-то макрос не определен иначе, как в случае с файлом .c вашего модуля. Для дополнительной безопасности вы можете использовать время компиляции в календаре.
Или имейте общедоступную версию вашей структуры, в которой нет ничего полезного, и используйте закрытую версию (с полезными данными) только в вашем файле .c, и утверждайте, что они имеют одинаковый размер. Немного хитрости в make-file может автоматизировать это.
Комментарий @Lundins о плохих (встроенных) программистах:
Тип программиста, которого вы описываете, вероятно, создаст беспорядок на любом языке. Макросы (присутствующие в C и C ++) являются одним из очевидных способов.
Инструменты могут помочь в некоторой степени. Для моих учеников я предписываю встроенный скрипт, который задает no-excptions, no-rtti и выдает ошибку компоновщика, когда используется либо куча, либо глобальные символы с инициализацией кода. И это указывает предупреждение = ошибка и включает почти все предупреждения.
Я рекомендую использовать шаблоны, но с constexpr и концепциями метапрограммирование все меньше и меньше требуется.
«запутанные программисты Arduino» Я бы очень хотел заменить стиль программирования Arduino (проводка, репликация кода в библиотеках) на современный подход C ++, который может быть более простым, более безопасным и производить более быстрый и меньший код. Если бы у меня было время и силы ....
источник
Я считаю, что FreeRTOS (может быть, другая ОС?) Делает что-то вроде того, что вы ищете, определяя 2 разные версии структуры.
«Настоящий», используемый внутренне для функций ОС, и «поддельный», который имеет тот же размер, что и «настоящий», но не имеет внутри каких-либо полезных элементов (просто куча
int dummy1
и тому подобное).Только «поддельная» структура предоставляется вне кода ОС, и это используется для выделения памяти статическим экземплярам структуры.
Внутренне, когда функции в ОС вызываются, им передают адрес внешней «поддельной» структуры как дескриптор, и это затем преобразуется в тип как указатель на «реальную» структуру, поэтому функции ОС могут делать то, что им нужно делать.
источник
На мой взгляд, это бессмысленно. Вы можете оставить комментарий там, но нет смысла пытаться скрыть его дальше.
C никогда не обеспечит такую высокую изоляцию, даже если для структуры нет объявления, будет легко случайно перезаписать ее, например, по ошибке memcpy () или переполнением буфера.
Вместо этого просто дайте структуре имя и доверьте другим людям писать хороший код. Это также облегчит отладку, когда структура имеет имя, которое вы можете использовать для обращения к ней.
источник
Вопросы о чистом ПО лучше задать на /programming/ .
Концепция предоставления объекту неполного типа вызывающей стороне , как вы описываете, часто называется «непрозрачный тип» или «непрозрачные указатели» - анонимная структура означает что-то совсем другое.
Проблема в том, что вызывающая сторона не сможет выделить экземпляры объекта, только указатели на него. На ПК вы бы использовали
malloc
внутри объектов «конструктор», но malloc не используется во встроенных системах.Итак, что вы делаете во встроенном - это предоставление пула памяти. У вас ограниченный объем оперативной памяти, поэтому ограничение количества создаваемых объектов обычно не является проблемой.
См. Статическое распределение непрозрачных типов данных в SO.
источник