94

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

StephenA
  • 61
  • 6
J Faucher
  • 988
  • 6
  • 14
  • 26
    have you consided `char unused[12];` and so on? – M.M Nov 01 '18 at 21:56
  • @M.M Yes I did, but then, the user will have `unused1`, `unused2` and so on on its autocomplete list from any IDE. Which is not good (And he will have a clear access to `unused[1]` for example). My dream would have been to do some `uint8_t [12] ` or `uint8_t[12]:96` stuff but this is not valid. – J Faucher Nov 01 '18 at 22:07
  • 3
    I would just suppress the warning. [\[class.bit\]/1](https://timsong-cpp.github.io/cppwp/class.bit#1) guarantees the behavior of `uint32_t :192;`. – NathanOliver Nov 01 '18 at 22:07
  • 3
    @NathanOliver I would gladly too, but it seems that this warning is not suppressible (Using GCC) or I didn't find how to do so. Moreover, it still is not a clean way to do (but it would be pretty satisfying) . I managed to find the correct "-W" flag but didn't manage to apply it only on my own files (I don't want the user to remove this kind of warnings for his work) – J Faucher Nov 01 '18 at 22:13
  • 3
    BTW you can write `:42*32` instead of `:1344` – M.M Nov 01 '18 at 22:15
  • 1
    Try this to suppress warnings? https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html#Diagnostic-Pragmas – Hitobat Nov 01 '18 at 22:26
  • GCC supports `__int128`, which might help... – jxh Nov 01 '18 at 22:31
  • 1
    You also might be interested in: https://stackoverflow.com/a/25938871/315052 – jxh Nov 01 '18 at 22:33
  • @Hitobat I already found that (but would have a hard time to find it again) but my IDE (or GCC) don't wanna give me the flag to use with this method. (And i can't seems to find it in GCC documentation). I will add as an edit the exact warning message that I get... – J Faucher Nov 01 '18 at 22:48
  • As stated in the last edit, there is no flag to remove the warning, so no pragma to be used... – J Faucher Nov 01 '18 at 23:34
  • With **Edit 1** solution, disable **all** warnings and see if it works. As it is only around a specific structure, you don't have to find **TheRightFlag**. – Phil1970 Nov 01 '18 at 23:51
  • 1
    Given that it is C++, you can easily define a template class that has the desired size and only private members. It will still appears in autocomplete but you won't be able to use it in any way. – Phil1970 Nov 01 '18 at 23:55
  • Do you really need to have a single `struct`? Similar registers are probably close to another anyway. – Phil1970 Nov 01 '18 at 23:57
  • How are defined macros (I guess) like `GPIO_MAP0_MODER`. Not sure if it a good idea to do what you are doing instead of relying on manufacturer header file as you would probably have to adjust your code every time they make a new device. From you example, it seems that all bit fields are multiple of 32 bits… Is it the case for other member of the struct. If so, then inline functions might be all you need. – Phil1970 Nov 02 '18 at 00:10
  • @Phil1970 Hi, I tried to remove all warning, using **Edit 1** with `-Wall` but it didn't worked. (As said in **Edit 2**, I highly doubt that it is possible to even remove this warning... ) Your 2nd comment might be a solution. I don't understand your 3rd comment, sorry. Finally, macros are defined either as `uint32_t :32` if the register is not present for the selected chip or as `MODER_TypeDef` which is a custom struct, mapped onto 32 bits and containing a bitfield with all required field, referring to the chip reference manual (RM). Those descriptions are generated through a script. – J Faucher Nov 02 '18 at 00:16
  • @Phil1970 Comming back about the idea to use a class template. The issue would be to have a (really) huge amount of "`RESERVED`" fields which i'd rather avoid... – J Faucher Nov 02 '18 at 00:22
  • If you generate the definitions of those macros with a script, then you could also generate the structures from a script and it would not be a problem at all to have as many consecutive `uint32_t : 32` as necessary and you could even add a comment to each line to show the computed offset… By my third comment, I was saying that if you don't put all register in the same structure you won't have to care about hole (same if instead of having GPIO_MAP0_MODER as a data member, it would be a function that return an (hardware) address casted to the proper type). – Phil1970 Nov 02 '18 at 01:06
  • @jxh: `__int128` is only supported on targets with 64-bit registers, not on 32-bit targets like 32-bit ARM (STM32). Plus it doesn't help at all for a 192-bit field. – Peter Cordes Nov 02 '18 at 02:07
  • A static array of some type with sizeof that you know. For example int32. – mathreadler Nov 03 '18 at 09:52

10 Answers10

45

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.

geza
  • 28,403
  • 6
  • 61
  • 135
  • Even better any other comments I have made elsewhere. That should worth more than 1 vote. – Phil1970 Nov 02 '18 at 01:11
  • 1
    This might possible optimize less well than a struct for access to multiple MMIO registers from the same function. With a pointer to the base address in a register, the compiler can use load/store instructions with immediate displacements, like `ldr r0, [r4, #16]`, while compilers are more likely to miss that optimization with each address declared separately. GCC will probably load each GPIO address into a separate register. (From a literal pool, although some of them can be represented as rotated immediates in Thumb encoding.) – Peter Cordes Nov 02 '18 at 02:12
  • @PeterCordes: I've just checked this with clang, and it optimizes this case well. I haven't checked GCC, maybe it misses this optimization as you say. – geza Nov 02 '18 at 02:40
  • 5
    Turns out my worries were unfounded; ARM GCC does optimize this way, too. https://godbolt.org/z/ztB7hi. But note that you want `static volatile uint32_t &MAP0_MODER`, not `inline`. An `inline` variable doesn't compile. (`static` avoids having any static storage for the pointer, and `volatile` is exactly what you want for MMIO to avoid dead-store elimination or optimization of write / read-back.) – Peter Cordes Nov 02 '18 at 02:59
  • 1
    @PeterCordes: inline variables is a new C++17 feature. But you're right, `static` does equally well for this case. Thanks for mentioning `volatile`, I'll add it to my answer (and change inline to static, so it works for pre C++17). – geza Nov 02 '18 at 03:07
  • Neat. I was using g++6.3 on Godbolt, which doesn't support `inline` in that context, even with `-std=gnu++17`. But yes, gcc8 and gcc7 do support it just fine, even without `-std=gnu++17`. – Peter Cordes Nov 02 '18 at 03:24
  • 3
    This is not strictly well defined behavior see [this twitter thread](https://twitter.com/myrrlyn/status/940365445957279744) and [maybe this one is useful](https://twitter.com/shafikyaghmour/status/940451354631290880) – Shafik Yaghmour Nov 02 '18 at 06:39
  • @ShafikYaghmour: sure, the cast is implementation defined. But there is no "better" defined solution, as far as I know. It's just an example, I bet OP has a similar cast in their code. – geza Nov 02 '18 at 06:51
  • @geza doesn't volatile stop optimizations? How does it end up making it possible for the compiler to inline? – Krupip Nov 02 '18 at 13:32
  • @geza It's an interesting idea, however, I got several GPIO (in example) like GPIOA, GPIOB and so on. The issue then is that your solution will only point out to a single GPIO so I should get as much namespace as I have GPIO or define a class/struct by GPIO. However, I got member functions on those GPIO (i.e `GPIOA.tooglePin()` ) which will not be usable with your solution. – J Faucher Nov 02 '18 at 13:39
  • 1
    @JFaucher: create as many namespaces as structs you have, and use standalone functions in that namespace. So, you'll have `GPIOA::togglePin()`. – geza Nov 02 '18 at 13:44
  • @opa: yes, it does stop optimisations, that's the intent (if you write multiple times to a memory-mapped register, then every write should happen. Just like, if you read from it multiple times all the reads must happen, as the content might have been changed between the reads). There are no functions around, so I don't know what you mean by inline here. – geza Nov 02 '18 at 13:46
  • @geza okay yeah I forgot about that, so its necesary to do so any way, I'm not sure what I mean by inline either, other people were talking about that up top like peter cordes – Krupip Nov 02 '18 at 13:47
  • @opa: if you mean comments under the answer here, `inline` was present in my answer before. But it was specified for variables, not for functions. So it has nothing to do with optimisation. And I've changed inline to static, because the effect is mostly the same (inline variables is a C++17 feature, but static variables always existed) . – geza Nov 02 '18 at 13:51
  • Just to be clear, you made up the `0x4000` values and the OP will have to use the addresses for his actual hardware? – JPhi1618 Nov 02 '18 at 17:29
  • @JPhi1618: exactly. – geza Nov 02 '18 at 18:00
  • 1
    How do you address the issue of bitfields, especially considering bitfields which span integer boundaries? – Matthieu M. Nov 02 '18 at 20:09
  • @MatthieuM. For this method to work well, you might want to use some custom structure implementing bitfields spaning over a proper 32bits (in example) instead of uint32. – J Faucher Nov 04 '18 at 20:41
  • @geza In my case, that would mean writing 11 times all function I would like to use on GPIO (not talking about 18 times for timers), and in this aspect (and with all due respect) I think using namespaces here is a total nonsense (and lose all "C++ ishness") ... That is why I use classes with bitfields properly implemented on a full 32bit "pack" (on a 32 bits system) which I can "map" (through ugly casts) onto the right address. – J Faucher Nov 04 '18 at 20:44
  • @JFaucher: If you have that much, then yes, you're right. But, to be honest, I consider both your original and my alternative solution bad. I always create wrapper classes for accessing HW. This class has a pointer to the beginning of the MMIO area, and have private enums for indexing HW registers. Externally, all the functionality accessible only with functions. This way, internally, all the register read/writes can be logged. It is a much better solution in my opinion. This wrapper class could have extra functionalities as well, not just a thin layer. Like caching register values, etc. – geza Nov 04 '18 at 20:52
  • @geza Well, my application is something unusual which aim to provide a very low-level interface with a minimum overhead while giving some type safety an so on. This class solution provide 0 overhead in terms of variables (all my functions will obiously take some memory) and the possibility to apply functions direcly onto register, without caching. – J Faucher Nov 04 '18 at 20:58
  • @JFaucher: Sure. If you need, you can easily create a wrapper class which has no overhead. For me, it is much better to write `gpu.setPrimitive(Primitve::Triangle);`, than `gpu->REG_PRIM = 3;`. And even, in debug mode, there could be a lot of extra features: parameter validation, detecting inconsistent states, etc. Anyways, I don't know about your exact situation, maybe this wrapper class cannot be done for some reason (it could happen, when the HW is very difficult to program, in a lot of ways, etc.). – geza Nov 04 '18 at 21:05
  • @geza In my case, the "issue" is that the MCU don't have much ressources (even though such wrapper would work). However, I don't see why using a mapped structure instead of a wrapper likes your, would prevent me from using `gpu.setPrimitive(Primitive::Triangle)` or to have parameter checks, states detection and so on ? (I ask that because this is exactly what I do) – J Faucher Nov 04 '18 at 22:02
  • @JFaucher: based on what you wrote, it seems that you give a raw interface to the user (because you worried that register autocomplete works for them). So, they can easily modify the value of a register: `gpu->REG_PRIM=42;`, even though 42 is an invalid primitive. With a proper wrapper class, this cannot happen. `REG_PRIM` is likely to be accessed in one place, in `setPrimitve`, which validates the values. Or, if `REG_PRIM` modified in several places, then the wrapper class contains a private `setPRIM` function, and this function does the validation. This is a whole different approach. – geza Nov 04 '18 at 22:12
  • @JFaucher: or maybe, you don't want to put `setPrimive` support into the wrapper class. But then, `setPRIM` would be the public function. The point is, it can validate the parameters, and can do logging. Anyways, as I've said, I don't know your exact problem, it's hard to say, how to design properly a low-level interface. – geza Nov 04 '18 at 22:14
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/183095/discussion-between-j-faucher-and-geza). – J Faucher Nov 04 '18 at 22:15
36

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;)
    ...
};
Clifford
  • 88,407
  • 13
  • 85
  • 165
  • Thanks for the answer. I already considered that, however it would add over 200 lines on some of my files (`uint32_t :1344;` is in the place) so I would rather not have to go this way... – J Faucher Nov 01 '18 at 23:11
  • 1
    @JFaucher Added a possible solution to your line count requirement. If you have such requirements, you might mention them in the question to avoid getting answers that do not meet them. – Clifford Nov 02 '18 at 12:30
  • Thanks for the edit and sorry for not stating the line count thing. My point is that my code is already painful to dive in since there is a lot of lines and I'd rather avoid adding too much more. Therefore, I was asking if someone knew a "clean" or "official" way to avoid using adjacent anonymous bitfield (even if that work fine). The macro approach seems fine to me though. By the way, in your example, don't you got a 36*32 bits space ? – J Faucher Nov 02 '18 at 13:15
  • @JFaucher - corrected. I/O register mapping files are necessarily large due to the large number of registers - normally you write once, and maintenance is not an issue because the hardware is a constant. Except by "hiding" registers you are making maintenance work for yourself if you later need to access them. You are aware of course that all STM32 devices already have a register map header provided by the vendor? It would be far less error-prone to use that. – Clifford Nov 02 '18 at 14:13
  • 2
    I agree with you and, to be fair, I think that I will go by one of those two method displayed in your answer. I just wanted to be sure that C++ doesn't provide a better solution before doing so. I am well aware that ST provide those header, however those are built upon the massive use of macros and bitwise operations. My project is to build a C++ equivalent to those headers which will be _less_ error prone (using enum classes, bitfields and so on). That's why we use a script to "translate" the CMSIS headers into our C++ structures (and found some errors in ST files btw) – J Faucher Nov 02 '18 at 14:33
  • I would add the way writing `#define REPEAT_16 REPEAT_2(REPEAT_8)` for the sake of completeness (and because I think it is interesting to underline the fact that `REPEAT_n(REPEAT_x)` provides some x to the power of n behavior, as shown in @mosvy and @jxh answers. – J Faucher Nov 03 '18 at 10:26
  • @JFaucher : You could do that, but in general perhaps it pays not to try to be too clever with macros. The simple binary progression is arguably easier to follow. – Clifford Nov 03 '18 at 14:01
  • @JFaucher if I was you, I would do it in the shortest number of steps possible while keeping things nice (`#define REP7(a) a a a a a a a`, `#define REP42(a) REP7(a a a a a a)`). There's no point in that whole binary expansion unless your purpose is to teach the powers of two to your niece. (btw, writing REPEAT_2(a) twice after having defined REPEAT_2 as something that repeats a thing twice is a bit *quaint*, just think about it ;-) –  Nov 04 '18 at 01:37
20

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

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • Thanks for your answer. However, I choose to use a structure approach to ease the work of the user when he got an auto-complete feature (You only will have the right attributes displayed) and I don't want to "show" the user the reserved slots as pointed out in a comment of my first post. – J Faucher Nov 01 '18 at 23:01
  • You can still have that by making the above address `static` members of a structure assuming that autocomplete is able to show static members. If not, it can be inline member functions too. – Phil1970 Nov 02 '18 at 01:09
  • @JFaucher I'm not an embedded systems person and haven't tested this, but wouldn't the autocomplete issue be resolved by declaring the reserved member private? (You can declare private members in a struct, and you can use `public:` and `private:` as many times as you want, to get the correct ordering of the fields.) – N. Virgo Nov 02 '18 at 06:13
  • 1
    @Nathaniel: Not so; if a class has both `public` and `private` non-static data members, then it's not a [standard layout type](https://en.cppreference.com/w/cpp/language/data_members#Standard_layout), so it doesn't provide the ordering guarantees you're thinking of. (And I'm pretty sure the OP's use-case does require a standard layout type.) – ruakh Nov 02 '18 at 06:42
  • @ruakh though if I'm reading that correctly, you could just make the whole thing private and provide inlined getters and setters for the fields that are part of the public interface. – N. Virgo Nov 02 '18 at 07:29
  • 1
    Don't forget `volatile` on those declarations, BTW, for memory-mapped I/O registers. – Peter Cordes Nov 02 '18 at 09:11
  • @PeterCordes: Compilers used to be smart enough to recognize that programmers wouldn't be using int-to-pointer casts if they weren't trying to do something "unusual", but your point is well taken that since the Standard allows compilers to be obtuse, some "modern" compilers will seek to take maximum advantage of that. – supercat Nov 03 '18 at 17:37
  • @PeterCordes: Incidentally, chip vendor libraries seldom bother including `volatile`. I would regard the ability to handle, without modification, chip vendor libraries that rely upoon the once-universal extension of treating as volatile an indirect access through a freshly-cast integer, as one of the more important traits for a compiler that claims to be suitable for embedded-systems use. Far more important than, e.g., support for variable length arrays. – supercat Nov 03 '18 at 17:45
13

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

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
9

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;
}
  • I have incorporated a variant of your suggestion in my answer following further _requirements_ in a comment. Credit where credit is due. – Clifford Nov 02 '18 at 12:34
7

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.

Adam Kotwasinski
  • 4,377
  • 3
  • 17
  • 40
  • 5
    Using Boost CPP macros, I think you can use recursion to avoid having to hand-create all the necessary macros. – Peter Cordes Nov 02 '18 at 02:15
  • 3
    You can cascade them (up to preprocessor recursion limit, but that's usually ample). So `#define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1` and `#define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1` etc. – Miral Nov 02 '18 at 03:51
  • @PeterCordes perhaps, but the tags indicate that perhaps booth C and C++ compatibility are required. – Clifford Nov 02 '18 at 12:27
  • 2
    C and C++ use the same C preprocessor; I don't see an issue other than maybe making the necessary boost header available for C. They do put the CPP-macro stuff in a separate header. – Peter Cordes Nov 02 '18 at 12:30
1

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)
};
jxh
  • 69,070
  • 8
  • 110
  • 193
1

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.

Farzad Karimi
  • 770
  • 1
  • 12
  • 31
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    Remember that the OP's specific use-case is for describing memory-mapped I/O registers to the compiler. It *never* makes sense to copy the whole struct by value. (And each member like `GPIO_MAP0_MODER` is presumably `volatile`.) Possibly reference or template-parameter usage of the previously-anonymous members could be useful, though. And for the general case of padding structs, sure. But the use-case explains why the OP left them anonymous. – Peter Cordes Nov 02 '18 at 21:21
  • You might use `$$$padding##Index_[N_];` to make the field name more self-explanatory in case it ever came up in autocomplete or when debugging. (Or `zz$$$padding` to make it sort after `GPIO...` names, because the whole point of this exercise according to the OP is nicer autocomplete for memory-mapped I/O location names.) – Peter Cordes Nov 02 '18 at 21:24
  • @PeterCordes: I scanned the answer again to check, and never saw any mention of copying. I did forgot the `volatile` qualifier on the reference, though, which has been corrected. As for naming; I'll let it up to the OP. There are many variations (padding, reserved, ...), and even the "best" prefix for auto-completion may depend on the IDE at hand, though I do appreciate the idea of tweaking the sorting. – Matthieu M. Nov 03 '18 at 11:39
  • I was referring to "*and no simple way of passing all related fields together*", which sounds like struct assignment, and the rest of the sentence about naming the struct members of the union. – Peter Cordes Nov 03 '18 at 11:51
  • 1
    @PeterCordes: I was thinking of passing by reference, as illustrated later down. I find it awkward that the OP's structure prevent them from creating "modules" that can be statically proven to only access a specific architecture (by taking a reference to the specific `struct`) and that the `union` ends up being propagated everywhere even in architecture-specific bits which could care less about the others. – Matthieu M. Nov 03 '18 at 13:21
0

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.

Jack White
  • 896
  • 5
  • 7
  • 1
    Users cannot declare identifiers in any namespace containing double underscores anywhere in the name (in C++, or only at the beginning in C); doing so makes the code ill-formed. These names are reserved to the implementation and therefore can in theory conflict with yours in horribly subtle and capricious ways. Anyway, the compiler has no obligation to retain your code if it contains them. Such names aren't a quick way to get 'internal' names for your own use. – underscore_d Nov 04 '18 at 19:21
  • Thanks, fixed it. – Jack White Nov 05 '18 at 12:25
-1

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];
};
Robert Andrzejuk
  • 5,076
  • 2
  • 22
  • 31
  • 4
    Bad idea; [you just lost the top-to-bottom member ordering](https://stackoverflow.com/a/36149568/560648). – Lightness Races in Orbit Nov 02 '18 at 10:48
  • 3
    Ok. If Nobody minds I'll leave the answer as what not to do. – Robert Andrzejuk Nov 02 '18 at 11:11
  • If it's a bad answer you should delete it. – Nic Nov 02 '18 at 16:52
  • 4
    @NicHartley Considering the number of answer, we are close to a "research" question. In research, knowledge of impasses is still knowledge, it avoids others to take the wrong way. +1 for the bravery. – Oliv Nov 02 '18 at 18:27
  • 1
    @Oliv And I -1'd because the OP required something, this answer violated the requirement, and therefore it's a bad answer. I explicitly did not make any value judgement, positive or negative, on the person, in either comment -- just on the answer. I think we can both agree it's bad. What that says about the person is off-topic for this site. (Though IMO anyone willing to take some time to contribute an idea is doing _something_ right, even if the idea doesn't pan out) – Nic Nov 02 '18 at 18:29
  • @NicHartley I agree this question mark should stay below 0 for the reason you mention. – Oliv Nov 02 '18 at 18:35
  • 2
    Yes it is the wrong answer. But I fear some people may come to the same idea. Because of the comment and link I just learnt something, that's not negative points for me. – Robert Andrzejuk Nov 02 '18 at 19:19