I’m using a union to be able to access individual bytes in a structure. What I’m seeing is an apparent 6 bytes being taken by a floating point number instead of 4 as specified in the documentation. Am I missing something or does a floating point actually take 6 bytes? I didn’t stuff anything in to the float as I realized it would be tough to interpret in this crude fashion.
Here’s the program (yes, it’s a heck of a hack):
struct MyObject // 16 bytes total
{
uint32_t sec; // 4 bytes
uint16_t recno; // 2 bytes
float vbat; // 4 bytes
uint16_t wlev; // 2 bytes
uint8_t dummy2; // 1 byte
uint8_t dummy3; // 1 byte
uint8_t dummy4; // 1 byte
uint8_t fault; // 1 byte
};
union {
MyObject fred;
byte individualBytes[16];
} gopher;
int martha = 0;
// setup() runs once, when the device is first turned on.
void setup() {
// Put initialization like pinMode and begin functions here.
Serial.begin(9600);
gopher.fred.sec = 9;
gopher.fred.recno = 31;
gopher.fred.wlev = 41;
gopher.fred.dummy2 = 21;
gopher.fred.dummy3 = 22;
gopher.fred.dummy4 = 23;
gopher.fred.fault = 24;
}
// loop() runs over and over again, as quickly as it can execute.
void loop() {
// The core of your code will likely live here.
Serial.println("looping");
Serial.print("dummy2 = ");Serial.println(gopher.individualBytes[14]);
Serial.print("dummy3 = ");Serial.println(gopher.individualBytes[15]);
Serial.print("dummy4 = ");Serial.println(gopher.individualBytes[16]);
Serial.print("gopher.fred.dummy3 = ");Serial.println(gopher.fred.dummy3);
for (int i = 0;i<20;i++) {
Serial.print(i);Serial.print("=");Serial.println(gopher.individualBytes[i]);
}
delay(5000);
}
The individualBytes should be addressed from 0 to 15 for 16 bytes, so looking at location 16 is out of bounds.
The reason you are seeing extra bytes is that 32-bit words need to be aligned to four byte boundaries on ARM. Your uint16_t recno must be padded out to 32-bits so that the float vbat can start on the right boundary.
If you reorder your struct so that all the 32-bits things come first, then the 16-bit things, then the 8-bit things, it work more like the you are expecting.
@bko, thank you very much! Yes, I realized I was going beyond the array, but since this simply a test program, I wasn’t worried. Again, thanks for clearing up the confusion about the boundaries! Coincidentally, when I changed the float to a 32 bit integer, I was seeing the same thing and then was even more confused! Your answer cleared up all that fog!
To slightly extend on @bko’s answer:
If for any reason you need to have your struct unpadded (e.g. for compatibility reasons) you can also use __attribute__((packed, aligned(X))) (where X can be any power of 2 including 2^0 = 1 for no padding - which would be the same as just (packed)) or have a your struct share the memory with a uint8_t[] of fitting size as UNION.
But be aware that this will slow down access to unaligned data since it needs to be realligned when transfered to and from registers.
@ScruffR, I don’t understand your UNION reference. Would the uint8_t type behave differently than the byte that I used? In other words, would that type force packed?
Nope, they would be just the same. But, you already have the struct with the padding in it and then make it a union with an array.
To work as intended the struct and the array would need to be declared together at the same instance.
By default the compiler tries to speed up memory access by aligning everything to boundaries. This uses more memory for padding, but is faster since a 32-bit float can be read with one instruction.
But the compiler can instead compact memory if you tell it to with an attribute. This saves memory but means that every time you read the 32-bit float from memory it takes several instructions since it has to read it as bytes one at a time and then assemble them into a 32-bit float.