1

There are many many questions from surprised C++ programmers who notice that tail padding is reused only when the base class is not a POD type.

For example, the following code works on x86-64 gcc (godbolt link):

struct A {
    int a;
    bool aa;
};
struct B : A {
    bool b;
};
struct C : B {
    bool c;
};
static_assert(sizeof(A) == 8);
static_assert(sizeof(B) == 12);
static_assert(sizeof(C) == 12);

This means that the field c is placed into the padding of B, but the field b is not placed into the padding of A.

The linked questions only explain that a particular ABI specifies this layout, and one answer goes as far as to say that reusing the tail padding of a POD type "breaks common assumptions a programmer would make", and so "basically any sane compiler won't do tail padding reuse" for such types. However, this explanation is unsatisfactory because it doesn't actually guarantee anything.

Does the standard guarantee that the tail padding of a POD type will not be reused by a derived class?

Bernard
  • 5,209
  • 1
  • 34
  • 64
  • I think the gcc layout is such that it can implement the copy constructor / assignment for `A` as copying 64bit in one instruction, which would overwrite `B::b` if it used the padding. Functions taking an `A*` might also `memcpy()` into it, again overwriting the padding. A common usage with POD types. But I think that is just a niceness of the ABI. Note: Windows ABI doesn't use tail padding at all giving `sizeof(C) == 16`. – Goswin von Brederlow Jun 12 '22 at 00:43

1 Answers1

0

Being an aggregate matters for exactly one thing: aggregate initialization. It has nothing to do with the layout of the members of a type.

That is governed by the layout rules. A is standard layout, which is a subset of types that have certain layout guarantees. Classes B and C are not standard layout (or POD, or whatever you'd like to call it), so there is much more variance permitted in the layout of those types.

The standard permits any of the implementations specifies by the other answers. They're all equally valid.

It's OK for a type to use the padding in non-empty base classes for its own subobjects. This is in part because doing things like memcpy into base class subobjects (or any other potentially-overlapping subobject) is expressly forbidden:

For any object (other than a potentially-overlapping subobject) of trivially copyable type T ...

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • _It's OK for a type to use the padding in non-empty base classes for its own subobjects_ Then, [replacing the subobjects](https://timsong-cpp.github.io/cppwp/n4861/intro.object#2.sentence-4) [will kill the base class subobject](https://timsong-cpp.github.io/cppwp/n4861/basic.life#1.5) – Language Lawyer Jun 11 '22 at 19:14
  • @LanguageLawyer: And how exactly do you intend to "replace the subobjects"? The only operation that can do so which will definitely overwrite the padding bytes is a `memcpy` or equivalent... which is *expressly* forbidden. – Nicol Bolas Jun 11 '22 at 20:15
  • _And how exactly do you intend to "replace the subobjects"?_ Using "placement new"? – Language Lawyer Jun 11 '22 at 23:18
  • @LanguageLawyer: Which will break the object, because the base class instance is a "potentially overlapping subobject". [basic.life]/8 only allows "transparently replaceable" objects to work correctly in that circumstance, and potentially overlapping subobjects are not transparently replaceable. – Nicol Bolas Jun 11 '22 at 23:40
  • So, `B::b` is transparently replaceable and `C::c` is not? – Language Lawyer Jun 12 '22 at 11:28
  • @LanguageLawyer: Neither of those is "potentially overlapping". However, `B::A` and `C::B` and `C::A` are. You can transparently replace the members even if they overlap with unused parts of base class subobjects. But you cannot transparently replace the base class subobjects. – Nicol Bolas Jun 12 '22 at 13:20
  • _You can transparently replace the members even if they overlap with unused parts of base class subobjects_ But if the parts really belong to base class subobjects, replacing an overlapping member subobject would kill the base one(s). But I doubt this is the intent. So, I think "padding is (re)used" is an unfortunate term, it looks like it means that a base class subobject still has padding, and that member subobjects overlap with it. – Language Lawyer Jun 12 '22 at 20:05
  • @LanguageLawyer: "*But if the parts really belong to base class subobjects*" Who said that they "belong to the base class subobjects"? Padding is *padding*; by definition, it is not part of the value representation of the object. And any of the operations on an object which would allow you to explicitly overwrite padding bits are forbidden on base class subobjects. – Nicol Bolas Jun 12 '22 at 21:45
  • _Who said that they "belong to the base class subobjects"?_ So, member subobjects are not placed in the padding bytes of a base class one, base's padding is "truncated" to give place to member subobjects? _Padding is padding; by definition, it is not part of the value representation of the object._ Don't think value vs. only object representation is relevant for https://timsong-cpp.github.io/cppwp/n4861/basic.life#1.5 – Language Lawyer Jun 14 '22 at 06:58