Регистры с отображением в памяти только для чтения, определенные с `volatile const` в C, но только`volatile` в C++

Работая над проектом встроенных систем с использованием Atmel SAM3X8E, я заметил следующий фрагмент кода в некоторых заголовочных файлах CMSIS.

#ifndef __cplusplus
typedef volatile const uint32_t RoReg; /**< Read only 32-bit register (volatile const unsigned int) */
#else
typedef volatile       uint32_t RoReg; /**< Read only 32-bit register (volatile const unsigned int) */
#endif

Почему typedef для C++ не включает const? Я видел где-то упоминание о том, что C++ не хранит целочисленные константные переменные в оперативной памяти, что, если true, будет означать const пришлось бы удалить из-за того, что регистры микроконтроллера отображаются в памяти, но я не могу найти ничего другого, говорящего, что C++ делает это (хотя мой поиск был довольно коротким). Не имея большого опыта работы с C++, я также подумал, что C++ не позволяет const члены структуры, так как эти typedefs в основном используются в struct typedefs для коллекций регистров, но, похоже, это тоже не так.

3 ответа

Решение

Потому что нет RoReg объект когда-либо создается, нет веских причин, чтобы опустить const классификатор в typedef.

Каждое использование RoReg находится в макросе, который определяет указатель на тип...

#define REG_WDT_SR (*(RoReg*)0x400E1A58U) /**< \brief (WDT) Status Register */

... или struct объявление, доступ к которому осуществляется с помощью аналогичного макроса.

typedef struct {
  WoReg WDT_CR; /**< \brief (Wdt Offset: 0x00) Control Register */
  RwReg WDT_MR; /**< \brief (Wdt Offset: 0x04) Mode Register */
  RoReg WDT_SR; /**< \brief (Wdt Offset: 0x08) Status Register */
} Wdt;

#define WDT        ((Wdt    *)0x400E1A50U) /**< \brief (WDT) Base Address */

Даже с const квалификатор, код должен вести себя одинаково как на C, так и на C++.

Возможно, автор неверно истолковал стандарт. Чтобы гарантировать, что структура C++ имеет ту же структуру, что и в C, требуется, чтобы класс "имел одинаковое управление доступом (пункт 11) для всех нестатических элементов данных". Автор, возможно, ошибся const а также volatile для спецификаторов контроля доступа. Если бы они были, то вы бы хотели, чтобы все члены структуры имели одинаковые cv-квалификаторы, чтобы обеспечить совместимость между макетами C и C++ (и аппаратными). Но это public, protected, а также private которые определяют контроль доступа.

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

Как упомянуто @fanl, const действительно изменяет связывание глобалов по умолчанию в C++ и не позволяет определить переменную без инициализации.

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

И, кроме того, эти переменные не определяются (что может привести к выбору адреса компилятором и компоновщиком), к ним всегда обращаются через указатели, причем адрес фиксируется в соответствии с картой памяти.

Для заголовков, предназначенных исключительно для использования C++, я так и делаю (карта памяти, соответствующая чипу TI Stellaris).

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

template<uintptr_t extent>
struct memory_mapped_peripheral
{
    char data[extent];
    volatile       uint32_t* offset( uintptr_t off )       { return reinterpret_cast<volatile       uint32_t*>(data+off); }
    volatile const uint32_t* offset( uintptr_t off ) const { return reinterpret_cast<volatile const uint32_t*>(data+off); }
};

struct LM3S_SYSTICK : private memory_mapped_peripheral<0x1000>
{
    volatile       uint32_t& CTRL   (void)             { return offset(0x010)[0]; }
    volatile       uint32_t& RELOAD (void)             { return offset(0x014)[0]; }
    volatile       uint32_t& CURRENT(void)             { return offset(0x018)[0]; }
}* const SYSTICK = reinterpret_cast<LM3S_SYSTICK*>(0xE000E000);

struct LM3S_NVIC : private memory_mapped_peripheral<0x1000>
{
    volatile       uint32_t& EN    (uintptr_t i)       { return offset(0x100)[i]; }
    volatile       uint32_t& DIS   (uintptr_t i)       { return offset(0x180)[i]; }
    volatile       uint32_t& PEND  (uintptr_t i)       { return offset(0x200)[i]; }
    volatile       uint32_t& UNPEND(uintptr_t i)       { return offset(0x280)[i]; }
    volatile const uint32_t& ACTIVE(uintptr_t i) const { return offset(0x300)[i]; }
    volatile       uint32_t& PRI   (uintptr_t i)       { return offset(0x400)[i]; }
    volatile       uint32_t& SWTRIG(void)              { return offset(0xF00)[0]; }
}* const NVIC = reinterpret_cast<LM3S_NVIC*>(0xE000E000);
Другие вопросы по тегам