Pull to refresh

Comments 134

В первых двух листингах у вас UB, хороший компилятор должен сделать прогу 0 байт)
Клуючевое слово «хороший». Плохой — может что угодно сделать.
Как-раз UB даёт право делать всё, что угодно. А из всего множества «всего, что угодно» вариант «не делать ничего» являетя оптимальным и по затратам времени и по размеру бинарника :)

Он не UB, но при включенной оптимизации, размер действительно 0. Можно сделать ввиде отдельной функции.

Насчёт UB я погорячился (не в туда посмотрел), но lowest должен инициализироваться testArray[0] или INT_MAX, а не нулём.
Какая разница передавать указатель или обращаться к объекту? Имя все равно пишется, параметр все равно передается.
Вот если много вложенных вызовов методов, то в C++ исходник действительно покороче будет.

Кстати, странно, что первый пример на Си не оптимизировался, обычно компиляторы это умеют.

Разница в том что указатель изменяемый, его можно забыть про инициализировать, можно нечаянно поменять. Таких примеров предостаточно. Для этого в срр и придуманы были ссылки, и умные указатели, чтобы такие проблемы и закрывать.

Нужен пример из реальных и больших задач/проектов. Сколько помню, всегда приводят в пример миганием светодиодом или обращение к IO пинам — это не показатель.

Есть такой, на этой же плате с графическим индикатором и блутузом, но в статью не влезет.

Epiphan AV.io, все девайсы в линейке. Epiphan KVM2USB3.0. Правда там ресурсов побольше: Cypress FX3, на код 300 кБ и оперативки около 100кБ. При этом, например, для типобезопасного доступа к регистрам I2C реализована абстракция… которая полностью исчезает в бинарном коде. Т.е. она решает проблему: ударить по рукам пользователю на этапе компиляции, если он пытается запихнуть непихуемое, но обладает нулевой стоимостью в рантайме. Да, шаблоны :) К сожалению, код приводить не могу по понятным причинам. Кодовая база около 50+ тыс строк. Рантайм свой — только необходимое, исключений нет. Код в релизной версии около 230кБ. Компилятор GCC 4.8.4. C++11.

А почему C++, а не сразу Rust? Переход с C на C++ не сильно проще, а преимуществ дает меньше.
Потому что С и С++ — промышленный стандарт де-факто для «нативного» программирования?
А ещё Rust некрасивый и неудобный, но это уже моё личное субъективное мнение.

Однако же в Rust уже как минимум с 1.0.0 есть целый набор фич, которые аналогичны тем, что вошли в С++17.

А у него уже тулчейн для микроконтроллеров в удобоваримом состоянии? Просто когда в последний раз смотрел, они весь embedded официально оставляли "на когда-нибудь потом", и всё что было доступно — это сделанные энтузиастами хаки разной степени допиленности и удобства в использовании. Что-то с тех пор поменялось, интересно?

Про некрасивый и неудобный не согласен, но т.к. это всего лишь мнение, комментировать не буду :)

Насчет стандарта — да, не стандарт, но попробовать можно, по крайней мере на обучающей статье. Хотя в хардкорный проект со многими нулями совать пока рановато, с этим никто и не спорит.
Как можно увидеть сгенерированный компилятором код на С++ на 4 байт меньше, а скорость работы на 12 тактов быстрее. Все это достигается за счет новых возможностей С++14

Причём тут возможности С++14? Вы о чём?
В первом листинге проблема с оптимизацией (она вообще включена?) — два раза перечитывается значение из массива, к тому же не произведёна оптимизация strength reduction на индексе цикла.
В результате имеем 4 инструкции, 12 байт против 2 инструкций, 4 байта
Т.е. 8 байт потеряно на пустом месте.

Оптимизация включена, при включенной код не имеет смысла, все вычисляется на этапе компиляции. Тут показано, что в общем случае for с обходом через итератор преобразуется в более эффективный код.

при включенной код не имеет смысла, все вычисляется на этапе компиляции

Детский сад.
Ну так создайте зависимость чтобы код не превращался в return 0;
godbolt.org/g/NwK3Wp

Пока что получается что ваша статья не имеет смысла, т.к. исходит из неверных предпосылок.
И добавлю версию с увеличенным количеством С++14, а то чего вручную-то по массиву ходить.

godbolt.org/g/xHMEho
А что мешает использовать в С не индекс а указатель? Насколько эффективна компиляция такого кода?:
for (int *i = testArray + 4;  i >= testArray; --i) {
  lowest = (lowest < *i) ? lowest : *i;
}
Проверил по коду размер одинаковый стал. Но согласитесь, что код на С++ с новым for лаконичнее.
Этого делать не нужно. Компилятор сам всё делает. Смотрите мой коммент выше.
Во второй половине 2017-ого года в ВУЗе был курс по микроконтроллерам. Преподаватель давал задачи решать на С, да и все одногруппники. Но я, человек, привыкший к С++ и ООП поколупавшись в Keil, взял и начал писать на С++. Набросал небольшую библиотеку на классах под CMSIS и понеслась))) Работали мы с STM32F103C8. Писали на Keil 4, где был стандарт С++03, что было для меня дико неудобным. В итоге притащил в универ Keil 5 (в последней версии добавили компилятор ARM Compiler с поддержкой С++14), помудрил с либой CMSIS под наш МК, в итоге скомпилировал проект со своей либой, но на МК не завелось нормально (и не было времени разбираться с этим).
Так вот, к чему я веду, С++ стараются внедрить в область разработки под МК, да и это безусловно удобнее, чем писать на чистом С.
Но вот заинклюдив тот же программа на МК уже не влезала… (На STM32F103C8 64Kb под программу).
«Но вот заинклюдив vector тот же, программа на МК уже не влезала...»

) конечно, можно было использовать просто массивы или std::array. Vector слишком громоздкий, и юзает динамически выделяемую память. Сразу появляется куча, за которой следить нужно… Лучше динамически создаваемые объекты вообще не использовать.

я кучу в своей обёртке использовал (не более 1,5 кб, при доступных 20кб). И std::array мне не был доступен, ибо с компилятором, с поддержкой С++14 я не стал разбираться дальше и продолжил писать на С++03.
А ещё можно было застрять на старом WATCOM даже без C++98, и в его контексте жаловаться на неудобства. На том же Болте Богов можно увидеть, что между gcc 4.5 и gcc 7.2 огромная разница, тем более, что для лучшего результата нужно теребонькать ключи компилятора.

Мне и C++03 неудобный, ибо я начинал программировать сразу на С++11, с использованием большинства нововведений. И код на чистом С мне не приходилось писать вообще никогда.
А под МК я писал только по учебе в универе.

Почему переменная lowest инициализируется нулём, а не первым элементом массива?
Если инициализировать нулём, то среди чисел {2, 1, 3} алгоритм покажет, что наименьшее — это ноль.
Это ошибка.
Инициализируйте lowest первым элементом массива и обходите массив начиная со второго.
Я, согласен, что это топорный пример, но я там написал, что это очень синтетический тест, его конечно можно улучшить, но я хотел показать, что если писать в лоб, примерно в одном стиле, то можно получить такой вот результат.
Если инициализировать первым элементом в массиве и проверять со второго, то количество итераций можно уменьшить на единицу.
Правильно использовать numeric_limits.
Просветите, пожалуйста, чем именно правильно?
Это идеологически более правильно. Более плюсово.
Простите, я что-то пропустил. В плюсах тоже религия завелась? И её не выжгли?
Плюсы с самого начала на ней основаны, извините. C++ — это «почти как C, но с блекджеком и шлюхами». Что-то заменили изначально (как те же numeric_limits), что-то — только в C++11, что-то — заменят ещё позже…

На да — процесс идёт потихоньку…
Попробую ответить за товарища, так как сам так считаю. INT_MAX — это макрос, и непонятно что там за число, и вообще компилятор и библиотека за него не отвечает, сегодня он один, завтра другой, перенесли с машины на машину, а там уже все по другому. А numer_limits — это библиотечный класс, и его правильность гарантирована стандартом и компилятором и не зависимо от того, на какой платформе вы компилируете код, вам будут возвращены правильные лимиты.
INT_MAX — это макрос, и непонятно что там за число, и вообще компилятор и библиотека за него не отвечает
Это как это? Это почему это? Страдарный заголовочный файл <climits>, INT_MAX должен быть описан, если компилятор претендует на совместимость с C++11, а если это C — то тогда это в <limits.h> если ваш компилятор претендует на совместимость с C99.

И то и другое может быть неправдой, конечно — ну так и numeric_limits тоже может чушь содержать если у вас компилятор, не соблюдающий стандарты…
Например можн использовать тип из шаблона. Можно использовать decltype для получения типа. И если в этих случаех мы используем numeric_limits, то логично использовать его и в других, для консистенции кода.

Зачем вы сравнили два разных кода с отключенной оптимизацией? В Си версии эквивалентом (без оптимизации) будет что-то типа:


  for (int i = 0;  i < 5; i++) {
    int val = testArray[i];
    lowest = ((lowest < val) ? lowest : val);
  };
С включенной оптимизацией, все вычислится на этапе компиляции и кода не будет.

Да, я это понимаю. И говорю о том, что в таком случае нужно сравнивать хотя бы эквивалентный код. Иначе в чем смысл? Данное сравнение показывает, что код с двумя чтениями из памяти менее оптимальный чем код с одним чтением из памяти.


Ну а в общем не совсем понятно, почему С++ код в Си стиле, должен давать менее оптимальный выхлоп? Ну и примеры совсем уж притянуты.


ЗЫ Ну и исправьте ошибку с инициализацией, понятно что код демонстрационный, но глаз режет.

Да, я раньше тоже считал, что С++ избыточен для МК.
Потом понял, что просто не знаю языка и вообще боюсь начинать с ООП. Прошло довольно много времени, и я успешно применяю С++ в проектах на микроконтроллерах.
Можете привести пример удачного использования C++ в крупном проекте? Где реализация на Си была бы слишком громоздкой
github.com/PX4/Firmware вот пример крупного проекта для микроконтроллеров написанного на c++.
*
По секрету у нас тоже свой автопилот и тоже на с++ под микроконтроллеры.
Насколько знаю старлайновцы на ++ пишут

Странно, что никто не вспомнил Arduino — вот вам и МК и С++ и исключительно успешный проект.

Я бы не относил это к удачным примерам. То, что я видел — достаточно топорно написано. Но для варианта — быстро что-то набросать, вполне.

За всё время наверное миллиарды плат Ардуино и клонов произведены и проданы, и это неудачный пример? Фреймворк и многочисленные библиотеки прекрасно справляются с задачами предоставления удобного API к контроллеру на С++ для любителей DIY.


Я предполагаю аргумент, что для промышленного применения это всё не годится. Согласен. Конечно, не годится, это проект не для промышленного применения. Но тем не менее это пример исключительно успешного использования С++ на МК.

UFO just landed and posted this here
готовая среда разработки (с каким-то своим Си-подобным диалектом)

Там самый настоящий С++, компилируется при помощи avr-gcc.


Если бы пользователю пришлось писать на чистом си или ассемблере — возможно и не было бы такого успеха.

UFO just landed and posted this here
У них поддержка синтаксиса в самой IDE значительно урезана. Позор-позор, если честно. Но если, например, подключить любой готовый проект с Atmel Studio, всё будет работать как надо.

Не совсем понятно что значит "поддержка синтаксиса в IDE"? Автокомплит? Лично писал код с классами, темплейтами и макросами.


PS: Хотя конечно IDE убогая

Arduino IDE хотя-бы бесплатная, хоть и похожа больше на примитивнейший текстовый редактор. А Keil uVision IDE продаётся за бешенные деньги (не считая Lite-версии), но при этом дико баганутая и не доработанная.

Atmel Studio бесплатная, вполне годная IDE.


но под Ардуино по мне так лучше смириться с убогим редактором, зато получить бесконечно богатый менеджер готовых библиотек. Все равно проекты там физически не могут быть большими. Пара десятков файлов это край, обычно не более 5. Многие вообще в одному модуле всё пишут даже без функций, сплошным текстом (жесть конечно).

Atmel — по сути тупо вижак с помидором. Бесплатно! А, значит,
ДАРОМ!!!


А Arduino IDE, по сути, не поддерживает проекты из нескольких файлов. Подсветка тех же шаблонов не работает. Автокомплит только после добавления собственных символов в keywords.txt
Оно не критично для atmega 168, но уже на 2560 размеры выходят из-под контроля. Да и attiny16 в такой IDE не запрогаешь без боли, но по обратной причине.
Да, менеджер библиотек — это круто, но он обновляется из рук вон плохо, так что некоторые поделки застряли на той же стадии, что и IDE — лагающее, извините мой французский, дерьмо.
А Arduino IDE, по сути, не поддерживает проекты из нескольких файлов.

Поддерживает. Просто закиньте несколько файлов в одну папку.

Незачем мириться с убогим редактором. Можно поставить дополнение VisualMicro (работает с Visual Studio или Atmel Studio) и получить мощь нормальной IDE вместе с доступом ко всем библиотекам Arduino. Даже отладка в симуляторе кое-как работает (правда, у меня не завелись точки останова — то ли нужна платная версия, то ли просто что-то недонастроил).

Хм. У меня лежат либы под AVRки написанные в 2007 году и на С++. Компилятор IAR генерит отличный компактный код, ничем не уступающий по компактности написанному на чистом C. При написании на плюсах есть хитрости в виде статического экземпляра класса или статические функции например.
А что касается ошибок вида «установка десятого бита в восьмибитной переменной», то у вас неправильно построен процесс разработки. Для любой периферии драйверы пишутся человеком, который очень хорошо знает саму железяку, и этот драйвер имеет функции не «SETBIT(port, pin), а функции вида „errStatus InitDisplay()“. И вот этот набор драйверов передаётся программисту, который пишет алгоритмы, протоколы обмена и прочий матан. Вот тогда всё работает отлично и все довольны.
Все описанное — это не C++. Это «Си с использованием элементов современного синтаксиса». Я и сам так пишу.

C++ — это когда с собой тащится stl или boost, море классов с наследованием, вcякие shared_ptr на каждой строчке и ключевое слово virtual повсеместно.

Но тогда, боюсь, все будет не так компактно.
Наследование, не показал согласен, но оно вообще когда не добавит, если вирутальные функции не использовать. Можно обойтись же без shared_ptr. Я тут старался показать, что код на намного С++ лаконичнее, и размер такой же. и это не просто Си с использование соврменного синтаксиса, так как на Си такое будет занимать больше места, я там написал, что можно структры использовать и указатели на функции, но это сразу + к размеру.
Про std, см комментарий от NightShad0w, при ключенной оптимизации все работает ОК.
habrahabr.ru/post/347980/#comment_10646698. Проверил на IAR, тоже код такой же. Поэтому её использовать можно, но не безздумно. unique_ptr например запросто, он ничего не добавит, а вот shared_ptr нет, он сразу добавит много накладных, хотя все зависит от задачи, если вы на Си будете решать, ту же задачу, что решает во многих случая shared_ptr, то возможно и кода будет больше.
Я уже много лет периодически развлекаюсь сравнением кода, генерируемого разными компиляторами из функционально эквивалентных программ на C и C++. Как-то переписал таким образом небольшую программу для ATTiny13, заменив прямое управление конкретными битами в конкретных регистрах на абстрактное, с наследованием и шаблонами — в WinAVR оптимизированный код получился практически байт-в-байт таким же, за исключением стандартного стартового модуля. :)

Неожиданно. С++ это код соответствующий одному из стандартов С++ХХ, вот и всё. Если у кого-то не получается писать на С++ без boost и stl — язык в общем-то тут ни при чём.

Ну, между прочим, shared_ptr-подобный интерфейс вполне можно накрутить поверх пулов памяти какой-нибудь RTOS.


Буст и прочие, конечно, хороши, но часто слишком избыточны для задачи.

UFO just landed and posted this here
Код на Си:

int lowest = INT_MAX;

И его ассемблерное представление:

int lowest = 0;

Что-то не так в датском королевстве. Хотя это и не влияет на сравнение, но все же код неверный.
Там ошибка изначально была, я её поправил, ассемблер еще нет — картинку надо делать новую.
Т.е. такую важную вещь ООП как исключения нежелательно использовать в проектах для МК?
Исключения сразу значительно увеличит размер кода, так как добавится таблица с информацией о том где исключение было поднято и где искать его обработчик и сам механизм поиска обработчика исключения тоже будет в коде. Ну и медленно это все будет… на 1 Мгц то.

Исключения не являются частью парадигмы ООП никаким боком. Идея исключений появилась примерно в тоже время, что языки с классами, но она решает ортогональную проблему. Вообще, в последние годы человечество пришло, как кажется, к консенсусу, что идея исключений была не очень удачной. Теперь все любят result типы.

for (auto led : leds) {

здесь led копируется по значению. Меняя состояние led вы не поменяете состояние *it. Если нужно просто обойти массив, более корректным был бы проход по ссылке:
for (auto &led : leds) {

Вероятно, в этих разыменованиях и есть разница в пару инструкций
Не просто можно но и нужно и не только для маленьких, а для больших тоже ибо из-за отсутствия пространств имён в C и шаблонов требуется огромное количество копипасты или макросгенерации, которая не отлаживаема вообще, а также дико длинные префиксы и постоянный геморой со сборкой и линковкой. В C++ этого ужаса и бардака нет. Я уже молчу про STL и нормальные интерфейсы к повседневной алгоритмике.

Что думают в царстве микроконтроллеров о том, что к вам придёт JavaScript и рынок труда сильно поменяется?

На 8, — 256 кБайта памяти программ и 4 — 20 кБ ОЗУ, он никогда не придет. Миниатюрным датчикам и низкопотребляющим устройствам это не грозит тоже. А вся промышленность сидит именно на этом. Все упирается в потребление и цену, зачем мне быстрый навороченный микро, который стоит 7 баксов, но с возможностью Джава, если то же само можно сделать на 1 баксовом? При объемах в 30 000 скажем датчиках в год (а к слову, средний объем датчиков температуры у какого-нибудь Элемера) получим 180 000 долларов экономии! А если взять конторы покрупнее, то выгода может быть и под миллион долларов.
Это сейчас так.

А через 5 лет микроконтроллеры будут иметь память 1 Гбайт спокойно и её просто некуда будет деть, поэтому всё будет написано на js, компилируемым в assembler =)
А через 5 лет микроконтроллеры будут иметь память 1 Гбайт спокойно и её просто некуда будет деть
Тогда они просто станут еще дешевле. Микроконтроллеры — это не готовый продукт для потребителя, поэтому маркетинг а-ля «два ядра, три гига» там просто смысла не имеет.

А вот всякого-рода *duino за много денег с кортексом на борту лишь для того, чтобы вращать педали JS-интерпретатора, уже есть. Но индустрия этого не замечает. Серьезные конторы в сфере IoT концентрируются лишь на облачных решениях с JS-интерфейсом, а исполнительные устройства/датчики все так же на C.
Как же вы задолбали все со своим js и вебом не к месту.
Что думают в царстве микроконтроллеров о том, что к вам придёт JavaScript

ачем мне быстрый навороченный микро, который стоит 7 баксов, но с возможностью Джава

ой
Java уже приходил, лет шесть назад ещё. Но оказался не шибко востребованным в отрасли…
Извините, а разве в С не принято делать через виртуальный порт, и по нему елозить масками?
не понял вопроса, вы имеете ввиду, что можно, например, сделать включение и выключение всех светодидов маской?
GPIOC->ODR |= ALLLEDS_MASK;
GPIOC->ODR &=~ ALLLEDS_MASK;
Но порты же разные вообще А и С, к ним впринципе одномоментно нельзя маски применить.
Или сделать массив из битов портов, используя битбендинг и по нему лазить, но в чем тогда разница, между обыччным массивом?
У ARM часто (всегда?) есть возможность указать адрес таким образом, чтобы запись была возможна по маске. То есть берем указатель на байт, в него пишем, скажем, 0xff, а единицы запишутся только в те биты, которые есть в маске. При этом маска указывается неявно, через адрес. Так можно обойтись вообще без масок в программе, заменив их на адреса.
То есть вместо PORTA |= 1<<4 пишем что-то типа *porta_mask_bit4 = 0xff.

Или об этом и было написано, но я не понял?
Я с машинным кодом ARM вожусь по работе последние лет 5 — и такой роскоши не замечал. Подобные CPU в природе бывают, но из-за использования большинством процессоров шины данных шириной более, чем в один быть, они мало распространены.

Не поясните о каких командах идёт речь?
Речь идет не о командах, а о том, что для некоторых областей памяти сделана возможность доступа к ним побитово через другие области памяти (несомненно, гораздо большего размера).
Вместо того, чтобы писать команды установки некоторых битов и/или сброса других в одном из портов можно написать одну команду записи по определенному адресу, при этом будет выполнена операция, эквивалентная {PORT1&=~maskand;PORT1|=maskor;}. Аналогичная радость есть и при работе с некоторыми участками памяти.
Такая возможность точно есть у процессора LX4F120, который когда-то стоял на первом ланчпаде от TI, и что-то мне подсказывает, что это не TI придумала такую замечательную возможность изменять биты группами, обращаясь к специальной области в адресном пространстве. Впрочем, может, кто-то и разобьет мою уверенность в том, что это ARM-фича.
Вы про BSRR и BRR? Про них уже тут писали…

Ну так в них не будет вместо PORTA |= 1<<4 пишем что-то типа *porta_mask_bit4 = 0xff.

Будет всего лишь PORTA = 1<<4 для установки или PORTA = 1<<(4+16) для сброса.

В адресе никаких масок там нет, не пугайте.
А пишем мы в итоге слово целиком или только бит?
BSRR (32 бита) имеет вид 0bLLLLLLLLLLLLLLLLHHHHHHHHHHHHHHHH
L — сброс пина
H — установить единицу на пине

BRR также 32 бита, но имеет вид 0b0000000000000000LLLLLLLLLLLLLLLL
0 — значит «не используется»
10.2.1.2 Data Register Operation
To aid in the efficiency of software, the GPIO ports allow for the modification of individual bits in the
GPIO Data (GPIODATA) register (see page 623) by using bits [9:2] of the address bus as a mask.
In this manner, software drivers can modify individual GPIO pins in a single instruction without
affecting the state of the other pins. This method is more efficient than the conventional method of
performing a read-modify-write operation to set or clear an individual GPIO pin. To implement this
feature, the GPIODATA register covers 256 locations in the memory map.
During a write, if the address bit associated with that data bit is set, the value of the GPIODATA
register is altered. If the address bit is cleared, the data bit is left unchanged.

Из даташита на lm4f120h5qr.

Короче говоря, есть базовый адрес регистра, к нему мы можем прибавить маску, правда, умноженную на 4, чтобы получить возможность записывать только в те биты, которые в маске установлены. При этом мы одной операцией записи изменяем только те биты, которые хотим. Кстати, и при чтении такая же фишка есть: биты, которые нам не нужны, при чтении будут сброшены в ноль.
я себе BSRR и BRR заворачивал в вот такое:
	MicroController& controller = MicroController::GetRef();
	PortInterface&	 portA 	    = controller.PortA();
	
	while (true)
	{
		portA.Pin(1).High();
	 	Delay(2000);
		portA.Pin(1).Low();
		Delay(2000);
	}


и это давало возможность писать алгоритмы, принимающие в качестве входных данных структуры, содержащие настройки пинов. Вообщем, удобно и гибко.

Главный ключ успешной оптимизации кода — узнать максимальное число ограничений на этапе компиляции. Если из константного указателя мы можем вывести значение, мы можем сэкономить N-тактов процессора, сразу подставив что и куда надо. Если есть возможность развернуть цикл, зная количество итераций, можно ускорить выполнение программы или вообще освободить несколько драгоценных регистров


Суть плюшек современного С++ в том, что compile-time вычисления делать куда проще, нежели делать это в Си (но там это тоже возможно).


UPD: в ARM есть регистры установки, сброса и записи значений GPIO, там операции |= и &= избыточны

UPD: в ARM есть регистры установки, сброса и записи значений GPIO, там операции |= и &= избыточны

BSRR и BRR. Причём через BSRR также можно сбрасывать состояние (через старшие разряды). И в мануале советуют работать через эти два регистра (производительнее, ибо ODR там как-то связан с прерыванием, в отличии от этих двух).

Или вы не о том?)

Да, о них, просто предпочитаю работать через тонкий HAL, и GPIO не из того рода периферии, в которой по ходу службы запоминаешь названия регистров (другое дело eHRPWM от TI, 86 регистров на модуль)

Я не стал делать через BRR, и BSRR чтобы показать замену макроса SET_BIT, а так полностью согласен, если использовать эти биты, то кода выйдет меньше
Термин bitBending я нашёл только применительно к arm, так что видимо мы разные вещи имеем в виду.
Можно сделать через указатели, неэффективно, но наглядно, как-то так:
typedef struct 
{
	volatile uint8_t *port;
	volatile uint8_t *pin;
	volatile uint8_t *dir;
	uint8_t mask;
} virt_port;
virt_port vport[7] = {{&PORTB,&PINB,&DDRB,(1<<0)},
		      {&PORTB,&PINB,&DDRB,(1<<1)},
		      {&PORTB,&PINB,&DDRB,(1<<2)},
		      {&PORTB,&PINB,&DDRB,(1<<3)},
		      {&PORTB,&PINB,&DDRB,(1<<4)},
		      {&PORTB,&PINB,&DDRB,(1<<5)},
		      {&PORTD,&PIND,&DDRD,(1<<7)}};

Пример взят с avrfreaks
Можно задать «виртуальные пины» таблицей в виде A, 5 (имя реального порта и его номер), а потом размотать через x-macro, тогда можно будет редактировать в одном месте без большой опаски накосорезить.

Другой способ сделать всё на макросах, можно посмотреть пример на chipenable. Там большая портянка, вроде бы для совместимости с С89, поскольку не используется _VA_ARGS_, с которым было бы чуть короче. Аналогичный православный способ — на асме на easyelectronics
Исключения на контроллерах практически не работоспособны. Объём кода сразы прыгает за 2 мегабайта для простенькой программы для Cortex.
Кроме того не работоспособна развёртка стека. Это адово долго по меркам контроллера, требует десятки килобайт стека, делает программу непредсказуемой в плане быстродействия.
Но каким обазом C++ связан с исключениями? --no-exption и --no-rtti делают всю магию.
Писать на C++ хорошо, благостно и правильно. Сейчас средненькая программа, это под сотню файлов и мегабайты текста когда.
Неймспейсы, шаблоны, constexpr-выражения (вместо ада из макросов) и даже наследование классов (например, для конечных автоматов) это делает программу проще, понятнее и уменьшает количество ошибок.
Зачем каждый раз клепать циклические буфер, если можно один раз отладить его на шаблонах?
Зачем городить туеву хучу кода в конечных автоматах и потом рыдать над отладкой, если это легко решается наследованием и виртуальными функциями? И никакого оверхеда. Ни байта. Вы бы всё равно писали равно тот же код и использовали те же переменные на чистом C. Местами даже лучше получается, т.к. компилятор может лучше оптимизировать работу с данными.
А уже заменить болто из макросов, которое есть в любом встраиваемом проекте constexpr выражениями, это сам Бог велел! Количество ошибок и время отладки сразу уменьшаются в разы.
Короче, чтобы писать на C++ для контроллеров, нужно очень хорошо понимать, как именно и во что это компилируется. Тогда код увеличивается только на 200 байт плюсового стартапа, всё остальное эквивалентно.
Но каким обазом C++ связан с исключениями? --no-exption и --no-rtti делают всю магию.

увы, но в stl есть несколько узких мест, где нет версии api без исключений.

Другой вопрос — а что будет, если повсеместно использовать noexcept, кроме буквально пары мест, где без них никак? Печатать да, много, но всё равно быстрее чем на чистом си.
Тут уже говорилось: любая попытка собрать программу под мироконтроллер с поддержкой исключений немедленно порождает монстра, который никуда не влазит.
stl (на микроконтроллерах) в большинстве случаев — в топку.
Вы очень категоричны. Есть куча готовых вещей из stl с нулевым оверхедом.
К сожалению узнать об нельзя практически никак. Завтра меняете компилятор или его версию, или версию библиотек и с ужасом понимаете, что у программа выросла в пять раз и требует десятки мегабайт ОЗУ. Т.е. теряется совместимость по компиляторам и их версиях.
Да и вообще там поле граблей. Шаг влево, шаг вправо и какая-нибудь сортировка сжирает всю память. И хорошо, если вы это заметите.
Такого быть неможет в принципе. Почти на все алгоритмы есть гарантии. Если вы такое пишете это показывает, что вы незнаете как устроенны библиотека.
Я могу вам перечислить куча вещей из stl которые гарантированно имею 0 оверхед и никогда не изменятся.
Начинаем
Почти весь атомик. Можно в большинстве своем применять, а не использовать ассемблер или Builtins
Все трейтисы, когда вы пишите куча шаблонного кода нету смысла плодить свой велосипед.
std::chrono
Многие вещи из utils.
Некоторые контейнеры, тут с оговорками.
Алгоритмы. Многие вы лучше всеравно не напишите.
atomic в общем случае для ПО под микрокнтроллеры не применим вообще. Тут пример плохой. Обычно он может применятся только там, где микро поддерживает набор инструкций для атомарного доступа. А если такого набора нет (а это практически все небольшие микро), то все, считай атомика нет вообще, так как компилятторы для микро на уровне библиотек это не поддерживают.
Про алгоритмы согласен.
Ну все ARMv7 поддерживают атомарные инструкции. То есть довольно большое количество МК. Понятно, что если говорить о совсем примитивных МК, то там нет поддержки.
Стандарт подразумевает, что atomic может не быть lock free. Ну а накрутить костылей с запретом прерываний можно в любом МК.
Добавлю, скажем если для Cortex M4 такое возможно сделать:
std::atomic<float> aF(12.0f);

так как флоат 32 бита и сохранение этого значения это атомарная операция, то вот такая запись уже
std::atomic<double> aD(12.0);

уже не отлинкуется, по причине отсуствия реализации функций load и store для этого типа, так как он уже занимает 8 байт и эти операции на 32 битном микро аппаратно не атомарны. Поэтому надо внимательно читать документацию на конкретный компилятор для микроконтроллера
Мне кажется, что это как раз хороший пример полезности атомиков.

Узнать что что-то не компилируется всяко полезнее, чем узнать о том, что что-то иногда (причём очень редко!) падает ввиду отсутствия атомарных load и store…
Возможно, единственное — оно компилируется без ошибки, линковщик собрать не может из-за отсутвия реализации методов.
Забыл, да. Это чуть хуже, но в любом случае гораздо лучше случая, когда оно компилируется и линкуется без ошибок, но не работает.
STL не применим для embedded от слова никак. Банально потому, что там активно используется динамическая память и алгоритмы не оптимизированные по расходу памяти.
noexept, как правило, мало. Нужно ещё компилятору ключ передавать, что использует C++ без исключений. Иначе он подключает не только библиотеку развёртки стека, но и, что хуже, невероятное число данных о стеках всех фукций в программе.

Как-то вы уж очень безапелляционно. Далеко не весь STL использует динамическое выделение памяти. Что-то просто не стоит использовать, памятуя что оно использует кучу. Что-то можно перевести на собственные аллокаторы с пулами. Для чего-то можно выделить максимальный размер памяти используя .reserve(), тем более что в embedded многое создаётся единожды и живёт пока прошивка работает — выделить память из кучи не страшно, если это делается один раз во время инициализации. Что-то можно разместить на флэше (неизменяемые ассоциативные массивы, например, хорошо ложатся на размещённый во флэше std::map). Без исключений тоже вполне можно жить.


Да, признаю, надо много знать об STL и держать в уме нюансы, но оно везде так, нюансов в любой области полно. Для кого-то овчинка не стоит выделки, для кого-то стоит.

большая часть алгоритмов из algorithms library не требует исключений и динамического выделения памяти, не добавляет никакого оверхеда, а объем кода сокращает в разы.

Идея о том, что на микроконтроллерах надо беречь память устарела лет на… Пятнадцать. И, кстати, использование динамических коллекций скорее снижает затраты на суммарное потребление ОЗУ, чем наоборот, за счёт возможно создавать объекты только тогда, когда это действительно нужно.

Конечно глубина стека при этом вырастет значительно, так как массив светодиодов создается на стеке, а он уже равен 56 байтам.

Использовать static или создавать массив вне функции религия не позволяет? :-)
Ну впринципе вообще глобальная переменная это вред. Static можно, но какая рзаница? ОЗУ все равно отъест, а где не имеет значения в стеке или сегменте данных (адрессное пространстров одно, это же не микрочип 16 где стек аппаратный). Единственное, контроллировать стек надо будет, но линкер выдает его максимальный размер, поэтому с моей точки зрения, вообще без разницы.
Линкер не может знать максимальный размер стека, он же не знает полное дерево вызовов, так как оно зависит от входных данных.
Почему в ОЗУ? У нас обычно фон-неймановская архитектура. Соответственно static const улетает в ПЗУ (при настроенном линкер-скрипте). И даже если архитектура строго гарвардская, ОЗУ зачастую состоит из нескольких сегментов. Соответственно можно при помощи прагмы запихать static в другой, более свободный, сегмент.
Да согласен, придется только поменять все методы класса Led на const, что собетвенно и правильно, так как поля класса не меняются и эти 56 байт улетят в ПЗУ, освободив стек. Хорошее замечание.
Ждём статьи со сравнением C++ и Javascript
Влажные мечты о Javascript можете отставить. Истина между вами лежит посередине. Ни о каких гигабайтах памяти в микроконтроллерах в ближайшее время и говорить не приходится, конечно, но и четырёхбитные микроконтроллеры с 256 байтами программной памяти и частотой в десятки килогерц индустрия использовать перестала. Перешли на более современные архитектуры с памятью в 8-16K и частотой в мегагерц, что уже позволяет использовать C/C++.

Думаю лет через 20 типичный объём памяти дорастёт уже до мегабайта — там уже и байткод какой-нибудь можно будет замутить. А JavaScript… ну если он до конца столетия не вымрет, то всё может быть, конечно.
UFO just landed and posted this here
Байткод — это средство сжатия программы до невероятно малых размеров. Только, разумеется речь не о javascript, а о форте. Полный размер системы с редактором и драйвером диска — 12килобайт, из них чуть больше половины — исполнительная система.

Типичный пример того, что писали на форте — это карманная игра «Ну погоди». Микропроцессор КБ1013ВК1-2, ОЗУ объёмом 65 4-битных ячеек со страничной организацией 13x5, объём программы — 1830 команд.
не путайте байткод и машинный код. Первое еще требует компиляцию или (в случае жс) интерпретацию. Сама-то программа конечно в мк влезет, а вот виртуальная машина — нет.
Не путаю. На форте использовался шитый код. Один из его вариантов (индексный шитый код) как раз и является байткодом. То есть в прямом шитом коде пишутся адреса подпрограмм а в индексном и в байт-коде — индексы в массиве, где сидят адреса подпрограмм. Аргументы (если они не в стеке) идут после адреса (или индекса) подппрограммы.

Размер машинозависимой части виртуальной машины форта — примерно 3 килобайта, размер машинонезависимой части — ещё 5 килобайт. В сумме получается интерпретатор вполне полного форта. Ещё 4 килобайта — на драйвер диска и полноэкранный редактор.

Скорость программ на форте — примерно в 10 раз меньше неоптимизированного С++ (20-30 раз к оптимизированному). Компактность — раза в полтора компактней ассемблера.

Да, компилятор на форте — это одна строка. Точнее то, что в форте называется компилятором: взять текстовую строку и превратить её в шитый код.

P.S. Ну что вы хотите, если язык был создан почти 50 лет назад для контроллеров, управляющих телескопами? :-) Форт и 50 лет назад на мелкие машинки легко ложился.

Я статью не читал и даже не собираюсь. :) Но ответ на вопрос из заголовка у меня есть. Можно и нужно. И более того, нужна причина, чтобы выбрать си. Код на плюсах почти всегда лучше, чем код на си.


Вообще, спор о том, на чём писать под микроконтроллеры следовало бы закрыть ещё лет пятнадцать назад… Но воз и ныне там


И да, выше в комментариях правильно пишут, что вообще-то хорошо бы внедрять rust. Все там будем

Часть народа выбирала си за отсутствием плюсового компилятора под целевую платформу. Или из-за использование перекорёженного gcc третьей ветки в конце 10х..

Sign up to leave a comment.

Articles