Arvid
Arvid

Reputation: 11245

C++ struct padding and field packing

Is there anything in the C++ standard preventing a compiler from packing its fields so that one field may overlap with the padding of another?

It's allowed when inheriting (possibly via the empty base class optimization). To illustrate the scenario, consider the following snippet:

#include <cstdint>
#include <cstdlib>

struct A {
    A() { a2 = rand(); } // force non-POD
    uint32_t a1;
    uint16_t a2;
};

struct B {
    A b1;
    uint16_t b2;
};

struct C : A {
    uint16_t c;
};

B and C contain the same fields, but C will only occupy 8 bytes, whereas B will have padding inserted before b2.

I've tested this both with clang (Apple LLVM version 5.0) and GCC 4.7.3, they both behave the same, and produces the following layout:

struct A [8 Bytes]
    0: [uint32_t : 4] a1
    4: [uint16_t : 2] a2
   --- 2 Bytes padding ---                                                             

struct B [12 Bytes]
    0: [uint32_t : 4] b1.a1
    4: [uint16_t : 2] b1.a2
   --- 2 Bytes padding ---                                                             
    8: [uint16_t : 2] b2
   --- 2 Bytes padding ---                                                             

struct C [8 Bytes]
    0: [uint32_t : 4] A::a1
    4: [uint16_t : 2] A::a2
    6: [uint16_t : 2] c

(If A is made a POD, both B and C will have padding, presumably because offsetof() is allowed and the optimization would become visible and possibly break code).

I'm curious, is there's a good reason for not packing the fields optimally in the B case?

Upvotes: 3

Views: 1142

Answers (2)

Zdeslav Vojkovic
Zdeslav Vojkovic

Reputation: 14581

I'm curious, is there's a good reason for not packing the fields optimally in the B case?

In this case, compiler makes sure that types fit on 4-byte boundaries (alignment is typically a performance optimization). Therefore it ensures that sizeof(A) is 8 bytes. If it would pack B to only take 8 bytes, this means that sizeof(b1) would have to be 6, thus sizeof(b1) != sizeof(A) which wouldn't make sense.

The reason why sizeof(b1) equals sizeof(A) is that sizeof operator doesn't evaluate the expression - it is evaluated at compile time, thus it operates on type of expression b1 which is A.

This means that you would end up with following:

B b;
sizeof(b.b1); // => 8, as b1 type is B
sizeof(b);    // => 8 (although b consists of b1 and c);

When you inherit C from A, it is completely new type, so compiler can align it as needed.

Upvotes: 1

Mats Petersson
Mats Petersson

Reputation: 129374

One struct will not overlap another struct, even if the elements of the struct that it overlaps are "padding".

Imagine this:

 A a;
 B x;
 memcpy(&x.b1, a, sizeof(a));

If B was "packed" so that b2 is right next to the a2 element in A, then it would get overwritten with whatever garbage is in a - which is probably not what the original author of such code would expect (I'm pretty sure the standard says that the above has to work).

However, your second example isn't a struct that contains another struct. It is a struct that inherits from another struct. In this case, the original struct A can not be used independently of the other struct member(s) (in this case c). The standard will have a section saying that copying a struct of type A over a struct of type C that inherits from A will be "undefined behaviour", allowing the compiler to "pack" in this case.

So these are two separate cases, which have different limitations.

Upvotes: 4

Related Questions