2

struct K is packed and the sum of it's class member sizes is 36. Each member has a static_assert checking this.

However, the size of K itself is 40, not 36. K isn't polymorphic etc.

Why is K not size 36?

Using Clang 16. I'm not aware of an easy way to visualize the struct, or I would have. If anyone can recommend a way that would be great.

template<class S>
struct P
{};

struct D : public P<D>
{
    uint64_t x_;
};

enum class E : uint8_t
{};

struct K
{
    D a_;          
    D b_;          
    uint64_t c_;   
    uint64_t d_;   
    uint16_t e_;   
    E f_;          
    E g_;          

    static_assert(sizeof(a_) == 8); // Passes
    static_assert(sizeof(b_) == 8); // Passes
    static_assert(sizeof(c_) == 8); // Passes
    static_assert(sizeof(d_) == 8); // Passes
    static_assert(sizeof(e_) == 2); // Passes
    static_assert(sizeof(f_) == 1); // Passes
    static_assert(sizeof(g_) == 1); // Passes

}__attribute__((packed));

static_assert(sizeof(K) == 36); // Fails, is 40 instead of 36

int main()
{
    K f;
}
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
intrigued_66
  • 16,082
  • 51
  • 118
  • 189
  • Compilers can add padding between members to enforce data alignment rules. – Scott Hunter Aug 10 '23 at 16:39
  • 1
    @ScottHunter I thought the whole point of packed was to disable that? – intrigued_66 Aug 10 '23 at 16:42
  • 1
    This might be related to the ABI that is being targeted. IIRC the itianium ABI has an issue with empty base classes. – NathanOliver Aug 10 '23 at 16:46
  • 2
    This is not the same as the linked question. One of the answers there implies `packed` should allow what I am expecting. – intrigued_66 Aug 10 '23 at 16:48
  • @NathanOliver I think you're correct. It's that base class..... I've just removed the parent and it works. Is there any way around this? I'm only using the empty parent for CRTP to share functions on the sub class members. – intrigued_66 Aug 10 '23 at 16:49
  • 2
    You might try using [`offsetof`](https://en.cppreference.com/w/cpp/types/offsetof) to get some insight. – Mark Ransom Aug 10 '23 at 16:52
  • 6
    GCC gives the warning: "ignoring packed attribute because of unpacked non-POD field 'D K::a_'" – interjay Aug 10 '23 at 16:53
  • There is a possibility that the compiler is throwing in a Virtual Function table, which may not qualify under the "packed" specification. – Thomas Matthews Aug 10 '23 at 16:58
  • @ThomasMatthews Why would it do that? – Paul Sanders Aug 10 '23 at 23:06
  • The `static_assert` passed on my machine. Homebrew clang version **16.0.6**, Target: **x86_64-apple-darwin22.6.0**, Thread model: **posix**, InstalledDir: **/usr/local/opt/llvm/bin** – Eljay Aug 10 '23 at 23:11
  • If you want to print out the internals of the struct (if you care), and the debugger is not the right view of the bytes, and looking at the compiler generated assembly isn't your thing, and iirc the code has a toe into UB land, you should be able to adapt this code I posted a while ago: https://stackoverflow.com/a/69227655/4641116 – Eljay Aug 10 '23 at 23:24
  • _I'm not aware of an easy way to visualize the struct_ See: https://cppinsights.io/s/b99d45b3 But C++ Insights doesn't exhibit the problem. – Paul Sanders Aug 10 '23 at 23:26

1 Answers1

2

With warnings enabled for clang, the issue becomes obvious:

<source>:17:7: warning: not packing field 'a_' as it is non-POD
               for the purposes of layout [-Wpacked-non-pod]
   17 |     D a_;          
      |       ^
<source>:18:7: warning: not packing field 'b_' as it is non-POD
               for the purposes of layout [-Wpacked-non-pod]
   18 |     D b_;          
      |       ^

Neither GCC nor clang seem to document this behavior (see GCC packed attribute, clang packed attribute), but it looks like using inheritance (from P<D>) disables the effect of packed for a member. There is no inherent reason why D couldn't be packed in this case, it simply appears to be a general ban.

If any member isn't packed, to ensure proper alignment of all members, the struct requires additional padding. The alignment of K is 8, as evidenced by:

static_assert(alignof(K) == 8); // passes; alignment is normally 1 for packed types

Since K is aligned to 8 bytes, and the combined size of its members is 36, 4 padding bytes have to be added at the end to correctly align K to 8 bytes (by increasing its size to 40).


See Also

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96