8

See online example: Ideone example

struct {
  union {
    struct {
      uint32_t messageID : 26;
      uint8_t priority : 3; 
    } __attribute__ ((packed));
    uint32_t rawID : 29;
  } __attribute__ ((packed));
  uint8_t canFlags : 3;
} __attribute__ ((packed)) idSpecial;

Why would the compiler report the size of the struct as 5 bytes instead of 4 here? It should contain 32 bits.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
c0dehunter
  • 6,412
  • 16
  • 77
  • 139
  • possible duplicate of [Structures and Unions in C, determining size and accessing members](http://stackoverflow.com/questions/3380118/structures-and-unions-in-c-determining-size-and-accessing-members) – David Titarenco Apr 01 '15 at 07:19
  • @DavidTitarenco: I don't think that particular post is good for bitfields. It wouldn't surprise me if this has been answered somewhere else tho'. – Mats Petersson Apr 01 '15 at 07:22
  • 4
    Responders should pay note that the OP is using the [non-standard `__attribute__((packed))`](https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html) GCC extension to avoid padding. So, saying that the compiler is allowed to insert padding anyway is incorrect - when `__attribute__((packed))` is used, GCC will organize the structure to avoid padding (making access more expensive on platforms that do not support unaligned reads). – user4815162342 Apr 01 '15 at 07:31
  • Short answer: structs and unions have to be a multiple of one byte, even anonymous ones. – user253751 Apr 01 '15 at 07:49

4 Answers4

6

The problem is that __attribute__((packed)) doesn't perform bitwise packing. It just guarantees that there is no padding between struct members. You can try this simpler example, where size is also reported as 5:

typedef struct structTag {
    struct {
      uint32_t messageID : 26;
      uint8_t priority : 3;
    } __attribute__ ((packed));
    uint8_t canFlags : 3;
} __attribute__ ((packed)) idSpecial;

Bitwise packing is only possible for bitfield members. You will need to redesign your struct to be a union of a struct with bitfields messageID/priority/canFlags, and a struct with bitfields rowID/canFlags. In other words, you will need to either have some duplication or resort to accessor macros or member functions.

user4815162342
  • 141,790
  • 18
  • 296
  • 355
5

It's because of memory alignment: The compiler won't start canFlags in the middle of a byte, it'll start it at the beginning of the next byte (probably*). So you have four bytes for your initial union, and a byte for canFlags.

If, for instance, you moved canFlags into the union, it would (probably*) have size 4:

typedef struct structTag {
  union {
    struct {
      uint32_t messageID : 26; /* 26bit message id, 67108864 ids */
      uint8_t priority : 3; /* priority: MUST BE 0 */
    } __attribute__ ((packed));
    uint32_t rawID : 29;
    uint8_t canFlags : 3; /* <==== Moved */
  } __attribute__ ((packed));
} __attribute__ ((packed)) idSpecial;

Updated example on ideone. Obviously, that specific change probably isn't what you want; I'm just demonstrating that the issue is starting a new field not on a byte boundary.


* "probably" because ultimately it's up to the compiler.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • canFlags next to rawid makes them union - which is not what the OP wanted, I think. – Mats Petersson Apr 01 '15 at 07:32
  • @MatsPetersson: Right, the OP's going to have to redesign, I was just pointing out why they're getting 5 instead of 4 for their current design. – T.J. Crowder Apr 01 '15 at 07:32
  • 1
    Using your suggestion, we wrapped `rawID` and `canFlags` in a structure and added dummy padding of 3 bits at the end of the first struct inside the union and now the whole structure is 4 bytes big as expected. – c0dehunter Apr 01 '15 at 07:47
1

Data is arranged and accessed in computer memory using Data structure alignment. Which has two related issues

  1. Alignment
  2. Padding

When a write operation is performed by computer, It writes usually in multiple of 4 bytes(for 32 bit systems). One reason for this act is the goal to increase performance. So when you are writing any data structure, that has first 1 byte variable and then 4 bytes variable data, it will do padding after the first 1 byte data to align it on 32 bit boundaries.

struct {
  union {
    struct {
      uint32_t messageID : 26;
      uint8_t priority : 3; 
    } __attribute__ ((packed));
    uint32_t rawID : 29;
  } __attribute__ ((packed));
  uint8_t canFlags : 3;
} __attribute__ ((packed)) idSpecial;

Now in above data structure you are using __attribute__ ((packed)) which means no padding. So uint32_t is of 4 bytes, but you are saing it have 26 bits and 3 bits for priority. Now as you have both variables in one structure so it will reserve 32 bits instead of 29 so that your first structure's information is alligned on boundaries.

Now for canFlags It will need another bytes. So that makes 5 bytes instead of 4.

Yasir Majeed
  • 739
  • 3
  • 12
0

In some compilers, to "merge" the bits, all items must be of the same type. So make it uint32_t where you now have uint8_t - this appears to not be the case in the compiler IdeOne uses tho'

[No matter what, It is still up to the compiler how it merges the bits, so it the only way to absolutely guarantee that your data is stored as 32 bits is to use a single uint32_t and declare a class that does the relevant shifting and anding/oring to manipulate the value - the only guarantee you have is that ONE element in your struct will have at least as many bits as you have asked for]

As others have pointed out, you can't start a new structure on other than a byte boundary. I fixed it by having a second struct inside the union, like this: http://ideone.com/Mr1gjD

#include <stdint.h>
#include <stdio.h>

typedef struct structTag {
  union {
    struct {
      uint32_t messageID : 26; /* 26bit message id, 67108864 ids */
      uint8_t priority : 3; /* priority: MUST BE 0 */
    } __attribute__ ((packed));
    struct {
      uint32_t rawID : 29;
      uint8_t canFlags : 3;
    };
  } __attribute__ ((packed));
} __attribute__ ((packed)) idSpecial;

int main() {
    printf("size: %d", sizeof(idSpecial));
    return 0;
}
Mats Petersson
  • 126,704
  • 14
  • 140
  • 227
  • @T.J.Crowder: Agreed. Although I have seen cases where this matters for some reason - I think it may be MS compilers that have that issue. – Mats Petersson Apr 01 '15 at 07:42