Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Packing unions/structure to avoid padding

Tags:

c++

padding

I have a structure that looks like this:

struct vdata {
  static_assert(sizeof(uint8_t *) == 8L, "size of pointer must be 8");
  union union_data {
    uint8_t * A; // 8 bytes
    uint8_t B[12]; // 12 bytes
  } u;
  int16_t C; // 2 bytes
  int16_t D; // 2 bytes
};

I would like to make this 16 bytes, but GCC is telling me it is 24, as the union is padding to 16 bytes.

I would like to put vdata into a large std::vector. From my understanding, there should be no issue with alignment if this were 16 bytes, since the pointer would always be 8 byte aligned.

I understand that I can force this to be packed using __attribute__((__packed__)) in GCC. But I would like to know if there is a portable and standard compliant way to get this to be 16 bytes?


Edit: Ideas

Idea 1: split up the B array.

struct vdata {
  union union_data {
    uint8_t * A; // 8 bytes
    uint8_t B[8]; // 8 bytes
  } u;
  uint8_t B2[4]; // 4 bytes
  int16_t C; // 2 bytes
  int16_t D; // 2 bytes
};

Could B2 elements be reliably accessed from a pointer of B? Is that defined behavior?

Idea 2: store pointer as byte array and memcpy as necessary (@Eljay)

struct vdata {
  union union_data {
    std::byte A[sizeof(uint8_t*)]; // 8 bytes
    uint8_t B[12]; // 12 bytes
  } u;
  int16_t C; // 2 bytes
  int16_t D; // 2 bytes
};

Would there be a performance penalty for accessing the pointer, or would it be optimized out? (Assuming GCC x86).

like image 333
thc Avatar asked Jul 15 '26 20:07

thc


2 Answers

You could change A to std::byte A[sizeof(uint8_t*)]; and then std::memcpy the pointer into A and out of A.

Worth commenting as to what is going on, and that these extra hoops are to avoid padding bytes.

Also adding a set_A setter and get_A getter may be very helpful.

struct vdata {
  union union_data {
    std::byte A[sizeof(uint8_t*)]; // 8 bytes
    uint8_t B[12]; // 12 bytes
  } u;
  int16_t C; // 2 bytes
  int16_t D; // 2 bytes

  void set_A(uint8_t* p) {
    std::memcpy(u.A, &p, sizeof p); 
  }
  uint8_t* get_A() {
    uint8_t* result;
    std::memcpy(&result, u.A, sizeof result);
    return result;
  }
};
like image 120
Eljay Avatar answered Jul 17 '26 14:07

Eljay


Store C+D in the union's array, and provide method access to them:

struct vdata {
  static_assert(sizeof(uint8_t *) == 8L, "size of pointer must be 8");
 
   union union_data {
    uint8_t * A;   // 8 bytes
    uint8_t B[16]; // 12 + 2*2 bytes
  } u;
  
  int16_t& C() { 
    return *reinterpret_cast<int16_t*>(static_cast<void*>(&u.B[12])); 
  }
  int16_t& D() { 
    return *reinterpret_cast<int16_t*>(static_cast<void*>(&u.B[14])); 
  }
};

Demo (with zero warnings for strict aliasing violations and run-time address sanitization enabled)

Keep in mind that there's no strict aliasing violation when the buffer is char* i.e. single byte type like uint8_t - I mean thankfully because otherwise it would be impossible to create memory pools. If it makes things clearer/safer you can even have an explicit char array buffer:

struct vdata {
   union union_data {
    uint8_t * A;   // 8 bytes
    uint8_t B[12]; // 12 bytes
    char buf[16];  // 16 bytes - could be std::byte buf[16]
  } u;
  
  int16_t& C() { return *(int16_t*)(&u.buf[12]); }
  int16_t& D() { return *(int16_t*)(&u.buf[14]); }
};

Regarding alignment The array is 8-aligned due to the address of the union, so positions 12&14 are guaranteed to be 2-aligned which is the requirement for int16_t (even though the string u.B appears in the code).


Alternatively you can force align the structure. The C++ alignas specifier would not be valid here because you want to lower the alignment of your structure, put a pragma directive is possible to give you again 16 bytes:

#pragma pack(4)
struct vdata {
  static_assert(sizeof(uint8_t *) == 8L, "size of pointer must be 8");
  union union_data {
    uint8_t * A; // 8 bytes
    uint8_t B[12]; // 12 bytes
  } u;
  int16_t C; // 2 bytes
  int16_t D; // 2 bytes
};

Demo

I'm fairly certain that this one will cause problems.

like image 29
Nikos Athanasiou Avatar answered Jul 17 '26 14:07

Nikos Athanasiou



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!