Проблема
В низкоуровневом встроенном контексте « голого металла» я хотел бы создать пустое пространство в памяти внутри структуры C ++ без какого-либо имени, чтобы запретить пользователю доступ к такой области памяти.
Прямо сейчас я добился этого, поместив уродливое uint32_t :96;
битовое поле, которое удобно заменит три слова, но вызовет предупреждение от GCC (битовое поле слишком велико, чтобы поместиться в uint32_t), что вполне законно.
Хотя он работает нормально, он не очень чистый, если вы хотите распространить библиотеку с несколькими сотнями этих предупреждений ...
Как мне это сделать правильно?
Почему вообще возникает проблема?
Проект, над которым я работаю, состоит из определения структуры памяти различных периферийных устройств всей линейки микроконтроллеров (STMicroelectronics STM32). Для этого результатом является класс, который содержит объединение нескольких структур, которые определяют все регистры, в зависимости от целевого микроконтроллера.
Вот один простой пример довольно простого периферийного устройства: универсальный ввод / вывод (GPIO)
union
{
struct
{
GPIO_MAP0_MODER;
GPIO_MAP0_OTYPER;
GPIO_MAP0_OSPEEDR;
GPIO_MAP0_PUPDR;
GPIO_MAP0_IDR;
GPIO_MAP0_ODR;
GPIO_MAP0_BSRR;
GPIO_MAP0_LCKR;
GPIO_MAP0_AFR;
GPIO_MAP0_BRR;
GPIO_MAP0_ASCR;
};
struct
{
GPIO_MAP1_CRL;
GPIO_MAP1_CRH;
GPIO_MAP1_IDR;
GPIO_MAP1_ODR;
GPIO_MAP1_BSRR;
GPIO_MAP1_BRR;
GPIO_MAP1_LCKR;
uint32_t :32;
GPIO_MAP1_AFRL;
GPIO_MAP1_AFRH;
uint32_t :64;
};
struct
{
uint32_t :192;
GPIO_MAP2_BSRRL;
GPIO_MAP2_BSRRH;
uint32_t :160;
};
};
Где all GPIO_MAPx_YYY
- макрос, определяемый либо как, либо как uint32_t :32
тип регистра (выделенная структура).
Здесь вы видите, uint32_t :192;
что работает хорошо, но вызывает предупреждение.
Что я рассмотрел до сих пор:
Я мог бы заменить его несколькими uint32_t :32;
(здесь 6), но у меня есть несколько крайних случаев, когда у меня есть uint32_t :1344;
(42) (среди других). Так что я бы не стал добавлять около сотни строк поверх 8k других, даже если генерация структуры написана по сценарию.
Точное предупреждающее сообщение выглядит примерно так:
width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type
(Мне просто нравится, насколько это сомнительно).
Я бы предпочел не решать эту проблему, просто удаляя предупреждение, но используя
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-WTheRightFlag"
/* My code */
#pragma GCC diagnostic pop
может быть решение ... если найду TheRightFlag
. Однако, как указано в этой теме , gcc/cp/class.c
с этой печальной частью кода:
warning_at (DECL_SOURCE_LOCATION (field), 0,
"width of %qD exceeds its type", field);
Это говорит нам, что нет -Wxxx
флага для удаления этого предупреждения ...
источник
char unused[12];
и так далее?uint32_t :192;
.:42*32
вместо:1344
Ответы:
Используйте несколько смежных анонимных битовых полей. Так что вместо:
например, у вас будет:
По одному для каждого регистра, который вы хотите сохранить анонимно.
Если у вас есть большие пробелы, которые нужно заполнить, может быть более четким и менее подверженным ошибкам использование макросов для повторения одного 32-битного пространства. Например, учитывая:
Затем можно добавить пространство 1344 (42 * 32 бита) следующим образом:
источник
uint32_t :1344;
есть на месте), поэтому я бы предпочел не идти этим путем ...Как насчет C ++ - ишского способа?
Вы получаете автозаполнение из-за
GPIO
пространства имен, и нет необходимости в пустом заполнении. Даже более ясно, что происходит, поскольку вы можете видеть адрес каждого регистра, вам вообще не нужно полагаться на поведение заполнения компилятора.источник
ldr r0, [r4, #16]
, в то время как компиляторы с большей вероятностью пропустят эту оптимизацию с каждым адресом, объявленным отдельно. GCC, вероятно, загрузит каждый адрес GPIO в отдельный регистр. (Из буквального набора, хотя некоторые из них могут быть представлены как повернутые непосредственно в кодировке Thumb.)static volatile uint32_t &MAP0_MODER
, а неinline
.inline
Переменная не компилируется. (static
позволяет избежать статического хранилища для указателя, иvolatile
это именно то, что вам нужно для MMIO, чтобы избежать устранения мертвого хранилища или оптимизации записи / чтения.)static
подходит и для этого случая. Спасибо за упоминаниеvolatile
, я добавлю его к своему ответу (и изменю встроенный на статический, чтобы он работал до C ++ 17).GPIOA::togglePin()
.На арене встраиваемых систем вы можете моделировать оборудование либо с помощью структуры, либо путем определения указателей на адреса регистров.
Моделирование по структуре не рекомендуется, поскольку компилятору разрешено добавлять отступы между членами для целей выравнивания (хотя многие компиляторы для встроенных систем имеют прагму для упаковки структуры).
Пример:
Вы также можете использовать обозначение массива:
Если вы должны использовать структуру, IMHO, лучший способ пропустить адреса - это определить член и не обращаться к нему:
В одном из наших проектов у нас есть как константы, так и структуры от разных поставщиков (поставщик 1 использует константы, а поставщик 2 использует структуры).
источник
static
члены структуры, предполагая, что автозаполнение может отображать статические члены. В противном случае это также могут быть встроенные функции-члены.public:
иprivate:
столько раз, сколько захотите, чтобы получить правильный порядок полей.)public
иprivate
нестатические члены данных, то это не стандартный тип макета , поэтому он не обеспечивает гарантий упорядочения, о которых вы думаете. (И я почти уверен, что для варианта использования OP действительно требуется стандартный тип макета.)volatile
об этих объявлениях, BTW, для регистров ввода-вывода с отображением в память.geza прав, что вы действительно не хотите использовать для этого классы.
Но, если вы настаиваете, лучший способ добавить неиспользуемый член шириной n байтов - это просто сделать это:
Если вы добавите прагму, зависящую от реализации, чтобы предотвратить добавление произвольного заполнения к членам класса, это может сработать.
Для GNU C / C ++ (gcc, clang и другие, поддерживающие те же расширения) одно из допустимых мест для размещения атрибута:
(пример в обозревателе компилятора Godbolt показывает
offsetof(GPIO, b)
= 7 байт.)источник
Чтобы расширить ответы @ Clifford и @Adam Kotwasinski:
источник
Чтобы расширить ответ Клиффорда, вы всегда можете макрос анонимных битовых полей.
Так что вместо
использовать
А затем используйте это как
К сожалению, вам понадобится столько
EMPTY_32_X
вариантов, сколько у вас есть байтов :( Тем не менее, это позволяет вам иметь отдельные объявления в вашей структуре.источник
#define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1
and#define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1
etc.Чтобы определить большой разделитель как группы по 32 бита.
источник
Думаю, было бы полезно ввести еще какую-то структуру; что, в свою очередь, может решить проблему проставок.
Назовите варианты
Хотя плоские пространства имен хороши, проблема в том, что вы получаете разношерстный набор полей и нет простого способа передать все связанные поля вместе. Более того, используя анонимные структуры в анонимном объединении, вы не можете передавать ссылки на сами структуры или использовать их в качестве параметров шаблона.
Поэтому в качестве первого шага я бы подумал о том, чтобы выделить
struct
:И наконец, глобальный заголовок:
Теперь я могу написать
void special_map0(Gpio:: Map0 volatile& map);
, а также получить краткий обзор всех доступных архитектур с первого взгляда.Простые распорки
Благодаря разделению определения на несколько заголовков, заголовки становятся более управляемыми по отдельности.
Поэтому мой первоначальный подход к точному соответствию вашим требованиям заключался бы в повторении
std::uint32_t:32;
. Да, он добавляет несколько строк по 100 к существующим строкам 8k, но поскольку каждый заголовок индивидуально меньше, это может быть не так плохо.Но если вы готовы рассмотреть более экзотические решения ...
Представляем $.
Это малоизвестный факт, который
$
является жизнеспособным символом для идентификаторов C ++; это даже жизнеспособный начальный символ (в отличие от цифр).$
Появляются в исходном коде, вероятно , поднять брови, и$$$$
, безусловно , будет привлекать к себе внимание во время обзора кода. Это то, чем вы легко можете воспользоваться:Вы даже можете собрать простой «линт» в качестве ловушки перед фиксацией или в вашем CI, который ищет
$$$$
в зафиксированном коде C ++ и отклоняет такие фиксации.источник
GPIO_MAP0_MODER
по-видимому, естьvolatile
.) Возможно, использование ссылки или параметра шаблона ранее анонимных членов могло бы быть полезным. И для общего случая структур заполнения, конечно. Но вариант использования объясняет, почему OP оставил их анонимными.$$$padding##Index_[N_];
чтобы сделать имя поля более понятным, если оно когда-либо появлялось при автозаполнении или при отладке. (Илиzz$$$padding
для сортировки поGPIO...
именам, потому что весь смысл этого упражнения в соответствии с OP - более приятное автозаполнение для отображенных в памяти имен местоположений ввода-вывода.)volatile
квалификатор в ссылке, который был исправлен. Что касается наименования; Я доведу его до ОП. Есть много вариантов (заполнение, зарезервировано, ...), и даже «лучший» префикс для автозаполнения может зависеть от имеющейся IDE, хотя я ценю идею настройки сортировки.struct
), и что вunion
конечном итоге они распространяются повсюду даже в специфичных для архитектуры битах, которые может меньше заботиться о других.Хотя я согласен, что структуры не должны использоваться для доступа к портам ввода-вывода MCU, исходный вопрос можно ответить следующим образом:
Вам может потребоваться заменить
__attribute__((packed))
на#pragma pack
или аналогичный в зависимости от синтаксиса вашего компилятора.Смешивание частных и общедоступных членов в структуре обычно приводит к тому, что такой макет памяти больше не гарантируется стандартом C ++. Однако, если все нестатические члены структуры являются частными, она по-прежнему считается POD / стандартным макетом, как и структуры, которые их встраивают.
По какой-то причине gcc выдает предупреждение, если член анонимной структуры является закрытым, поэтому мне пришлось дать ему имя. В качестве альтернативы, включение его в еще одну анонимную структуру также избавляет от предупреждения (это может быть ошибкой).
Обратите внимание, что
spacer
член сам по себе не является частным, поэтому доступ к данным все же можно получить следующим образом:Однако такое выражение выглядит очевидным взломом и, будем надеяться, не будет использовано без действительно уважительной причины, не говоря уже об ошибке.
источник
Анти-раствор.
НЕ ДЕЛАЙТЕ ЭТОГО: смешивайте частные и публичные поля.
Может быть, вам пригодится макрос со счетчиком для генерации уникальных имен переменных?
источник