14 ms·
Embedded C: Struct and Union (Part 2)
- Nursie 8y agoInteresting stuff. I have been a C coder (I'm not so much any more) for about 12 years of my career, and never came across bitfields in all that time, even when doing embedded work. One style question - why not use the stdint.h types all the way through?
- makmanalp 8y ago> I have been a C coder (I'm not so much any more) for about 12 years of my career, and never came across bitfields in all that time, even when doing embedded work These days it shows up in databases stuff, either because you have e.g. one of these flags per row in your database (or imagine keeping track of used/unused fields in an in-memory block of data), which might add up to millions of booleans, and packing them by a ratio of 64 is a big improvement in space and also time it takes to process that data because the denser the data is, the more of it you can read per second and the more of it can fit in CPU caches. Also you can then use SIMD instructions to process it quicker too, etc etc.
- keithnz 8y agoweird.... most embedded platforms do something like ( following from an actual lib header, which is full of the same ). The entire interface to the hardware is basically bitfields. typedef union { struct { unsigned REC :8; }; struct { unsigned REC0 :1; unsigned REC1 :1; unsigned REC2 :1; unsigned REC3 :1; unsigned REC4 :1; unsigned REC5 :1; unsigned REC6 :1; unsigned REC7 :1; }; } RXERRCNTbits_t;
- eecsninja 8y agoDid you forget to include field width specifiers?
- jschwartzi 8y agoI've seen this a lot in Texas Instruments' bare-metal BSPs for their processors that are generated using HalCoGen. Their own proprietary compiler handles them as you'd expect. Other BSPs typically follow the coding style for their operating system. I've never seen QNX BSP drivers use bitfields. I've also never seen them in Linux drivers. QNX and Linux typically use variants of GCC to compile their drivers unless you pay for another toolchain.
- khedoros1 8y agoI've been a C++ programmer for almost as long, with some work in C. I discovered bitfields while writing emulators, in particular, building representations of registers. I'd suspect that if you haven't run into them in embedded work, that maybe they used macros and bitmasking instead.
- Nursie 8y agoI believe you are correct. Lots of that.
- jandrese 8y agoOne place you can come across them is in IP header processing. Very first byte of an IPv4 header is defined as: struct ip { #if BYTE_ORDER == LITTLE_ENDIAN u_char ip_hl:4, /* header length */ ip_v:4; /* version */ #endif #if BYTE_ORDER == BIG_ENDIAN u_char ip_v:4, /* version */ ip_hl:4; /* header length */ #endif As you can already see, they're kind of a pain to work with, so most people avoid them when possible.
- etqwzutewzu 8y agoThe only issue with C bit-fields is as KR says it: "Almost everything about fields is implementation-dependent" (bit order, endianness, padding size, etc.)
- bfrog 8y agoisn't the ordering of bitfields undefined, therefore useless when trying to map out register bitflags? I just end up creating configuration structs and mapping those to/from register values in some get/set functions as needed
- ChuckMcM 8y agoNo, the ordering of bitfields is implementation defined rather than undefined. So if you figure out a bitfield pattern that works on your compiler for your architecture it will continue to work for you. It is not very portable but for embedded applications that often isn't a problem.
- keithnz 8y agoalso, given this is "Embedded" C, the compilers often provide libs to target micros with bitfields, so it has a known definition.
- tzahola 8y ago>So if you figure out a bitfield pattern that works on your compiler More like: if your compiler is documented to implement bitfields a certain way.
- geofft 8y agoIt is also not portable to new versions of the same compiler, or possibly different flags passed to the same compiler and version.
- nategri 8y agoLine 20 in Application #1 has an unfortunate typo.
- chocolate_ 8y ago:D Also, I think the comments are off in lines 5-6 of Application #2: "0 .. 1" should be "1 .. 2", "2 .. 7" should be "3 .. 7"
- YTusername 8y agoOh my oh! It's my bad. It's fixed now ;) This was distracted for sure!
- barbegal 8y agoI'm not sure this guy really knows what he is talking about. Bit fields are almost universally hated and not allowed in style guides. The Arm guide he links to states: > Please note that peripheral locations should not be accessed using __packed structs (where unaligned members are allowed and there is no internal padding), or using C bitfields. Using bit fields for protocols is even more ill advised because often you may wish to implement the same code across different devices using the same protocol but the compilers will arrange the bit fields differently. The only valid example he gives is the last one which is the correct way to do it: a struct made up of 32 bit values. This makes sure that everything is 4 byte aligned and the processor can access those variables in a single instruction. If you wish to use the dense packing of bit fields but without the compiler ambiguity then you should use explicit bit shifting and bit masking (again shown in his last example)
- ausjke 8y agoAgree. For Embedded coding suggestions you have to know what you are talking about really well otherwise it could be just misleading. Bit fields should be avoided even for resource restricted MCUs, period.
- kevin_thibedeau 8y agoStyle guides are universally written for systems programming where RAM is in abundance. For embedded, memory usage matters and packing data in is a thing. It is ill advised because of the portability traps but portability is low on the priority chain if you just have to make it work. Any decent compiler will do what you want if you pad out the unused bits with a reserved field to guarantee alignment. Reminds me of the C# jockey who complained endlessly about the bitfields (using manual masking) I put into an IoT data structure that was supposed to be kept as small as possible to minimize hosting costs. He had no clue about how to use bitwise operations to extract bits. They managed to bloat that into a horrendous monstrosity but I wasn't going to play their game. If they had their way, a half dozen bits packed into a 32-bit word would have consumed 20x more space with unnecessary schema boilerplate.
- tzahola 8y ago
- ChuckMcM 8y agoGenerally ST Micro did some fairly abhorrent stuff around mapping their registers into bit field packed structures for the Cortex M line. One of the reasons I don't use their library for coding them. The authors use of looking at lines of assembly language code as a metric of goodness for the choices was also problematic. If they coded that stuff for a VAX they would be treated to variable length bit field addressing (see [1] below, page 8-6). Easy to implement very complex bit manipulation in just a few assembly language statements :-). A better measure is CPU clocks + Memory Accesses (typically measured in kilocoreseconds [another VMS joke]) [1] http://www.ece.lsu.edu/ee4720/doc/vax.pdf http://www.ece.lsu.edu/ee4720/doc/vax.pdf
- barbegal 8y agoMapping registers into bit field packed structures is actually okay because most ARM Cortex M processors have bit banding enabled on all the peripherals. Unfortunately not all peripheral libraries use that functionality and GCC doesn't support it well.
- evancox100 8y agoYa,but the newest Cortex-M chips (M7, M22, and M33) do not even support bit-banding, so I wouldn't rely on that being around forever. Not quite sure why ARM removed it. Maybe there were performance costs to all instructions due to the extra hw/decode, while bit-banding only helped a small number of instructions. If anyone has more info, would love to hear!
- joshumax 8y agoI remember a discussion on the Linux Kernel Mailing list regarding memory corruption on the ia64 architecture[1] because of a strange way GCC handled bit-fields. While oftentimes packed bit-fields are a decent way of representing/serializing data for memory-constrained devices or even network applications, it's always a good reminder how they can wreak havoc for scenarios where portability is important. 1: https://lwn.net/Articles/478657/ https://lwn.net/Articles/478657/
- pritambaral 8y agoWasn't that bug triggered in the case of adjacent non-bitfield 32-bit fields too?
- 8_hours_ago 8y agoThis article has some issues, the first struct in Example #1 is only compiled by GCC with a size of 2 if it has the packed attribute. Other compilers may behave differently which is exactly why bit-fields should be avoided. The behavior of bit-fields even changes between compiler versions, for example: """Packed bit-fields of type char were not properly bit-packed on many targets prior to GCC 4.4. On these targets, the fix in GCC 4.4 causes an ABI change. For example there is no longer a 4-bit padding between field a and b in this structure: struct foo { char a:4; char b:8; } __attribute__ ((packed)); There is a new warning to help identify fields that are affected: foo.c:5: note: Offset of packed bit-field 'b' has changed in GCC 4.4 """ (https://gcc.gnu.org/gcc-4.4/changes.html https://gcc.gnu.org/gcc-4.4/changes.html) Also, the code in Application #3 isn't using bit-fields at all (the definition of GPIO_P_TypeDef is here: https://github.com/SiliconLabs/Gecko_SDK/blob/master/platform/Device/SiliconLabs/EFM32G/Include/efm32g_gpio_p.h https://github.com/SiliconLabs/Gecko_SDK/blob/master/platfor...)
- kbumsik 8y agoNo. Please. Struct bitfields may be useful to save memory (e.g. 1 bit for boolean-like variable) but don't rely on it when accessing register data or implenting protocols. The biggest issue with struct bitfields is that its implementation is ABI-specific, meaning it is not even compiler-specific. For example, his "Application #2: Implementing Protocols" example is not portable across archtectures because the order of bits are swapped when compiling for a Big-endian CPU. We should use bitwise operations for this purpose. Look at his last example, "Application #3: Access to MCU Registers". The SDK source code he mentioned does not use bitfield but bitwise operations to access register bits.
- kazinator 8y ago"ABI-specific" should be broader, more portable than "compiler specific"; different compilers must implement the same ABI on a given system (or else not interoperate). Of course bitfields are endian-specific, but two different compilers should agree on them for a given endian.
- eecsninja 8y agoBitfields are too useful of a feature not to use. The solution is simple: add a unit test to verify that setting one bitfield to all 1's will result in the overall 32-bit or 64-bit word having the expected value. Add a comment explaining this too. That will catch nonportable use in the future.
- AnimalMuppet 8y agoBut for embedded software, ABI-specific isn't (usually) an issue. I have never seen an embedded project that changed either architectures ore compilers in the middle. In embedded, once you start the project, you're almost always using the same tooling until it dies. (Even moving to the latest rev of a compiler is rarely done, in my experience.) So you can use bitfields, however they have to be defined to work with your compiler/architecture/hardware, and expect that they're going to continue to work for the duration. (Whether bitfields are a bad idea for other reasons is outside the scope of my comment.)
- mikeash 8y agoABI-specific means you can count on the behavior as long as you know the target hardware and OS. If you’re twiddling registers, it’s highly likely you know the target.
- aetherspawn 8y agoMany people talking about portability of but fields, but; The assumed behaviour of bit fields is well defined between gcc and clang with the proper pragmas. It is therefore probably a non-issue. You even get better aliasing optimisations* using union/bit fields over just using bit shifts. *) this means, if your union contains a way to read the whole field as ie uint_32t and also to read each field individually and also write struct as u32 via pointer, compiler can properly detect which actions cause read/write invalidation of each other instead of having to assume all volatile-like. Use case 1: https://gist.github.com/donkeybonks/8749545 https://gist.github.com/donkeybonks/8749545 Use case 2 (with asm showing better codegen with union): https://gist.github.com/donkeybonks/11103152 https://gist.github.com/donkeybonks/11103152 nb these gists are ~4 years old because I don’t much care about micro-optimisation anymore. nb2 it’s a really good idea to static assert the size of all your structs below their declaration for confidence and splash a few unit tests for ordering.
- kbumsik 8y ago> The assumed behaviour of bit fields is well defined between gcc and clang with the proper pragmas. From the GCC documentation[1], it says: > Determined by ABI. This means that it may be different when compiling for different architectures. How you can say it well-defined? Here is a good example [2] on the compability issue when using bit fields [1]: https://gcc.gnu.org/onlinedocs/gcc/Structures-unions-enumerations-and-bit-fields-implementation.html https://gcc.gnu.org/onlinedocs/gcc/Structures-unions-enumera... [2]: http://mjfrazer.org/mjfrazer/bitfields/ http://mjfrazer.org/mjfrazer/bitfields/
- jcelerier 8y ago> This means that it may be different when compiling for different architectures. How you can say it well-defined? In an immense majority of case you won't be serializing or exchanging your bitfields over the network ; this is entirely a non-problem.
- aetherspawn 8y agoYeah, this is a case where it matters. But you can also see the workaround is pretty straightforward here.
- kazinator 8y agoIn TXR Lisp's FFI, I developed detailed support for bitfields, alignment and packing. You can comply with any kind of struct type no matter how it's declared. In the reference manual, I added a section on what I believe are the bitfield allocation rules used by GCC, as I empirically reverse engineered them. https://www.nongnu.org/txr/txr-manpage.html#N-027D075C https://www.nongnu.org/txr/txr-manpage.html#N-027D075C Hope someone finds it useful. Endian considerations are not covered in that section because that topic is documented elsewhere in the document. In a nutshell, on big endian, bitfields are packed from the most significant end of the storage word, and on little endian, from the least significant end of the storage word. Thus the lowest addressed byte of the storage unit is filled first, and so on.
- eecsninja 8y agoAn example of how not to use bitfields: https://lwn.net/Articles/478657/ https://lwn.net/Articles/478657/ I am not sure whether the underlying spinlock_t struct is something in memory or mapped to a hardware register. But it seems like this was being used to save space rather than to access hardware.
- booblik 8y agoPretty sure nipples suppose to be nibbles in the code
- YTusername 8y agoIt's fixed now ;)