Comments 49
P.S.: на навесной плате проблем не было, потому что я на автомате подключал всё правильно.
Интересно, сколько народа на этом прокололось, в процессе поменяв корпус на более жирный, а ножки оставив прежние.
Но фокус в том, что не совпадает не положение ножек на корпусе, а их номер в порту, то есть сущность чисто виртуальная.
На Meeting C++ 2015 демонстрировали ряд приемов, как избавляться в частности от выделения на куче с помощью шаблонов и compile-time оптимизации в статические объекты. Очень много метапрограммирования.
Еще сейчас в рамках работы над embedded Rust очень активно разрабатывают решение в принципе этих же проблем: оно очень хорошо ложится на Rust-овскую философию "все проверяем на compile-time". Пока уверенно работает только на некоторых stm32 и немножко на msp430, пока не продакшн, но для некоторых проектов уже можно. В любом случае сильно проще C++ получается.
А ведь до того момента я об ее объеме даже и задумывался. На черта ли сдалась мне эта куча, полагал я. Все равно у меня все переменные и объекты либо статические, либо лежат на стеке. Так, просто по инерции оставил под нее 0х300 байт, раз уж какой-то объем под кучу выделяется во всех шаблонных проектах. Ан нет вот, для рантайма С++ все равно нужна динамически выделяемая память, причем в достаточно заметных, по меркам контроллеров, количествах.
С этим можно справится, если переопределить функцию, которая вызывается после main'a. Если у вас ARM (и Keil), то эта функция называется __aeabi_atexit
. Если сделать ее пустой, то куча будет не нужна.
int __aeabi_atexit(void)
{
return 1;
}
Я вообще предпочитаю делать #pragma import(__use_no_heap_region)
, чтобы сразу получать ошибку компиляции, если кто-то где-то пытается трогать кучу.
Уточню, переопределение __aeabi_atexit поможет только от вот этого вызова деструкторов для статических объектов после завершения main.
Вообще, статические объекты в области видимости функции, зная как они создаются и разрушаются, а разрушаться, по стандарту, они должны в строго противоположном конструированию порядке (поэтому и замут с atexit
, так бы просто: аллоцировали памяти под объект чуть больше и флаг "создан/нет" туда, а затем в список деструкторов, как для глобальных статических объектов /которые в единице трансляции, ну или просто глобальные/), стоит избегать, как вносящие дополнительную путаницу и неопределённость, ровно, как и динамическая аллокация памяти.
А вообще, у меня на проекте с Cypress FX3 рантайм стал самописным, только строго то, что нужно /зато потом на язык и код смотришь сильно по другому, очень многое становится на свои места/. Хотя динамическая аллокация используется в коде.
Ну и если пошло про malloc
, то, по крайней мере в случае с Newlib, ещё стоит реализовать свои __malloc_lock()
/__malloc_unlock()
. Даже без RTOS, как минимум два контекста есть: ISR и пользовательский. На большом проекте может что-то просочиться и попытаться сделать malloc()
из ISR.
Поправка, на самом деле все немного не так, но это не очень существенно:
до main'a при каждом вызове конструктора статического или глобального объекта, через длинную цепочку вызывается __aeabi_atexit, который должен "зарегистрировать" этот объект для удаления после вызова main'a (когда вызывается _sys_exit).
Я в очередной раз повелся на название "atexit", но не суть, работать это работает.
__aeabi_atexit
, чтобы он вызвался при выходе из программы… и вот уже __aeabi_atexit
— выделяет память.Соответственно если вместо
__aeabi_atexit
у вас «пустышка» — то память никто не аллоцирует и куча не нужна.Да, речь тут только про вспомогательную память на куче, которая требуется для деструкторов статичских объектов. В куче создается только список каких-то записей про объекты, а не они сами.
Кстати для выравнивания можно использовать
std::aligned_storage
Вот тут пример использования я накидал:Синглтон создающий объекты в RAM и RОМ
Вообще, если хочется поиграться, то начиная с Кейла 5.23 примерно можно выбрать другой компилятор — armclang, форк clang'a, — который вполне умеет в С++14. И стандартная библиотека к нему идет нормальная, а не от С++03.
Я лично им пока побаиваюсь пользоваться в продакшене, потому что некоторые вещи там еще сыроваты, но потрогать уже можно. Заодно время компиляции снижается очень значительно.
Собственно да, использование динамического выделения памяти не хорошо влияет на надежность программы
Да ладно, экстремизм это все (впрочем — сам с этим жил и не жаловался). Если один раз в начале программы, ну или по крайней мере в процессе инициализации (который может быть не только в самом начале) — то и не страшно. Нужно только возвращаемые значения проверять аккуратно (само собой я про malloc).
Можно взамен использовать плейсмент нью с выравниванием
Вот тут не понял. Как new () принципиально облегчает описанную ситуацию и делает создание объектов более безопасным?
Примерно так.Можно взамен использовать плейсмент нью с выравниваниемВот тут не понял. Как new () принципиально облегчает описанную ситуацию и делает создание объектов более безопасным?
Вы в других местах описываете
extern Foo foo;
, а в нужном месте отводите под него память и насильно заставляете компилятор вызвать конструктор.Хотя строго говоря это нарушение ODR и есть вероятность получить приключения при использовании LTO, так что если уж хотеть быть «святее папы римского», то вот так.
Хотя если переносимость не нужна, то можно сущности без необходимости и не умножать.
А вот ваш второй пример (спасибо за него) я не совсем понял. Зачем нужна строка 12? Мне показалось, что Foo& foo впоследствии не использовано.
Зачем нужна строка 12? Мне показалось, что Foo& foo впоследствии не использовано.Рука-лицо. Да, здесь не использовано, конечно — но зато вся остальная программа может обращаться к
foo
, считая, что это просто глобальный объект.Компилятор, видя, что
foo
ссылается на foo_placeholder
просто заменит все ссылки на foo_placeholder
во всех местах — а вам не нужно будет городить никакого синтаксического сахара и вообще помнить о том, что объекты у вас — это не просто глобалы, а что они размещены через placement new…Как-то примерно так (сравните код функций Bar и Baz). На x86-64 лучше видно, что ссылка извелась, но и на ARM — это тоже происходит…
P.S. Вообще про этот трюк мне рассказали знакомые из Гугла — правда он там для других целей используется: в многопоточной программе бывает очень больно, когда
main
успешно завершился и основной поток все глобалы поудалял, а какие-то другие потоки к ним всё ещё обращаются. Схема «placeholder, placement new, да плюс ссылка» позволяет перевести в программе все глобалы на эту схему изменив только заголовок — что очень полезно, когда у вас программа состоит из сотен тысяч файлов… понятно, что для удобства там к этой схеме ещё пару макросов добавили — но тут, я думаю, вы и сами справитесь… меня просто позабавило, что трюки от «сурового HighLoad'а», где бинарники имеют вес в сотни мегабайт, а объёмы памяти меряются в десятках гигабайт — применимы, оказывается, и в микроконтроллерах…Рука-лицо. Да, здесь не использовано, конечно — но зато вся остальная программа может обращаться к foo, считая, что это просто глобальный объект.
А, ну это да. Просто это к самой инициализации не относится, вот я и спросил.
И вообще — не будьте строги к идиотам. :)
Компилятор, видя, что foo ссылается на foo_placeholder просто заменит все ссылки на foo_placeholder во всех местах
Да, так и есть, спасибо за очередной пример.
вам не нужно будет городить никакого синтаксического сахара и вообще помнить о том, что объекты у вас — это не просто глобалы, а что они размещены через placement new
Зато имеет место дополнительный код при объявлении и создании объекта. Впрочем это все попросту неизбежно, если нужно создать объект в конкретном хранилище.
Это же placement new, вы сами размещаете обьпкты в выделенной заранее вами памяти. И в данном случае куча не используется, а выделять память вы можете точно мод размер обьекта.
static typename std::aligned_storage<sizeof(T),alignof(T)>::type placeholder; //выделили память статически (не на куче), заранее, размером с объект типа T с выравниванием. В принципе можно вообще массивом выделить, типа static std::array<t_unit8, sizeof(T)> placeholder, но это будет неправильно для 32 и 16 битных микро из-за выравнивания.
...
//а затем где-то в коде разместили там объект
new (&placeholder) T;
Естественно, обновился до последней версии этой самой Energia. И началось:
1. Контроллер стартует с задержкой 30 сек — оказывается они припилили автоинициализацию часового кварца (зочем!!!, если на плате с завода он не распаян).
2. Тактовая частота в 4 раза меньше — нашел ответ на форуме, что это поломка каким-то коммитом, пропатчил либу.
3. Не работает UART — опять косяк в либе.
4. Не работает I2C — за пару дней обсуждений таки подсказали патч.
Но, вы итоге, я все равно вернулся на старую версию, ибо после всех патчей, режимы сна начали криво работать и я забил.
Я конечно благодарен форумчанам выручили, но блин как это достает особенно если за 4 года архитектуру этих МК почти забыл и понятия не имеешь даже куда соваться.
Так много людей ратуют за опенсурс, но мало кто понимаем сколько труда надо вложить, что бы от релиза к релизу не только добавлять фишки, но и просто ничего не сломать.
Ну в статьях про мобильную разработку мелькали фотки, что у них там отдельно стенд с десятками смартов, для тестирования. Касательно же моего случая то поддерживаемых прогой плат не более 1.5 десятка. Плюс в случае МК можно написать не очень большую прогу которая бы тестировала хоть бы частично функционал (например не все пины АЦП а часть), и сделать соответствующий шилд с известными значениями напряжения, i2c датчиком и тд. Но это все время и деньги, а там похоже все на энтузиастах держится.
Поэтому по возможности я стараюсь не использовать ардуино.
Так чей рантайм оказался с кривым маллоком?
У Кейла, внезапно, есть две реализации malloc/free с разной асимптотикой (линейной и логарифмической, по-умолчанию линейная). Возможно, если это и правда баг, его можно полечить, сменив реализацию.
http://www.keil.com/support/man/docs/armlib/armlib_chr1358938928135.htm
А вообще такое поведение маллока может быть связано с тем, что программа где-то переполняет память, записывая данные в кучу или в служебную область рантайма, которой пользуется маллок. Но отсутствие проверки возвращаемого значения маллока в рантайме — это очень алохая ошибка
А по поводу выделения памяти, не надо использовать malloc(), вот просто не надо и всё, про это написано в MISRA C/C++, и не стоит этим пренебрегать, даже если вас не ставят в рамки этих правил, они не просто так писались.
Маскирующиеся баги в эмбедде