Amount of storage for floating point numbers

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);
}

Here’s the output from the program:

looping
dummy2 = 21
dummy3 = 22
dummy4 = 23
gopher.fred.dummy3 = 22
0=9
1=0
2=0
3=0
4=31
5=0
6=0
7=0
8=0
9=0
10=0
11=0
12=41
13=0
14=21
15=22
16=23
17=24
18=0
19=0

Hi @ctmorrison

I have a couple of comments:

  • 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.
2 Likes

@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! :wink: 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.

1 Like

@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.

1 Like

What @ScruffR is saying is:

  • 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.

It is a classic speed versus memory trade-off.

2 Likes

@ScruffR and @bko, Thanks for the clarification. You Elites are amazing!

1 Like