如何在C++类内存结构中创建"spacer"?

J F*_*her 93 c c++ memory-management bare-metal low-level

问题

低级裸机嵌入式上下文中,我想在内存中创建一个空格,在C++结构中并且没有任何名称,以禁止用户访问这样的内存位置.

现在,我已经通过设置一个丑陋的uint32_t :96;位域来实现它,它将方便地取代三个单词,但它会从GCC发出警告(Bitfield太大而不适合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;
    };
};
Run Code Online (Sandbox Code Playgroud)

所有GPIO_MAPx_YYY都是宏,定义为uint32_t :32或寄存器类型(专用结构).

在这里,您可以看到uint32_t :192;哪个效果很好,但它会触发警告.

到目前为止我考虑过的事情:

我可能已经用几个替换它uint32_t :32;(这里有6个),但我有一些极端情况,我有uint32_t :1344;(42)(其中).所以我宁愿不在8k其他行上添加大约100行,即使结构生成是脚本化的.

确切的警告信息是这样的:( 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
Run Code Online (Sandbox Code Playgroud)

可能是一个解决方案...如果我发现TheRightFlag.但是,正如在这个帖子中指出的那样,gcc/cp/class.c这个可悲的代码部分:

warning_at (DECL_SOURCE_LOCATION (field), 0,
        "width of %qD exceeds its type", field);
Run Code Online (Sandbox Code Playgroud)

这告诉我们没有-Wxxx标志可以删除此警告......

gez*_*eza 44

如何使用C++ - ish方式?

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;
}
Run Code Online (Sandbox Code Playgroud)

由于GPIO命名空间,您获得自动完成,并且不需要虚拟填充.甚至,更清楚的是发生了什么,因为你可以看到每个寄存器的地址,你根本不必依赖编译器的填充行为.

  • 结果证明我的担忧是没有根据的; ARM GCC也会以这种方式进行优化.https://godbolt.org/z/ztB7hi.但请注意,您需要`static volatile uint32_t&MAP0_MODER`,而不是`inline`.`inline`变量不能编译.(`static`避免指针有任何静态存储,而`volatile`正是你想让MMIO避免死存储消除或写/读回优化的原因.) (4认同)
  • 这不是严格定义的行为,请参阅[此推特帖子](https://twitter.com/myrrlyn/status/940365445957279744)和[也许这个有用](https://twitter.com/shafikyaghmour/status/940451354631290880 ) (2认同)

Cli*_*ord 34

使用多个相邻的匿名位域.所以代替:

    uint32_t :160;
Run Code Online (Sandbox Code Playgroud)

例如,你有:

    uint32_t :32;
    uint32_t :32;
    uint32_t :32;
    uint32_t :32;
    uint32_t :32;
Run Code Online (Sandbox Code Playgroud)

每个寄存器一个,你想匿名.

如果你有足够的空间来填充它可能会更清晰,并且更不容易使用宏来重复单个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)
Run Code Online (Sandbox Code Playgroud)

然后可以添加1344(42*32位)空间:

struct
{
    ...
    REPEAT_32(uint32_t :32;) 
    REPEAT_8(uint32_t :32;) 
    REPEAT_2(uint32_t :32;)
    ...
};
Run Code Online (Sandbox Code Playgroud)

  • 我同意你的观点,并且,公平地说,我认为我将使用你答案中显示的这两种方法之一.我只是想确保在这样做之前C++没有提供更好的解决方案.我很清楚ST提供了那些头文件,但是这些文件是基于大量使用宏和按位操作构建的.我的项目是构建一个C++,相当于那些容易出错的标题(使用枚举类,位域等).这就是为什么我们使用脚本将CMSIS标题"翻译"到我们的C++结构中(并且在ST文件中发现了一些错误) (2认同)

Tho*_*ews 20

在嵌入式系统领域,您可以通过使用结构或通过定义指向寄存器地址的指针来建模硬件.

建议不要使用结构建模,因为允许编译器在成员之间添加填充以进行对齐(尽管嵌入式系统的许多编译器都有一个用于打包结构的编译指示).

例:

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);
Run Code Online (Sandbox Code Playgroud)

您还可以使用数组表示法:

uint16_t status = UART1[UART_STATUS_OFFSET];  
Run Code Online (Sandbox Code Playgroud)

如果你必须使用结构,恕我直言,跳过地址的最佳方法是定义一个成员而不是访问它:

struct UART1
{
  uint16_t status;
  uint16_t reserved1; // Transmit register
  uint16_t receive_register;
};
Run Code Online (Sandbox Code Playgroud)

在我们的一个项目中,我们有来自不同供应商的常量和结构(供应商1使用常量,而供应商2使用结构).


Lig*_*ica 12

geza是对的,你真的不想为此使用类.

但是,如果您坚持,添加n个字节宽度的未使用成员的最佳方法就是这样做:

char unused[n];
Run Code Online (Sandbox Code Playgroud)

如果添加特定于实现的pragma以防止向类的成员添加任意填充,则可以这样做.


对于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");
Run Code Online (Sandbox Code Playgroud)

(例如Godbolt编译器资源管理器显示offsetof(GPIO, b)= 7个字节.)


mos*_*svy 9

扩展@ Clifford和@Adam Kotwasinski的答案:

#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;
}
Run Code Online (Sandbox Code Playgroud)


Ada*_*ski 7

为了扩展Clifford的答案,你总是可以将匿名位域宏观化.

而不是

uint32_t :160;
Run Code Online (Sandbox Code Playgroud)

使用

#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
Run Code Online (Sandbox Code Playgroud)

然后像使用它一样

struct A {
  EMPTY_UINT32(3);
  /* which resolves to EMPTY_32_3, which then resolves to real declarations */
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,你需要尽可能多的EMPTY_32_X变量和你拥有的字节:(但是,它允许你在你的结构中有单个声明.

  • 使用Boost CPP宏,我认为您可以使用递归来避免手动创建所有必需的宏. (5认同)
  • 您可以级联它们(直到预处理器递归限制,但这通常是充足的).所以`#define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1`和`#define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1`等 (3认同)
  • C和C++使用相同的C预处理器; 除了可能为C提供必要的提升标题之外,我没有看到任何问题.他们确实将CPP宏内容放在单独的标题中. (2认同)