3

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?

4
  • reason for not packing the fields optimally optimally by which criteria? Commented Dec 2, 2013 at 9:01
  • Zdeslvav, size I guess. Commented Dec 2, 2013 at 9:03
  • Out of curiosity: Can you share how you can print those nice layouts with Clang and GCC? Thanks in advance :) (Side note: your example uses names from std without “std::” qualification nor using-declarations / using-directive; I guess that on your implementation <cxxx> happens to include <xxx.h> but that's not guaranteed.) Commented Dec 2, 2013 at 10:03
  • 2
    the layout is pulled out of the debug information. github.com/arvidn/struct_layout Commented Dec 2, 2013 at 17:12

2 Answers 2

4

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.

Sign up to request clarification or add additional context in comments.

6 Comments

+1 Likewise important, the size of a struct/class cannot be less than the size of its members, which is exactly what would happen if b2 were packed into b1's padding. It can be greater (due to structure padding of B members, duh), but it can never be less.
this is a good example. however, if A and B are non-PODS (or let's say non-standard layout), I would be surprised if the standard defines copying their bytes as well defined.
@WhozCraig I do seem to recall C guaranteeing sizeof() being at least the sum of sizeof() of all its members. However, it's not obvious that this rule is necessary or makes sense for non-standard layout types (where offsetof is not allowed for instance).
Although A is a "non-POD" type, doesn't mean that it is allowed to break standard rules for struct - sizeof(A) should still be the same as sizeof(B::b1), for example.
yeah. This may be the part of the standard that enforces this behavior. However, I can't think of a good reason for the standard to provide such guarantee (keep in mind we're talking about non-standard layout types). C++98, presumably, had the opportunity to not provide it (again, for non-POD types).
|
1

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.

13 Comments

It's not entirely obvious to me that it doesn't make sense. In a world of C++ and non-standard layout, it's not obvious that these properties need to be exposed to the programmer.
for sure having an instance with different size than its type is not something that makes much sense?
I think that depends on what you expect to conclude from the return value of sizeof(). My understanding is that non-standard layout types do not expose anything about their layout. It doesn't seem that far fetched that they also wouldn't provide those kinds of guarantees about sizeof() of their members. What can one use sizeof() on a non-standard layout type for other than allocating storage for it?
What sizeof returns is defined by C++ standard. There are no standard and non-standard layouts, they are all compliant with C++ spec or the compiler is broken. sizeof can be used for other things: implementing some compile time checks, implementing countof, etc.
c++11 defines the term "standard layout" as essentially the layout constraints of a POD type. I have still not been able to find a place in the standard that requires this padding behavior. sizeof is not particularly strictly defined on non primitive types. I'm just looking in the c++ standard though, maybe it's inherited from C? It does say explicitly that sizeof(A) does not have to be the amount of space used by the base class when C derives from it, in footnote 77.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.