All streams
Search
Write a publication
Pull to refresh
77
0
Send message

Если вы контролируете размещение стека и кучи, то можно воспользоваться таким трюком — разместить в самом начале ОЗУ стек, а кучу убрать. Тогда при переполнении стека будет HardFault из-за доступа к несуществующей памяти (при желании, там же можно и запас разместить).


Всегда хорошо сделать сразу после векторов секцию с писанием что это, когда собрано, а в идеале еще и коммит. Крайне удобно «в полях» — когда не знаешь что именно и когда именно залито было.

Согласен, тоже так делаю. А не подскажете, как вы в коде узнаете размер таблицы векторов?


Нецелевое использование… Ну да, не здорово. С другой стороны — а чем куча будет отличаться от массива? Я ее принципиально в своем коде не использую. Вся память у меня распределяется на этапе компиляции. Пока, во всяком случае, удавалось справиться так.

В С++ с этим несколько сложнее :) Стандартная библиотека очень любит аллокации, приходится ее сдерживать.
Вообще, если мне память не изменяет, в С тоже есть несколько стандартных функций, которые выделяют память по-тихому, что-то из <string.h> этим грешило.


Соответственно, я предпочитаю перекладывать проверку отсутствия аллокаций на компилятор, а не помнить самому, что там может проаллоцировать, а что нет.


Плюс иногда стандарт не оговаривает, нужно кучу использовать или нет.
Например, у Keil'a вспоминается два прикола такого рода — alloca, которая должна выделять массив на стеке, использует кучу для каких-то внутренних нужд. Или если создать статический объект класса, у которого есть виртуальный деструктор (а виртуальный он просто чтобы warning убрать), то Keil тоже резко захочет кучу.


Короче, только на себя полагаться мне уже страшно.

Там будет используется reinterpret_cast<>, что сразу же ограничивает использование как в шаблонах, так и в constexpr конструкторах

Тооочно, совсем забыл про это. Ужасно раздражает.


Так я и сделал: регистры генерятся из svd файла

Оу. Тогда это круто! К генеренному коду у меня заниженные требования :)
В таком случае могу только поапплодировать вашему упорству, меня на такое не хватило бы.


Думаю это дело вкуса, на самом деле, это просто установка битов в регистре CR1, можно было бы написать и последовательно

Хм. А я уж было подумал, что тот шаблон проверяет все маски на уникальность и великий смысл передачи всего в параметрах шаблона именно в этом.

По поводу переполнения стека могу бессовестно посоветовать мою статью на эту тему :) Хотя IAR я в ней не рассматриваю, поскольку не использовал, но, полагаю, что подход можно и на него натянуть.


Но "нецелевое" использование кучи — имхо ну такое себе решение. Я бы рекомендовал просто маркерный массив выделять по фиксированному адресу, если такой способ вам больше нравится, а кучу все-таки занулить. Иначе получается, что вы рассчитываете на конкретное расположение кучи и стека, которое стандартом языка не гарантировано; а если вы его сами контролируете через линкер, то можно и обычный массив разместить после стека или что-то более веселое проделать.


Насчет компиляторо-специфичных кусок я в целом с вами согласен, прагму привел просто как пример. Зануление кучи, пожалуй, можно назвать переносимым "подходом" — потому что в коде на С размер кучи обычно не задается (ну, или это просто массив фиксированной длины).
Скорее всего, это самый простой способ — либо в каком-то компиляторозависимом файле выбирать размер кучи нулевой, либо линкеру говорить, чтобы он убирал регион с кучей, либо просто дефайн выбрать.


Для gcc можно еще вот так сделать.

Спасибо за статью! Я, в целом, за любой движ в embed, который позволит уйти от чистого С, поэтому всегда рад подобным статьям :)


С другой стороны, в своей практике я ни разу не встречал проблему, которую (вроде бы) решает предложенный подход — "чтобы у Pinа, настроенного на вход не было возможности вызвать метод Set(), и наоборот для Pinа, настроенного на выход, не было даже намека на метод Get()."
Или же встречал, но она очень быстро решалась, потому что уровень на пине не изменялся, хотя должен был.


Ко второй части утверждения хочется добавить уточнение, что есть режим выхода Open-Drain, в котором чтение вполне имеет смысл. При этом в коде (при не слишком тщательном чтении) я отсылок к режимам входов и выходов не заметил.


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


Но, на мой вкус, ваше решение просто слишком "сложное" — очень много шаблонного кода ради относительно простой вещи. И кода станет больше, если попытаться сделать его переносимым хотя бы в рамках STM32, поскольку регистры портов на разных сериях немножко разные.


Чем меня пугает шаблонный код? В основном тем, что его отлаживать тяжело. И читать, на самом деле, тоже, особенно, если он обильно перемазан enable_if'ами.
Ну и можно спровоцировать нехилый code-bloat на ровном месте, но это, в целом, решаемо.


Я лично сию проблему решил примерно таким классом:


Кусок кода
    struct GpioPin
    {
        GPIO_TypeDef * raw_port;
        uint16_t raw_pin;

        void initOutPP() const;
        void initOutOD() const;
        void initInFloating() const;
        void initInPullUp() const;
        void initInPullDown() const;
        void initClock() const;

        void set() const;
        void reset() const;
        void toggle() const;

        bool read() const;
    };

Отмазки

Да, тут просто структура и у нее просто методы. Да, оверхед на вызов, на хранение указателей в рантайме и т.д. В подавляющем числе случаев — пофиг: если надо дергать пином сверхбыстро, то можно и по-старинке через CMSIS'ный указатель. Но зачем такое может быть надо — ума не приложу, скорее всего, что-то спроектировано неправильно. Типа, надо было ШИМ генерировать, а взяли ногу, не подключенную к таймеру.


Если хочется сделать порт из пинов, можно просто сложить их в массив.


Оверхед по памяти относительно невелик, если начинаем каждый байт считать, то под нож первым пойдет что-то потолще.


Зато код короткий, простой и понятный; любой мой коллега или приблудный студент в нем разберется. Плюс я на разработку потратил совсем немного времени.


Я все еще считаю, что на настройку и использование периферии в реальных программах уходит ну процентов 10 кода и может быть процентов 15 рабочего времени. Все остальное — логика, на которую уходит гораздо больше сил. Соответственно, на нее эти силы и следует тратить в первую очередь.


Чисто формально глаз цепляется еще вот за это (прошу прощения за непрошенное code-review)
using Led1Pin = Pin<Port<GPIOA>, 5U, PinWriteableConfigurable> ;
using Led2Pin = Pin<Port<GPIOC>, 5U, PinWriteableConfigurable> ;
using Led3Pin = Pin<Port<GPIOC>, 8U, PinWriteable> ;
using Led4Pin = Pin<Port<GPIOC>, 9U, PinWriteable> ;
using ButtonPin = Pin<Port<GPIOC>, 10U, PinReadable> ;

Зачем нужен шаблон Port, который тут все время повторяется? Не проще ли сразу принимать указатель на GPIO_TypeDef?


И вот в таких фрагментах от количества шаблонных параметров становится не по себе


  SPI2::CR1Pack<
    SPI2::CR1::MSTR::Master,   //SPI2 master
    SPI2::CR1::BIDIMODE::Unidirectional2Line,
    SPI2::CR1::DFF::Data8bit,
    SPI2::CR1::CPOL::Low,
    SPI2::CR1::CPHA::Phase1edge,
    SPI2::CR1::SSM::NssSoftwareEnable,
    SPI2::CR1::BR::PclockDiv64,
    SPI2::CR1::LSBFIRST::MsbFisrt,
    SPI2::CR1::CRCEN::CrcCalcDisable
    >::Set() ;

И повторы SPI2::CR1 опять-таки царапают глаз; наверное, от этого можно избавиться, если сложить битовые маски в namespace, а не в класс, и написав using.


С третьей стороны, если rust'овые обертки над регистрами генерируются из svd-файлов, то, может, и ваши тоже можно?

Я слегка запоздал на огонек, но по поводу спонтанных аллокаций могу посоветовать:
1) Ставить размер кучи равным 0 — все компилируется, но встает до входа в main на инструкции BKPT
2) В Keil можно сказать #pragma import(__use_no_heap), тогда будет ошибка линковки при попытке юзать кучу
3) В том же Keil после компиляции генерируется htm-файл, в котором можно проследить, кто вызывает malloc.

Мне кажется, что "standardize" это уже не совсем английский, и даже англоговорящим его понять достаточно трудно.


А формальное описание (по крайней мере, у меня в голове это так работает) можно использовать для более автоматизированных ответов на вопросы типа "валидный ли это синтаксис" и "какое правило тут нарушается". И может быть даже для генерации английского описания, по аналогии с ошибками компиляторов.

Да, прошу прощения, как всегда показалось, что контекст очевиден. Я имел в виду https://en.cppreference.com/w/cpp/thread/future.

Ммм, вроде по смыслу не то немного. Фьючерс — это вроде как "результат" операции, которая еще не произошла, но произойдет.

Помогло бы или нет — тут я не возьмусь судить. Но мне все же кажется, что жить стало бы полегче.

Как бы futures перевести так, чтоб не вызывало ассоциаций с биржевыми фьючерсами?

Ну, так-то я понимаю, что это было исправление косяка и это, в целом, хорошо.


Но такие случаи как раз и вызывают желание "скомпилировать стандарт" (в смысле, как-то формально верифицировать), чтобы подобные недосмотры были менее вероятны. Потому что сейчас (как мне кажется) ни один человек в одиночку стандарт целиком в голове удержать уже не может.

Возможность объявлять (или определять, тут это неважно) friend-функции в классах?

Зачем нужна возможность их определять — реально непонятно, кстати. Кому-то было лень было выйти за пределы класса и там тело функции написать? Или логика такая, что френд-функция — это почти что метод?


Это мой внутренний парсер ломает, внутри фигурных скобок класса написан код, который к этой области видимости не относится, это как-то совсем странно.

Ндааа…
Если что-то стало работать в С++17, а потом перестало в С++20, то я могу лишь сделать вывод, что это в стандарт внести слишком поспешили.

Я предпочитаю рассматривать это как урок :)
Типа, "смотрите, дети, вот что бывает, когда система типов делается методом фигак-фигак^W ad-hoc, а не формально доказывается".

Тут не спорю.
По крайней мере, транспиляторы из плюсов в плюсы на каждом шагу не используются :)

Стараются сохранять так-то.

Ну, иногда меняют таки вполне осознанно — типа, поведение auto int = {5};(если я правильно помню) в С++14.


Или смена смысла ключевого слово auto в С++11. Или запрет триграфов (господи, наконец-то!).


Я соглашусь, это все изменения к лучшему, но все же ломающие обратную совместимость.

Ну это уже исправлено: C++17-comformant-компиляторов от одного до трёх (не помню, какая там ситуация с параллельными алгоритмами у gcc и clang).

Увы, нет, это означает лишь, что теперь есть не один С++, а целых пять (не считая гибридных разновидностей, типа armcc, когда компилятор почти С++11, а STL осталась от С++03).


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

Эмм… а это не значит ли, что произвольный кусок кода нельзя однозначно трактовать? Я в формальных грамматиках не силен, но звучит как-то совсем страшно.

Я скорее имел в виду — как они вносят изменения в стандарт, не ломая то, что уже там есть?

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


С другой есть десятки (если не сотни) компиляторов и стандартных библиотек, которые этот самый стандарт интерпретируют по-разному.


С третьей стороны вышеупомянутый стандарт и библиотеку меняют каждые 3 года, в т.ч. меняя поведение уже существующего кода.


Возникает резонный вопрос — а как жить-то вообще, ребят?


Spoiler header

Может, пора стандарт переписывать на каком-нибудь более формальном языке, а не на английском, и компилировать? Чтобы он хотя бы самосогласованным был?


Как вообще один человек может все это в голове удержать?! Как комитет с этим справляется?!

Кстати, perf тоже умеет показывать ошибочные предсказания.

Information

Rating
4,836-th
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Registered
Activity