Optimizing class layout by minimizing padding safely

I have the following type and my goal is to optimize the size of Storage:


struct Inlined   { int  data[9];              };            // sizeof => 36
struct Allocated { int* data; size_t capacity };            // sizeof => 16
union  Data      { Inlined inlined; Allocated allocated; }; // sizeof => 40

struct Storage {                                            // sizeof => 56
  size_t size;
  bool is_allocated;
  Data data;
};

I understand that sizes and layout may vary between compilers and platforms. My goal is to get more compact layout on at least some compilers/platforms.

As you can see in my example, Data has 4 bytes of padding because Inlined and Allocated have different alignments and Data is 8-byte aligned. I am trying to find a way to squeeze boolean is_allocated into this padding.

So far, I came up with the following structure and I would really appreciate if someone can confirm or deny "safety" / "legalness" of such type:

struct CompactStorage {                                      // sizeof => 48
  size_t size;
  union {
    struct {
      bool is_allocated;
      Inlined inlined;
    };
    struct {
      bool copy_of_is_allocated;
      Allocated allocated;
    };
  };
};

To clarify, by safety I mean that I expect CompactStorage::is_allocated and CompactStorage::copy_of_is_allocated to be aliasing the same memory. And I expect that if I write to CompactStorage::is_allocated then it does not overwrite bytes aliased by CompactStorage::allocated (and vice versa). If that does not hold, then I consider CompactStorage unsafe as it cannot be used as a replacement for Storage.

P.S. I know about bitfields and deliberately do not use them due to (proven) performance implications.

>Solution :

This is very much well-defined. From [class.mem]

In a standard-layout union with an active member of struct type T1, it is permitted to read a non-static data member m of another union member of struct type T2 provided m is part of the common initial sequence of T1 and T2; the behavior is as if the corresponding member of T1 were nominated.

Where common initial sequence is

The common initial sequence of two standard-layout struct types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that corresponding entities have layout-compatible types […]

Which means you may use is_allocated and copy_of_is_allocated interchangeably, given that the structs are standard-layout types.

Leave a Reply