如何在 C + + 类内存结构中创建一个“间隔器”?

问题

低位裸金属嵌入上下文中,我想在内存中创建一个空白空间,在 C + + 结构中,没有任何名称,以禁止用户访问这样的内存位置。

现在,我已经实现了它通过放置一个丑陋的 uint32_t :96;位字段,这将方便地取代三个单词,但它会引起一个警告从 GCC (Bitfield 太大,以适应 uint32 _ t) ,这是相当合法的。

虽然它工作得很好,但是当您希望分发一个包含几百个这样的警告的库时,它并不是很干净..。

我该怎么做?

一开始为什么会有问题?

我正在进行的项目包括定义整个微控制器生产线(意法半导体 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)(其中)。因此,即使结构生成是脚本编写的,我也不愿意在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

可能是一个解决方案... 如果我找到 TheRightFlag。然而,正如在 这根线gcc/cp/class.c中指出的这个悲哀的代码部分:

warning_at (DECL_SOURCE_LOCATION (field), 0,
"width of %qD exceeds its type", field);

这告诉我们没有 -Wxxx标志来移除这个警告..。

6411 次浏览

In the embedded systems arena, you can model hardware either by using a structure or by defining pointers to the register addresses.

Modeling by structure is not recommended because the compiler is allowed to add padding between members for alignment purposes (although many compilers for embedded systems have a pragma for packing the structure).

Example:

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);

You could also use the array notation:

uint16_t status = UART1[UART_STATUS_OFFSET];

If you must use the structure, IMHO, the best method to skip addresses would be to define a member and not access it:

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

In one of our projects we have both constants and structs from different vendors (vendor 1 uses constants while vendor 2 uses structures).

Use multiple adjacent anonymous bitfields. So instead of:

    uint32_t :160;

for example, you'd have:

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

One for each register you want to be anonymous.

If you have large spaces to fill it may be clearer and less error prone to use macros to repeat the single 32 bit space. For example, given:

#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)

Then a 1344 (42 * 32 bit) space can be added thus:

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

How about a C++-ish way?

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;
}

You get autocompletion because of the GPIO namespace, and there is no need for dummy padding. Even, it is more clear what's going on, as you can see the address of each register, you don't have to rely on the compiler's padding behavior at all.

To expand on Clifford's answer, you can always macro out the anonymous bitfields.

So instead of

uint32_t :160;

use

#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

And then use it like

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

Unfortunately, you'll need as many EMPTY_32_X variants as many bytes you have :( Still, it allows you to have single declarations in your struct.

To expand on @Clifford's and @Adam Kotwasinski's answers:

#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;
}

To define a large spacer as groups of 32 bits.

#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)
};

Anti-solution.

DO NOT DO THIS: Mix private and public fields.

Maybe a macro with a counter to generate uniqie variable names will be useful?

#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];
};

geza's right that you really don't want to be using classes for this.

But, if you were to insist, the best way to add an unused member of n bytes' width, is simply to do so:

char unused[n];

If you add an implementation-specific pragma to prevent the addition of arbitrary padding to the class's members, this can work.


For GNU C/C++ (gcc, clang, and others that support the same extensions), one of the valid places to put the attribute is:

#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");

(example on the Godbolt compiler explorer showing offsetof(GPIO, b) = 7 bytes.)

I think it would be beneficial to introduce some more structure; which may, in turn, solve the issue of spacers.

Name the variants

While flat namespaces are nice, the issue is that you end up with a motley collection of fields and no simple way of passing all related fields together. Furthermore, by using anonymous structs in an anonymous union you cannot pass references to the structs themselves, or use them as template parameters.

As a first step, I would, therefore, consider breaking out the 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 ...

And finally, the global header:

// Gpio.h
#pragma once


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


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

Now, I can write a void special_map0(Gpio:: Map0 volatile& map);, as well as get a quick overview of all available architectures at a glance.

Simple Spacers

With the definition split in multiple headers, the headers are individually much more manageable.

Therefore, my initial approach to exactly meet your requirements would be to stick with repeated std::uint32_t:32;. Yes, it adds a few 100s lines to the existing 8k lines, but since each header is individually smaller, it may not be as bad.

If you are willing to consider more exotic solutions, though...

Introducing $.

It is a little-known fact that $ is a viable character for C++ identifiers; it's even a viable starting character (unlike digits).

A $ appearing in the source code would likely raise eyebrows, and $$$$ is definitely going to attract attention during code reviews. This is something that you can easily take advantage of:

#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);
};

You can even put together a simple "lint" as a pre-commit hook or in your CI which looks for $$$$ in the committed C++ code and reject such commits.

Although I agree structs should not be used for MCU I/O port access, original question can be answered this way:

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

You may need to replace __attribute__((packed)) with #pragma pack or similar depending on your compiler syntax.

Mixing private and public members in a struct normally results in that memory layout is no longer guaranteed by C++ standard. However if all non-static members of a struct are private, it is still considered POD / standard layout, and so are structs that embed them.

For some reason gcc produces a warning if a member of an anonymous struct is private so I had to give it a name. Alternatively, wrapping it into yet another anonymous struct also gets rid of the warning (this may be a bug).

Note that spacer member is not itself private, so data can still be accessed this way:

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

However such an expression looks like an obvious hack, and hopefully would not be used without a really good reason, let alone as a mistake.