Как мне создать «спейсер» в структуре памяти класса 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 ...

93 голоса | спросил J Faucher 2 52018vEurope/Moscow11bEurope/MoscowFri, 02 Nov 2018 00:53:42 +0300 2018, 00:53:42

9 ответов


0

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

    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;)
    ...
};
ответил Clifford 2 52018vEurope/Moscow11bEurope/MoscowFri, 02 Nov 2018 02:02:03 +0300 2018, 02:02:03
0

А как насчет 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, и нет необходимости в фиктивном заполнении. Даже более понятно, что происходит, так как вы можете видеть адрес каждого регистра, вам совсем не нужно полагаться на поведение заполнения компилятора.

ответил geza 2 52018vEurope/Moscow11bEurope/MoscowFri, 02 Nov 2018 03:46:45 +0300 2018, 03:46:45
0

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

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

Пример:

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 использует структуры).

ответил Thomas Matthews 2 52018vEurope/Moscow11bEurope/MoscowFri, 02 Nov 2018 01:30:56 +0300 2018, 01:30:56
0

Подробнее об ответах @ Клиффорда и @ Адама Котвасинского:

#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;
}
ответил mosvy 2 52018vEurope/Moscow11bEurope/MoscowFri, 02 Nov 2018 10:39:22 +0300 2018, 10:39:22
0

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

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

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, сколько у вас есть байтов :( Тем не менее, он позволяет вам иметь отдельные объявления в вашей структуре.

ответил Adam Kotwasinski 2 52018vEurope/Moscow11bEurope/MoscowFri, 02 Nov 2018 04:03:09 +0300 2018, 04:03:09
0

Чтобы определить большой разделитель как группы из 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)
};
ответил jxh 2 52018vEurope/Moscow11bEurope/MoscowFri, 02 Nov 2018 12:35:07 +0300 2018, 12:35:07
0

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

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

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

В качестве первого шага я бы поэтому рассмотрел выделение 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 ++; это даже жизнеспособный начальный символ (в отличие от цифр).

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

#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 ++ и отклонил такие коммиты.

ответил Matthieu M. 2 52018vEurope/Moscow11bEurope/MoscowFri, 02 Nov 2018 23:48:30 +0300 2018, 23:48:30
0

Хотя я согласен, что структуры не должны использоваться для доступа к порту ввода /вывода MCU, на оригинальный вопрос можно ответить следующим образом:

struct __attribute__((packed)) test {
       char member1;
       char member2;
       volatile struct __attribute__((packed))
       {
       private:
              volatile char spacer_bytes[7];
       }  spacer;
       char member3;
       char member4;
};

Возможно, вам придется заменить __attribute__((packed)) на #pragma pack или аналогичный в зависимости от синтаксиса вашего компилятора.

Смешивание частных и открытых членов в структуре обычно приводит к тому, что компоновка памяти больше не гарантируется стандартом C ++. Однако, если все нестатические члены структуры являются закрытыми, это все еще считается POD /стандартным макетом, как и структуры, которые их внедряют.

По какой-то причине gcc выдает предупреждение, если член анонимной структуры является закрытым, поэтому мне пришлось дать ему имя. Кроме того, включение его в еще одну анонимную структуру также избавляет от предупреждения (это может быть ошибкой).

Обратите внимание, что элемент spacer сам по себе не является закрытым, поэтому доступ к данным по-прежнему возможен следующим образом:

(char*)(void*)&testobj.spacer;

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

ответил Jack White 4 72018vEurope/Moscow11bEurope/MoscowSun, 04 Nov 2018 14:41:20 +0300 2018, 14:41:20
0

Анти-раствор.

НЕ ДЕЛАЙТЕ ЭТОГО: Смешайте частные и публичные поля.

Может быть, макрос со счетчиком для генерации уникальных имен переменных будет полезен?

#define CONCAT_IMPL( x, y ) x##y
#define MACRO_CONCAT( x, y ) CONCAT_IMPL( x, y )
#define RESERVED MACRO_CONCAT(Reserved_var, __COUNTER__) 


struct {
    GPIO_MAP1_CRL;
    GPIO_MAP1_CRH;
    GPIO_MAP1_IDR;
    GPIO_MAP1_ODR;
    GPIO_MAP1_BSRR;
    GPIO_MAP1_BRR;
    GPIO_MAP1_LCKR;
private:
    char RESERVED[4];
public:
    GPIO_MAP1_AFRL;
    GPIO_MAP1_AFRH;
private:
    char RESERVED[8];
};
ответил Robert Andrzejuk 2 52018vEurope/Moscow11bEurope/MoscowFri, 02 Nov 2018 13:38:26 +0300 2018, 13:38:26

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132