Доступные только для чтения регистры, отображаемые в памяти, определенные с помощью 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 для С++ не включает const? Я где-то видел упоминание о том, что С++ не хранит целочисленные константные переменные в памяти выполнения, что, если это правда, будет означать, что const нужно будет удалить из-за того, как регистры микроконтроллера отображаются в памяти, но я не могу найти что-либо еще, говорящее что С++ делает это (хотя мой поиск, по общему признанию, был довольно кратким). Не имея большого опыта работы с С++, я также подумал, что, возможно, С++ не допускает членов структуры const, поскольку эти определения типов в основном используются в определениях типов структур для коллекций регистров, но это тоже не так.


person JAB    schedule 14.03.2013    source источник
comment
... и почему первоначальный автор считал, что если нет/иначе, это хороший способ структурировать логику? Ааааааааааааааааааааааааааааа   -  person unwind    schedule 14.03.2013
comment
@unwind Многие советуют всегда ставить положительную часть на первое место. Автор этого кода посчитал #ifndef __cplusplus положительным.   -  person Daniel Fischer    schedule 15.03.2013
comment
Лично я использую несколько способов, чтобы решить, какой блок кода поставить первым; Обычно я основываюсь на сочетании того, какая версия условного оператора выглядит лучше/имеет больше смысла в контексте, какая из них более вероятна, если ее можно оценить, и какой блок кода больше (если операторы с небольшими основными блоками и большие блоки else кажутся мне несбалансированными).   -  person JAB    schedule 15.03.2013
comment
Reputation += LARGE_CONST; /* Ask some bike shed language question, that every idiot (including me) will have an opinion on. */ Однако, возможно, эта ссылка действительно полезна. embedded.com/electronics-blogs/programming-pointers/ 4025609/ Почему этот вопрос помечен ARM?   -  person artless noise    schedule 15.03.2013
comment
@BillPringlemeir Поскольку файл заголовка, в котором был обнаружен фрагмент кода, является частью библиотеки для процессора ARM, в частности CMSIS (отсюда и тег CMSIS). Я подумал, что этого достаточно, чтобы добавить эти теги.   -  person JAB    schedule 15.03.2013
comment
@BillPringlemeir: не все компиляторы созданы равными, возможно, его компилятор ARM может иметь какое-то необычное поведение, объясняющее проблему.   -  person Ben Voigt    schedule 15.03.2013
comment
@BenVoigt: Точно. Если бы я работал с несколькими различными встроенными системами (ARM, не-ARM и т. д.) и встречал фрагмент кода в своем вопросе в разных местах, я бы пропустил arm и cmsis, но, поскольку это было встречено в этом конкретном контексте, я указал этот контекст в тегах.   -  person JAB    schedule 15.03.2013
comment
@JAB: Хотя в этом случае указать, какой компилятор будет полезнее, чем какая архитектура ЦП.   -  person Ben Voigt    schedule 15.03.2013
comment
@BenVoigt: Это правда. Я на самом деле не уверен, что это за компилятор, поскольку я использую Arduino IDE, чтобы позаботиться о большей части этого. Просматривая структуру каталогов, я думаю, что это может быть Sourcery C++ Lite?   -  person JAB    schedule 15.03.2013
comment
@JAB: Arduino использует AVR, а не ARM. Возможно, вы используете среду Armuino?   -  person Ben Voigt    schedule 15.03.2013
comment
@BenVoigt: На самом деле я работаю с Arduino Due, у которого есть процессор ARM (тот, который упоминался в моем вопросе). Однако мой вопрос на самом деле не связан с библиотекой Arduino, а только с базовым уровнем абстракции (или как бы он ни назывался).   -  person JAB    schedule 15.03.2013


Ответы (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 определяют контроль доступа.

person D Krueger    schedule 15.03.2013
comment
И этот ответ, и ответ Бена Фойгта хороши, поэтому было немного сложно выбрать, но в итоге я остановился на этом, так как чувствую, что он более строго относится к конкретному заданному вопросу и, следовательно, работает лучше как официальный, поэтому разговаривать. - person JAB; 19.03.2013

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

person Felipe Lavratti    schedule 14.03.2013
comment
Похоже на цитату - откуда? - person us2012; 14.03.2013
comment
Хотя это вполне может быть частью ответа, однако в ситуации, поднятой здесь, я сомневаюсь, что компилятору когда-либо поручается резервирование пространства, поскольку рассматриваемый регистр, вероятно, относится к аппаратной функции с фиксированным местоположением процессора / периферийных устройств на кристалле. Скорее всего, typedef используется только для объявления указателя на такой регистр. - person Chris Stratton; 14.03.2013
comment
Я также согласен с тем, что typedef'ing типы памяти только для чтения, которые используются с определенными аппаратными адресами, поскольку volatile const совершенно нормально в C++. Сам вопрос вообще не показывает определения глобальной константы, поэтому я не вижу актуальности вашего ответа... - person πάντα ῥεῖ; 14.03.2013
comment
github.com/arduino/Arduino/blob/ide-1.5.x/hardware/arduino/sam/ и github.com/arduino/Arduino/blob/ide -1.5.x/hardware/arduino/sam/ для примера, член структуры и указатель соответственно. - person Chris Stratton; 14.03.2013
comment
Конечно, причина, указанная @fanl, может по-прежнему быть точной причиной, даже если она неприменима, потому что всегда используется просто указатель на тип. Вполне возможно, что автор того заголовка имел именно такое рассуждение (независимо от того, применимо оно на практике или нет). По крайней мере, это правдоподобное рассуждение. Тот факт, что компилятор все еще может это обработать, не обязательно означает, что человек не ошибется. - person Damon; 14.03.2013
comment
@ChrisStratton Для регистров с фиксированным адресом некоторые используют компоновщик для назначения адресов объектам, а не используют определения указателей в коде. Это улучшает отладку, поскольку в этом случае регистры имеют ассоциации символов. Здесь может быть такая ситуация. - person D Krueger; 14.03.2013
comment
@DKrueger - возможно, в каком-то другом случае, но CMSIS явно определяет адреса в файлах заголовков. - person Chris Stratton; 14.03.2013
comment
Продолжая пример SPI от ChrisStratton, здесь используется struct typedef: github.com/arduino/Arduino/blob/ide-1.5.x/hardware/arduino/sam/ - person JAB; 14.03.2013
comment
Хорошо, ребята, отредактировал мой ответ. Понятно теперь. Спасибо за советы. - person Felipe Lavratti; 14.03.2013
comment
Еще один хороший момент, но удаление const по-прежнему не лучшее решение. - person Ben Voigt; 14.03.2013
comment
Все случаи, когда адреса регистров назначаются переменным, кажутся внутри структурных назначений/конструкторов классов, а #define используются для самих голых адресов, поэтому требуемый-инициализатор-для-констант, похоже, не имеет здесь большого значения. Насколько я могу судить, кодовая база никогда не объявляет переменную этого типа const, поэтому инициализация не будет принудительной. - person JAB; 15.03.2013

Как упоминалось @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);
person Ben Voigt    schedule 14.03.2013
comment
Полный код находится здесь: coocox.org/repo/bf7c3c91-96ed-11df-80ae-001d7d723e56/src/cmsis/ Как правильно это сделать? - person Felipe Lavratti; 14.03.2013
comment
@fanl: Действительно, вы можете просто вернуть const обратно. Ремонтопригодность этого кода не ваша проблема. - person Ben Voigt; 15.03.2013
comment
@fanl: Но я добавил свой подход только для справки. - person Ben Voigt; 15.03.2013
comment
Мне интересен ваш подход. Есть ли способ избежать размещения данных в структуре memory_mapped_peripheral? - person Felipe Lavratti; 15.03.2013
comment
Предположение о том, что такая окольная схема лучше всего, по-видимому, вносит большой вклад в спор о пригодности C++ для работы со встроенными аппаратными средствами. Было бы намного чище просто предложить выполнять все манипуляции с вводом-выводом в C и сохранить C++ для задач более высокого уровня (если вы чувствуете необходимость его использовать). - person Chris Stratton; 15.03.2013
comment
[T] смещение адреса закодировано вне зависимости от порядка и заполнения полей внутри структуры [.] Проблема заполнения имеет смысл, но для системы, где известен размер регистра и все регистры в группе являются смежными, мне кажется, что использование более традиционной структуры было бы понятнее/проще для кодировщика. Ваша версия может быть более гибкой, но она также немного более тупая, поскольку вы можете легко изменить порядок смещений, сохраняя порядок переменных, что может вызвать путаницу у тех, кто напрямую ссылается на структуры. - person JAB; 15.03.2013
comment
Вернее, порядок регистров. - person JAB; 15.03.2013
comment
@fanl: выделения нет, потому что ни один объект этого типа не выделен. Вместо этого он используется через указатель. (C++ позволяет объекту с тривиальным конструктором начать свое существование просто за счет наличия области памяти достаточного размера и выравнивания.) - person Ben Voigt; 15.03.2013
comment
@JAB: Одно конкретное место, где гибкость пригодится, - это когда сопоставление содержит пронумерованные наборы регистров, которые не являются смежными (да, Stellaris делает это, хотя в извлеченном фрагменте нет примера). Другое дело, что разные микросхемы семейства имеют разные регистры. Смещения никогда не меняются... но окружающие поля в структуре с #if будут иметь тенденцию отбрасывать последующие поля. По-моему, вы можете условно включить регистры без каких-либо побочных эффектов. - person Ben Voigt; 15.03.2013
comment
@BenVoigt Звучит весьма полезно. И теперь, когда я думаю об этом, вероятно, не будет слишком сложно добавить макрос или два, чтобы ваша установка выглядела менее шаблонной (что-то вроде #define RoReg(type, name, off) volatile const type & name (void) const {return offset(off)[0];} и #define RoRegArray(type, name, off) volatile const type & name (uintptr_t i) const {return offset(off)[i];}), а затем, может быть, еще один или два, чтобы сделать структуры чуть менее грязно. - person JAB; 15.03.2013
comment
@JAB: Да, в значительной степени (но отбросьте аргумент типа, это всегда uint32_t). И когда массив несмежный, вы не сможете использовать макросы. - person Ben Voigt; 15.03.2013
comment
@BenVoigt: Как несмежный массив будет работать в указанном вами контексте? Разве вам не нужен класс, который в любом случае переопределяет оператор [], чтобы он работал так же, как доступ к непрерывному массиву? - person JAB; 15.03.2013
comment
(Кроме того, что, если это среда, которая поддерживает несколько регистров в слове? Тогда у вас могут быть регистры uint16_t и uint8_t. И тогда, возможно, могут быть системы с uint64_t регистрами.) - person JAB; 15.03.2013
comment
@JAB: Просто рассчитайте смещение на основе i вместо использования подписки. И хотя вполне возможно, что существуют системы с разными размерами регистров, у ARM их нет (многие из 32-битных регистров имеют зарезервированные или нереализованные биты, но 32-битное чтение или запись по-прежнему используются) - person Ben Voigt; 15.03.2013