Как создать «разделитель»? в структуре памяти класса C ++?

В низкоуровневый металл В контексте я хотел бы создать пустое пространство в памяти, внутри структуры 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;
};
};

Где все GPIO_MAPx_YYY это макрос, определяемый как uint32_t :32 или тип регистра (выделенная структура).

Здесь вы видите uint32_t :192; который работает хорошо, но вызывает предупреждение.

Я мог бы заменить его на несколько uint32_t :32; (6 здесь), но у меня есть несколько крайних случаев, когда у меня есть uint32_t :1344; (42) (среди прочих). Поэтому я бы не стал добавлять около ста строк поверх остальных 8 тыс., Хотя генерация структуры выполняется по сценарию.

Точное предупреждение выглядит примерно так:
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 флаг, чтобы удалить это предупреждение …

92

Решение

Используйте несколько смежных анонимных битовых полей. Так что вместо:

    uint32_t :160;

например, у вас будет:

    uint32_t :32;
uint32_t :32;
uint32_t :32;
uint32_t :32;
uint32_t :32;

Один для каждого регистра вы хотите быть анонимным.

Если у вас есть большие пробелы для заполнения, это может быть более понятным и менее подверженным ошибкам при использовании макросов для повторения одного 32-битного пространства. Например, учитывая:

#define REPEAT_2(a) a a
#define REPEAT_4(a) REPEAT_2(a) REPEAT_2(a)
#define REPEAT_8(a) REPEAT_4(a) REPEAT_4(a)
#define REPEAT_16(a) REPEAT_8(a) REPEAT_8(a)
#define REPEAT_32(a) REPEAT_16(a) REPEAT_16(a)

Затем можно добавить пробел 1344 (42 * 32 бита):

struct
{
...
REPEAT_32(uint32_t :32;)
REPEAT_8(uint32_t :32;)
REPEAT_2(uint32_t :32;)
...
};
34

Другие решения

Как насчет C ++ — это путь?

namespace GPIO {

static volatile uint32_t &MAP0_MODER = *reinterpret_cast<uint32_t*>(0x4000);
static volatile uint32_t &MAP0_OTYPER = *reinterpret_cast<uint32_t*>(0x4004);

}

int main() {
GPIO::MAP0_MODER = 42;
}

Вы получаете автозаполнение из-за GPIO пространство имен, и нет необходимости в фиктивном заполнении. Даже более ясно, что происходит, так как вы можете видеть адрес каждого регистра, вам совсем не нужно полагаться на поведение заполнения компилятора.

44

На арене встраиваемых систем вы можете моделировать оборудование, используя структуру или определяя указатели на адреса регистров.

Моделирование по структуре не рекомендуется, поскольку компилятору разрешено добавлять отступы между элементами для выравнивания (хотя многие компиляторы для встроенных систем имеют прагму для упаковки структуры).

Пример:

uint16_t * const UART1 = (uint16_t *)(0x40000);
const unsigned int UART_STATUS_OFFSET = 1U;
const unsigned int UART_TRANSMIT_REGISTER = 2U;
uint16_t * const UART1_STATUS_REGISTER = (UART1 + UART_STATUS_OFFSET);
uint16_t * const UART1_TRANSMIT_REGISTER = (UART1 + UART_TRANSMIT_REGISTER);

Вы также можете использовать обозначение массива:

uint16_t status = UART1[UART_STATUS_OFFSET];

Если вы должны использовать структуру, IMHO, лучший способ пропустить адреса — определить члена, а не получить к нему доступ:

struct UART1
{
uint16_t status;
uint16_t reserved1; // Transmit register
uint16_t receive_register;
};

В одном из наших проектов у нас есть как константы, так и структуры от разных поставщиков (поставщик 1 использует константы, а поставщик 2 использует структуры).

20

Геза прав, что вы действительно не хотите использовать классы для этого.

Но, если вы настаиваете, лучший способ добавить неиспользованный член N ширина байта, это просто сделать так:

char unused[n];

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


Для GNU C / C ++ (gcc, clang и другие, которые поддерживают те же расширения), одно из допустимых мест для размещения атрибута:

#include <stddef.h>
#include <stdint.h>
#include <assert.h>  // for C11 static_assert, so this is valid C as well as C++

struct __attribute__((packed)) GPIO {
volatile uint32_t a;
char unused[3];
volatile uint32_t b;
};

static_assert(offsetof(struct GPIO, b) == 7, "wrong GPIO struct layout");

(пример на проводнике компилятора Godbolt показ offsetof(GPIO, b) = 7 байт.)

12

Чтобы расширить ответы @ Клиффорда и @ Адама Котвасинского:

#define REP10(a)        a a a a a a a a a a
#define REP1034(a)      REP10(REP10(REP10(a))) REP10(a a a) a a a a

struct foo {
int before;
REP1034(unsigned int :32;)
int after;
};
int main(void){
struct foo bar;
return 0;
}
9

Чтобы расширить ответ Клиффорда, вы всегда можете макрокомандировать анонимные битовые поля.

Так что вместо

uint32_t :160;

использование

#define EMPTY_32_1 \
uint32_t :32
#define EMPTY_32_2 \
uint32_t :32;     \ // I guess this also can be replaced with uint64_t :64
uint32_t :32
#define EMPTY_32_3 \
uint32_t :32;     \
uint32_t :32;     \
uint32_t :32
#define EMPTY_UINT32(N) EMPTY_32_ ## N

А потом использовать как

struct A {
EMPTY_UINT32(3);
/* which resolves to EMPTY_32_3, which then resolves to real declarations */
}

К сожалению, вам понадобится как можно больше EMPTY_32_X Вариантов как можно больше байтов 🙁
Тем не менее, он позволяет вам иметь отдельные объявления в вашей структуре.

7

Чтобы определить большой разделитель как группы из 32 бит.

#define M_32(x)   M_2(M_16(x))
#define M_16(x)   M_2(M_8(x))
#define M_8(x)    M_2(M_4(x))
#define M_4(x)    M_2(M_2(x))
#define M_2(x)    x x

#define SPACER int : 32;

struct {
M_32(SPACER) M_8(SPACER) M_4(SPACER)
};
1

Я думаю, что было бы полезно ввести еще несколько структур; что, в свою очередь, может решить проблему с проставками.

Назовите варианты

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

Поэтому в качестве первого шага я бы рассмотрел вспыхивая struct:

// GpioMap0.h
#pragma once

// #includes

namespace Gpio {
struct Map0 {
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;
};
} // namespace Gpio

// GpioMap1.h
#pragma once

// #includes

namespace Gpio {
struct Map1 {
// fields
};
} // namespace Gpio

// ... others headers ...

И, наконец, глобальный заголовок:

// Gpio.h
#pragma once

#include "GpioMap0.h"#include "GpioMap1.h"// ... other headers ...

namespace Gpio {
union Gpio {
Map0 map0;
Map1 map1;
// ... others ...
};
} // namespace Gpio

Теперь я могу написать void special_map0(Gpio:: Map0 volatile& map);, а также получить краткий обзор всех доступных архитектур с первого взгляда.

Простые проставки

С определением, разделенным на несколько заголовков, заголовки индивидуально намного более управляемы.

Поэтому, мой первоначальный подход, чтобы точно соответствовать вашим требованиям, был бы придерживаться повторного std::uint32_t:32;, Да, он добавляет несколько сотен строк к существующим 8-тысячным строкам, но поскольку каждый заголовок индивидуально меньше, он может быть не таким плохим.

Если вы готовы рассмотреть более экзотические решения, хотя …

Представляем $.

Это малоизвестный факт, что $ является жизнеспособным символом для идентификаторов C ++; это даже жизнеспособный стартовый символ (в отличие от цифр).

$ появление в исходном коде, вероятно, вызовет удивление, и $$$$ определенно собирается привлечь внимание во время обзоров кода. Это то, что вы можете легко использовать в своих интересах:

#define GPIO_RESERVED(Index_, N_) std::uint32_t $$$$##Index_[N_];

struct Map3 {
GPIO_RESERVED(0, 6);
GPIO_MAP2_BSRRL;
GPIO_MAP2_BSRRH;
GPIO_RESERVED(1, 5);
};

Вы даже можете собрать простой «пух» в качестве ловушки перед фиксацией или в своем CI, который ищет $$$$ в принятом коде C ++ и отклонить такие коммиты.

1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector