old_divider = SpiRegs.CONFIG_REG.bit.CLK_DIVIDER;
SpiRegs.CONFIG_REG.bit.CLK_DIVIDER = new_divider;
instead of: old_divider = (SpiRegs.CONFIG_REG & SPI_CONFIG_CLK_DIVIDER_MASK) >> SPI_CONFIG_CLK_DIVIDER_POS;
SpiRegs.CONFIG_REG = (SpiRegs.CONFIG_REG & ~SPI_CONFIG_CLK_DIVIDER_MASK) | (new_divider << SPI_CONFIG_CLK_DIVIDER_POS);
or the slightly nicer but even longer: config = SpiRegs.CONFIG_REG;
old_divider = (config & SPI_CONFIG_CLK_DIVIDER_MASK) >> SPI_CONFIG_CLK_DIVIDER_POS;
config &= ~SPI_CONFIG_CLK_DIVIDER_MASK;
config |= new_divider << SPI_CONFIG_CLK_DIVIDER_POS;
SpiRegs.CONFIG_REG = config;
For anything other than hardware registers, I agree that they're not portable enough to rely on. old_divider = (SpiRegs.CONFIG_REG >> SPI_CONFIG_CLK_DIVIDER_POS) & 1;
Slightly cleaner IMO.Not in my experience. The buggiest part was the preprocessor. You don't hear much about preprocessor bugs anymore because the C standard doesn't dare change it, and in 40 years people have finally got them working right :-/
Personally, I had to scrap and rewrite the C preprocessor 3 times to get it right.
Without bitfields the code would be absolutely filled with bit-access macros decreasing readability and screwing with IDE's indexers and static analyzers big time
Not to mention the pain it would be to refactor/reorder/change fields sizes which is relatively painless with bitfields
> Multiple adjacent bit-fields are usually packed together (although this behavior is implementation-defined)
> The special unnamed bit-field of size zero can be forced to break up padding.
> int b:3; may have the range of values 0..7 or -4..3 in C
> on some platforms, bit-fields are packed left-to-right, on others right-to-left
I wouldn't touch them unless I absolutely had to, and knew I could guarantee compiler and platform.
Now you go ahead and teach GCC to use the arm UBFX instruction for those cases. It DOES use it for actual bitfields. shift + mask = 2-3 instructions (immediate load may be needed). UBFX is one.
Compiler implementors don't like to guess, but don't get a choice. If the instruction provided doesn't match the Standard, which do they implement? Both choices are wrong.
The best "bitpacking" I have ever dealt with is the "Erlang Bit Syntax". I really wish more languages would adopt it.
See: https://www.erlang.org/doc/programming_examples/bit_syntax.h...
1 - https://stackoverflow.com/questions/58493193/ada-how-to-expl...
One time, I was working with an older embedded PPC architecture on a driver to talk to an FPGA that was attached to a 32 bit local bus. The problem is that it didn’t support unaligned accesses. The lower two address bits simply weren’t hooked up, so any access to byte addresses that weren’t a multiple of 4 would just behave as if you masked the two lower address bits off.
There was a structure that used bitfields to access bits in a register on that FPGA. It worked fine til I updated GCC, then it stopped working.
It turns out that the newer version of GCC would do a single byte unaligned read if you were accessing, say, bits 8-15 in a 32 bit bitfield, whereas the older GCC would read the full 32 bit word and shift/mask as needed.
It turns out you can force the older behavior with -fstrict-volatile-bitfields.
Took a minute to figure out, but I learned my lesson. That said, I don’t think you should really be doing IO that way anyway. I typically use IO accessor methods, sometimes with raw addresses, sometimes with struct overlays and taking the address of the member.
... but that's what a bit field is?
A bit field (in C/C++) is a weird object type that can only exist in a structure or union type, which kind of acts like an underlying regular integral type except for those situations where it does not.
For an example of why compilers might have issues compiling bit fields properly (although this requires C++, since C's ternary operator works on rvalues, not lvalues):
struct A { int x: 3; int y: 5 } a;
(choice ? a.x : a.y) = val;
Enjoy making that codegen work properly. struct foo {
char a : 4;
char b : 4;
};
Is a in the high-order 4 bits, or the lower 4 bits? Both choices are allowed, so it's up to the compiler and makes the code non-portable.A result of people avoiding declaring bit fields in serious use cases has been that compiler vendors didn't worry too much about bitfield codegen bugs.
Probably Gcc and Clang are OK on x86, by now. But that does not carry to, e.g., obscure microcontrollers. Heaven help you if your bit field members are supposed to correspond to hardware register sub-fields.
Its not so much that "structure packing" is dead, as much as a wide variety of techniques have been developed above-and-beyond just simply structure packing. There's many ways to skin a cat these days, and packing your structures more intelligently is just one possible data optimization.
If you have some giant bloated struct and you only care about one or two fields at a time, that's one thing. But if you have a well-aligned, correctly packed struct and you're processing all its data, it's total nonsense to break that up.
Also of course AoS has fixed fields, while SoA has dynamic fields.
I figure that's not the primary reason for structure packing, but rather for fine-grained control over writing to very specific memory layouts (think global descriptor table) as structs.
I think it has to do with reading/writing them to disk but honestly never cared enough to ask anyone. Did make things convenient sometimes when you could ‘steal’ a padding value and magically got backwards compatibly because the older versions just ignored that field (and when reading an older file just set it to a sane default).
If that struct is getting written to storage or shoved over the wire though, I'll always optimize/pack them down, both for the size reduction and because it makes it easy to reconstruct on any other CPU.
The Lost Art of C Structure Packing - https://news.ycombinator.com/item?id=15626205 - Nov 2017 (49 comments)
The Lost Art of C Structure Packing (2014) - https://news.ycombinator.com/item?id=12231464 - Aug 2016 (112 comments)
The Lost Art of C Structure Packing - https://news.ycombinator.com/item?id=9517623 - May 2015 (4 comments)
The Lost Art of C Structure Packing - https://news.ycombinator.com/item?id=9069031 - Feb 2015 (113 comments)
The Lost Art of C Structure Packing - https://news.ycombinator.com/item?id=6995568 - Jan 2014 (143 comments)
You are packing small structs in order to squeeze lots of them into the caches. However for large structs, you should at least consider refactoring them into small structs which you can then pack to your heart's content.
…except it didn’t.
The way the library was compiled by default made the structure there smaller than my copy. Took me some time to guess why my data was all garbled, but the cause was pretty simple: there was no padding, even if it meant some members ended up unaligned. I had to replace the unaligned members by char arrays to get it to work (I did not dare explore the compilation options of the library).
And then I found a totally different solution for my problem. Oh well.
Each element node has 6 pointers, so a naive struct would need at least 48 bytes, but you can do it with 8
Pahole is a decent utility to look at what the packing of a structure actually ended up after everything has had its last effect in the compile chain.
Of course it may very different if you work on embedded systems...