Комментарии 24
Всё остальное с C++'14 на голом железе на фиг не нужно или непринципиально.
А что это за ситуация с 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)), но по сути все верно.
Использование линкера жестоко ломает портабельность не говоря уже о том, что вы фактически призываете использовать другой язык программированя (язык описния скриптов линкера).
Лол, о какой портабельностьи вообще речь, когда указывается специфичный для платформы адрес?
Что же до отладчиков — то я как видел отладчики, которые умеют вызывать функции — так и запросто могу представить отладчик, который не сможет показать значение 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]
Первый вариант с оптимизацией — две инструкции :)
Есть у меня класс таймеров, один на все серии STM32, если я, допустим, хочу разрешить какое-то прерывание, то делаю так:
tim.enableInterrupts<TimInt::Break>();
При этом на стадии компиляции проверяется есть ли у данного таймера этот Break и проверяется именно при помощи концептов, потому что если использовать static_assert(), то получим ошибку внутри enableInterrupts() и не понятно что именно является ее источником, а с концептом будет ошибка в месте вызова.
потому что если использовать static_assert(), то получим ошибку внутри enableInterrupts() и не понятно что именно является ее источником
Ну так это же и есть
Улучшить сообщения от ошибках при инстанциировании шаблонов не теми типами.
Интригующие возможности С++ 20 для разработчиков встраиваемых систем