Union of bitfields and larger type on ARM

A vote on this: At least GCC optimizes these to single bit field extract instructions on a Cortex.

Alternative format of the same:

static inline uint8_t integer_high(uint32_t x) { return (uint8_t)(x >> 24); }

This should work correctly even without the cast.

--

-TV
Reply to
Tauno Voipio
Loading thread data ...

Are you sure? Have you checked the assembly output for this code? For non-volatile variables at least, I expect that these inline functions are optimized to the same instructions. I don'T have an ARM compiler handy, but maybe you could check if it also works with "volatile unit32_t x"

If C++ is used, an inline function with a reference parameter is also easily optimized:

static inline void set_integer_low (uint32_t x&, uint8_t y) { x= (x & 0xff00ffff) | ((uint32_t) y

Reply to
Christian Gollwitzer

Yes, I have checked - though not for a wide variety of versions, optimisation flags, etc. The inline functions will result in the same ARM code in the non-volatile case (assuming at least -O1 optimisation). But in the volatile case, the compiler correctly generates different code for the structure access and the manual bitfield extraction.

It is still a mess compared to the bitfield struct access, and it is still different in the volatile case. Yes, the compiler can optimise the ugly, unmaintainable and error-prone source code into good object code - but why not just write clear, simple source code that gets optimised into good object code?

I would expect the compiler to manage this optimisation.

I think the OP should look first and foremost at the /source/ code, and decide from that. The code generation details are up to the compiler - enable optimisation, give it clear and correct source code, and let it do its job. It is only in very rare situations that the generated code should be the guide for a decision like this.

In both C and C++, you have to embed the item in a struct - "typedef" itself does not introduce a new type. Yes, this technique can make the code safer.

But you are going round in circles, writing lots of extra source code, writing access functions with potentially error-prone masks (imagine a case where the bitfields were not as simple, or where they were changed later), with duplicate information in multiple places, and finally resulting in some unnecessarily verbose and ugly usage.

In the end, all you have done is duplicate a feature that C and C++ support as part of the language. It is a pointless exercise.

Reply to
David Brown

I should have mentioned this before, but in the case where your bitfield sizes match the type, you don't need the bitfield specifiers:

typedef union { uint32_t phase_accumulator_full; struct { uint8_t integer_high; uint8_t integer_low; uint8_t frac_high; uint8_t frac_low; }; } phase_accumulator;

(Of course, the byte ordering is still wrong - I just wrote it that way to keep it consistent.)

Reply to
David Brown

Under K&R C the ordering of fields was not guaranteed, but I think this was cleaned up by C99 or possibly somewhat later.

OP should see which language standard their compiler uses and refer to that standard and comment appropriately. (In addition to testing the code...)

Steve

Reply to
Steve Pope

I haven't looked at K&R for an older definition, but C11 certainly says that the order of allocation of bit-fields is implementation-dependent. There are some rules about packing, but there is plenty of scope for weird implementations if the compiler writers so fancy.

As long as his compiler follows the ARM EABI (and all serious embedded ARM compilers will do so), there should be no problem - the EABI gives more details for this sort of thing than any of the C or C++ standards.

(He could make it a little easier by dropping the bitfields - they are not necessary when the struct fields are uint8_t and the bitfields are 8 bits.)

Reply to
David Brown

If it doesn't, fix it. Endianness is more or less undefined behavior in 'C' so you have to test it.

Walk a 1 through phase_accumulator_full and print out ( or just store the result and dump it with a JTAG if you are without a console ).

--
Les Cargill
Reply to
Les Cargill

I don't mean "this is an inappropriate question" but I'd think he could have the answer in 15 minutes or less by trying it.

To wit:

phase_accumulator p;

p.phase_accumulator_full = 0x012345678;

printf("p.bob.integer_high = %x\n",p.bob.integer_high);

etc. etc.

If'n ya ain't got no printfs, then you hopefully know how to deal with that.

--
Les Cargill
Reply to
Les Cargill

I thought I had no printfs, but I discovered the other day that I can serial println to the console via USB on the development board I'm using...

Reply to
bitrex

No, endianness is not remotely "undefined behaviour" - it is /implementation/ defined behaviour. There is a huge difference.

This means that the implementation has to document exactly what endianness is used - and in the case of ARM compilers for embedded systems, this is specified in the ARM EABI to be little endian.

Being implementation-defined behaviour, this also means that you /can/ test it - sometimes that is easier than trying to find the relevant documentation! Once you have tested it, you know exactly how it works for that implementation. And while in theory it could change in later versions of the compiler, making such a change between compiler versions would be astoundingly stupid and unhelpful - especially in an embedded compiler - so it is not going to happen. (I know of only one case where this happened - and that was on MS's x86 compiler, a long time ago, where they changed the bitfield endianness.)

If endianness (or bitfield endianness) really were undefined behaviour, you could not rely on it being the same between two runs of the same compiler on the same source code - testing would be useless.

Reply to
David Brown

We have software that is built for both big-endian and little-endian hosts (and more interestingly, it must internally simulate both big-endian and little-endian processors).

We have this in one of the project header files.

#include #ifndef __BYTE_ORDER #if defined(__BIG_ENDIAN) && !defined(__LITTLE_ENDIAN) #define __BYTE_ORDER __BIG_ENDIAN #elif !defined(__BIG_ENDIAN) && defined(__LITTLE_ENDIAN) #define __BYTE_ORDER __LITTLE_ENDIAN #define __BIG_ENDIAN 4321 #elif !defined(__BIG_ENDIAN) && !defined(__LITTLE_ENDIAN) #define __BIG_ENDIAN 4321 #define __BYTE_ORDER __BIG_ENDIAN #else #error Unable to determine Endian mode #endif #endif

#if !defined(htobe16) # if __BYTE_ORDER == __LITTLE_ENDIAN # define htobe16(x) __bswap_16 (x) # define htole16(x) (x) # define be16toh(x) __bswap_16 (x) # define le16toh(x) (x)

# define htobe32(x) __bswap_32 (x) # define htole32(x) (x) # define be32toh(x) __bswap_32 (x) # define le32toh(x) (x)

# define htobe64(x) __bswap_64 (x) # define htole64(x) (x) # define be64toh(x) __bswap_64 (x) # define le64toh(x) (x) # else # define htobe16(x) (x) # define htole16(x) __bswap_16 (x) # define be16toh(x) (x) # define le16toh(x) __bswap_16 (x)

# define htobe32(x) (x) # define htole32(x) __bswap_32 (x) # define be32toh(x) (x) # define le32toh(x) __bswap_32 (x)

# define htobe64(x) (x) # define htole64(x) __bswap_64 (x) # define be64toh(x) (x) # define le64toh(x) __bswap_64 (x) # endif #endif

Then use hto{bl}eXX and {bl}e64tohXX in the code without the need for local ifdefs.

Reply to
Scott Lurndal

Yes, that looks like a reasonable solution if you need to handle different endiannesses. Another possibility will come with gcc 6, which supports attributes for structs with specific endiannesses (and I know at least one compiler that has had that feature for perhaps a couple of decades).

But if your code does not have to be portable - such as if you are modelling a hardware register on a particular device - you can just use the one fixed layout.

Reply to
David Brown

Ah, there you go. Right! I'd forgotten the term of art.

--
Les Cargill
Reply to
Les Cargill

It happened to me on an 8051 C compiler in the '90s. The endianness of bitfields changed between one compiler version and the next. I learned a lesson, and have not used bitfields in C since then.

I believe the compiler vendor is still in business today (well, they were bought by a microcontroller manufacturer).

Regards, Allan

Reply to
Allan Herriman

Thanks - now I know of /two/ cases where compilers have changed bit ordering!

You are throwing out the baby with the bathwater. If you avoid using a feature just because a small-time compiler writer for a brain-dead and C-unfriendly processor made an inconvenient decision two decades ago, you would have to give up programming altogether. Bitfields have portability issues, but can be extremely useful - they are perfectly safe to use once you understand them.

Reply to
David Brown

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.