How do I create a "spacer" in a C++ class memory structure?

C++CMemory ManagementLow LevelBare Metal

C++ Problem Overview


The issue

In a low level bare-metal embedded context, I would like to create a blank space in the memory, within a C++ structure and without any name, to forbid the user to access such memory location.

Right now, I have achieved it by putting an ugly uint32_t :96; bitfield which will conveniently take the place of three words, but it will raise a warning from GCC (Bitfield too large to fit in uint32_t), which is pretty legitimate.

While it works fine, it is not very clean when you want to distribute a library with several hundreds of those warnings...

How do I do that properly?

Why is there an issue in the first place?

The project I'm working on consists of defining the memory structure of different peripherals of a whole microcontroller line (STMicroelectronics STM32). To do so, the result is a class which contains a union of several structures which define all registers, depending on the targeted microcontroller.

One simple example for a pretty simple peripheral is the following: a General Purpose Input/Output (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;
    };
};

Where all GPIO_MAPx_YYY is a macro, defined either as uint32_t :32 or the register type (a dedicated structure).

Here you see the uint32_t :192; which works well, but it triggers a warning.

What I've considered so far:

I might have replaced it by several uint32_t :32; (6 here), but I have some extreme cases where I have uint32_t :1344; (42) (among others). So I would rather not add about one hundred lines on top of 8k others, even though the structure generation is scripted.

The exact warning message is something like: width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type (I just love how shady it is).

I would rather not solve this by simply removing the warning, but the use of

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-WTheRightFlag"
/* My code */
#pragma GCC diagnostic pop

may be a solution... if I find TheRightFlag. However, as pointed out in this thread, gcc/cp/class.c with this sad code part:

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

Which tells us that there is no -Wxxx flag to remove this warning...

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

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

Solution 3 - C++

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

Solution 4 - C++

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

Solution 5 - C++

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

Solution 6 - C++

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.

Solution 7 - C++

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

Solution 8 - C++

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.

Solution 9 - C++

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.

Solution 10 - C++

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJ FaucherView Question on Stackoverflow
Solution 1 - C++gezaView Answer on Stackoverflow
Solution 2 - C++CliffordView Answer on Stackoverflow
Solution 3 - C++Thomas MatthewsView Answer on Stackoverflow
Solution 4 - C++Lightness Races in OrbitView Answer on Stackoverflow
Solution 5 - C++mosvyView Answer on Stackoverflow
Solution 6 - C++Adam KotwasinskiView Answer on Stackoverflow
Solution 7 - C++jxhView Answer on Stackoverflow
Solution 8 - C++Matthieu M.View Answer on Stackoverflow
Solution 9 - C++Jack WhiteView Answer on Stackoverflow
Solution 10 - C++Robert AndrzejukView Answer on Stackoverflow