So the 8 I expect to see. A double on the R4 is 8 bytes. The 1 makes sense for an enum with only two options. I was expecting 4 since that's the size of an int but ok.
But then the 56. I have 6 doubles and a byte, so that should be 6*8 +1 = 49 bytes.
Is this an alignment thing where it's allotting 8 bytes for the direction variable even though it only needs one so that the struct stays aligned nicely? Can I rely on that size not changing even if I create more instances?
I would certainly expect a double would have to be aligned on either a 32-bit word or 64-bit word boundary. But that will be processor and compiler dependant. You can test this by creating another data object BEFORE the struct, to shift its address, and see if the size changes.
An enum, I think, does not have a defined size, as it is, effectively, a compile-time constant. So, a good compiler will use the smallest data type capable of holding the maximum enum value, in this case a byte.
I will be copying a few of these struct to and from EEPROM using put and get and I want to be sure they're all the same size when I lay out my addresses and offsets. So I know that they will be where I expect them to be when I come back for them later.
I just want to make sure that it's not something that is going to mysteriously change on me one day because I made a change elsewhere in the code and the compiler decides to do something different and suddenly my EEPROM images don't fit back into the struct right.
It's something that works but I can't 100% explain why it works and those are the things that scare me the most.
The compiler is not allowed to reorder the fields. You can potentially use that to your advantage to save some space. For example, putting two enum together in your definition allows the compiler to place them together in the layout. In your case, enum is byte-sized so they can be byte-aligned. Your example just has the one enum so this is only helpful for PID_Settings if you add another field that's 1, 2, or 4 bytes in size.
I cannot find any references one way or the other but I suspect the optimization level could affect layout.
As mentioned above, "packing" can make the structure smaller. Were I in your shoes I'd have two versions. One that's stored in EEPROM and the one you've shown us that's used by the running program. The EEPROM one would be packed to save space and include a revision number so the future me can fairly easily alter the layout. You'd need a bit of code to pack and unpack (copy the fields).
Indeed. I put a header value (that can change with version I suppose) and a simple checksum with it when I put it into EEPROM.
I'm doing like this. I'm not sure how to take out the dead bytes here without copying fields one by one. a few empty bytes aren't going to hurt me I don't think. And like you said it leaves room in case I want to add something later without changing my layout.
I'd be tempted to embed anything that might 'vary' in between the doubles, singly, though it's wasteful. May still not be foolproof, but if the compiler is busy aligning the doubles, chances are your size won't change. But hey, no compiler expert here.
Interesting question.
Does the same problem arise if we mix uint8_t, uint16_t and uint32_t variables in a struct? Will every compiler/version generate the same size of structure, or are those constructs still "at the compiler's discretion", and our EEPROM reads will be victim if we change? And, will the answer be different if we add 'packed' to our struct declarations?
you could have alignment decisions made based on the architecture. On a 8 bit UNO for example , you would not get any padding, but on a 32 bit ESP32 or MKR, the compiler might decide to align some members on a 32 bit boundary.
GCC offers the option to set some __attribute__ for the struct so you could do something like
Yup. That's what you'd have to do. I'm a bit paranoid so that motivates what I'd do. It's probably not be worth the effort.
I suggest using a CRC-32 instead of a one-byte sum. The CRC is guaranteed to fail on single bit errors and runs. It's my understanding those are the common EEPROM failures.
Exactly.
Compiler / version / platform / optimization level ... yes. The optimization level may not matter. The compiler definitely matters. The version doesn't matter very often.
With time, that becomes less of a problem. There are strong forces pushing towards specific struct layouts. But, if the compiler vendor does not provide a written guarantee, you cannot assume it's true.
Yes. Packing forces the compiler's hand. The tradeoff with using a packed structure is performance. The double in @Delta_G's structure are eight-byte aligned because that makes the processor go fast.
This conversation has moved along since I last read it. Has there been a definitive answer as to why the struct in @Delta_G's initial post reported a sizeof() of 56 bytes? I would have expected a maximum of 6 * 8 + 4 = 52, allowing 4 bytes for the enum.