Как стать автором
Обновить

Комментарии 24

не совсем понял чем эти фичи с++20 примечательны для разработчиков встраиваемых систем более, чем для буквально любых других разработчиков на с++.
«Интригующей возможностью» было бы возвращение произвольного преобразования указателей в constexpr, из-за отмены которого вес embedded вынужден пользоваться адовыми макросами с совершенно нечитаемыми сообщения об ошибках.
Всё остальное с C++'14 на голом железе на фиг не нужно или непринципиально.
Железо железу рознь, вон ESP-шки и под 400Мгц бывают, и памяти 512Мб не так уж фантастично.
Угу и в результате всё тот же ад с макросами для работы с аппаратурой.

А что это за ситуация с constexpr указателями? Пример или ссылку можете дать почитать?

Представьте, что у вас есть структура отображаемая на абсолютные адреса — как её описать на С++?
Можно было бы, что-то вроде:
constexpr uint32_t* HW_ADDR = 0x80001234;
struct PortStruct {
    volatile uint32_t control;
    volatile unit32_t data;
};
constexpr HW_PORT* HwPort = (PortStruct *) HW_ADDR;
// И юзаем hwPort->control = 12345;

Красиво, оптимизируемо и нормальные ошибки, если с типа накосачишь.
Да? Нет! В С++11 можно было, а с C++14 нельзя потому, что программисты в ногу выстрелят (ха-ха, типа, на плюсах больше нечем в ноги стрелять).
И возвращается старый добрый макросный ад:
#define HW_ADDR 0x80001234
struct PortStruct {
    volatile uint32_t control;
    volatile unit32_t data;
}
#define HW_PORT ((PortStruct *) HW_ADDR)

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

Зачем тут constexpr? В стандартных хедерах полно таких структур, для примера можно все переписать таким образом:


struct GPIO_TypeDef
{
    __IO uint32_t MODER;
    __IO uint32_t OTYPER;
    __IO uint32_t OSPEEDR;
.....
};

static constexpr uint32_t PERIPH_BASE = 0x40000000;
static constexpr uint32_t D3_AHB1PERIPH_BASE = PERIPH_BASE + 0x18020000;
static constexpr uint32_t GPIOD_BASE = D3_AHB1PERIPH_BASE + 0x0C00;
static const auto GPIOD = (GPIO_TypeDef*)GPIOD_BASE;

Если теперь написать GPIOD->ODR = 123, то даже для -O0 получим три инструкции, меньше просто некуда...

Вообще-то всегда можно было вот так:


inline PortStruct* HwPort() { return (PortStruct*) 0x80001234; }

Если компилятор не в состоянии заинлайнить такую простую функцию — проблема явно не в языке.


А ещё можно сделать вот так:


extern PortStruct HwPort;

и расположить эту штуку по нужному адресу линкером.

Inline непосредственно для инлайнинга уже давно практически бесполезен, нужно использовать всякие attribute((always_inline)), но по сути все верно.

Inline тут был для включения в заголовочный файл.

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

Лол, о какой портабельностьи вообще речь, когда указывается специфичный для платформы адрес?


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

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

Касательно constexpr Вам уже ответили, я дополню что в конкретном случае constexpr в принципе не применим — его назначение это вычисления времени компиляции, что в принципе не возможно для отображаемых структур (да еще и в кросскомпиляции зачастую), так что ужесточение правил в этом случае показывает логическую ошибку в коде.
Еще volatile поля в структурах в c++20 вам код вероятно попортят, особенно если вы используете вложенные структуры. Опять таки ничего криминального в этом нет, наоборот это подчеркивает что volatile не так прост и использовать его следует с умом.

Можно подробнее? Помнится в gcc 6 или 7 я в либе портов передавал в качестве шаблонного параметра (int)GPIOA и т.д., а потом уже пришлось передавать GPIOA_BASE, который не volatile. Соответственно внутри класса сейчас так:


static auto _inline_ base() { return (GPIO_TypeDef*)Gpio; }
static void _inline_ write(bool data) { base()->BSRR = (0x10000 | data) << pin; }

А раньше вместо base() можно было использовать constexpr константу, но по сути изменения минимальны, никаких макросов и непонятных сообщений об ошибках, на генерации кода это также никак не отразилось. Больше ничего на эту тему и не вспомню, при этом C++20 у меня задействуется по-полной...

Куда подробнее — вы, вроде, всё правильно понимаете.
В вашем примере вся грязь просто перехала внутрь inline-функций, а количество кода только выросло. «Gpio» это у вас что? Макрос. А там же на самом деле ещё есть «GpioA»,«GpioB» и т.п.
Вам этих функций нужно будет по две на каждый порт, а внутри всё равно будут макросы. А порты это очень просто случай, хотя и их тысячи. Там же ещё вложенные однотипные структуры и массивы сруктур, всякие аппаратные очереди, буфера CAN/Ethernet и прочие DMA, никзоуровенвый API OS в конце-концов.
А как вы под отладчиком доберётесь до этих структур? Это вообще особая боль.
И всё это из-за идиотского запрета на преобразование типов указателей внутри constexpr выражений, который всё равно концептуально ничего не меняет.
Получается, что есть (в совершенно любой системе) некоторая частоиспользуемая физическая сущность — структора определённого типа, находящаяся по фиксированну адресу, но коммитет упорно делает вид, что её нет, запрещает описывать средствами языка и все продолжают есть кактус.

Передаю в качестве шаблонного параметра GPIOx_BASE, внутри класса получаю его как Gpio, далее использую base() и при -O0 никакого вызова функции не будет, потому что за inline(с подчерками) скрывается always_inline O2. Более того, можно взять не пин, а значительно более сложный список пинов, тогда write() будет выглядеть так:


static void _inline_ write(uint32_t value)
{
    ports_.foreach([=](auto port) __inline__ { GpioT<unbox<port>::gpio, 
       getPinsMask<unbox<port>::gpio>(pins_)>::write(readWrite<PinShift::Write>(value, 
       getIndexedPins<unbox<port>::gpio>(pins_))); });
}

Тут видно 7 вызовов функций, base() — 8-я, она вызывается в самом конце, а readWrite() — это достаточно тяжелая функция вызывающая еще ряд других. Смотрим что будет для простого списка пинов с -O0:


PinList<PC4, PC3, PC2> pins;
pins.write(5);

И получим 8 инcтрукций, никаких вызовов функций здесь нет:


movs r3, #5 
str.w r3, [r7, #404]
ldr.w r3, [r7, #404]
lsls r3, r3, #2 
and.w r3, r3, #28 
orr.w r3, r3, #1835008
ldr r2, [pc, #164]
str r3, [r2, #24]

А вот так всего 3 инструкции:


pins.write<5>();

// ldr r3, [pc, #168]
// ldr r2, [pc, #184]
// str r2, [r3, #24]

Первый вариант с оптимизацией — две инструкции :)

По правде говоря даже С++11 временами поддерживается с горем пополам, имеющимися тулчейнами. Бывало такое что вообще фичи были в пространстве имен std::tr1. Так что С++20 для многих это фантазии, к сожалению.
Где киллер фича которая была у C++ изначально: возможность компилировать в plain C?
НЛО прилетело и опубликовало эту надпись здесь

Есть у меня класс таймеров, один на все серии STM32, если я, допустим, хочу разрешить какое-то прерывание, то делаю так:


tim.enableInterrupts<TimInt::Break>();

При этом на стадии компиляции проверяется есть ли у данного таймера этот Break и проверяется именно при помощи концептов, потому что если использовать static_assert(), то получим ошибку внутри enableInterrupts() и не понятно что именно является ее источником, а с концептом будет ошибка в месте вызова.

потому что если использовать static_assert(), то получим ошибку внутри enableInterrupts() и не понятно что именно является ее источником

Ну так это же и есть


Улучшить сообщения от ошибках при инстанциировании шаблонов не теми типами.

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

НЛО прилетело и опубликовало эту надпись здесь
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории