7

Recently, I've been reading "inside the c++ object model". It says that the padding used in base class should also be copied into the derived class, in case you want to assign the base class to the derived class. Thus, I run a test under a 64-bit computer:

    class A {
    public:
        int valA;
        char a;
    };
    class B : public A {
    public:
        char b;
    };
    class C : public B {
    public:
        char c;
    };
    int main(){
        std::cout << sizeof(A) << " " << sizeof(B) << " " << sizeof(C) 
        << std::endl;
        C c;
        printf("%p\n%p\n%p\n",&c,&c.b,&c.c);
    }

Here is the result:

    8 12 12

    0x7ffd22c5072c

    0x7ffd22c50734

    0x7ffd22c50735

So why is C the same size of B? Although it seems that B used the 3 byte padding in A.

Evg
  • 25,259
  • 5
  • 41
  • 83
刘卫东
  • 325
  • 1
  • 7
  • 1
    Evidently, the single char class isn't padded (there's no need). If your `c` field in `C` was an `int`, say, that would no doubt be aligned properly. – 500 - Internal Server Error Oct 15 '18 at 14:02
  • 2
    Change `c` to `int` and run your program again. your suspicion is likely correct for your toolchain. – WhozCraig Oct 15 '18 at 14:04
  • 2
    The standard makes no guaranties about this. The class might get `memcpy`ed in some cases or it might be copied member-wise. The copy might not happen due to optimizations and make it look like the padding was copied. Lots of things can happen. – François Andrieux Oct 15 '18 at 14:05

2 Answers2

3

So why is C the same size of B?

Because trailing padding of B was reused for C::b. The padding can be reused because B is not a POD (plain old data) class (because it is not a standard layout class).

Although it seems that B used the 3 byte padding in A.

The padding of A cannot be reused for other sub objects of B, because A is a standard layout class and is trivially copyable i.e. A is a POD class.


will the padding of base class be copied into the derived class?

I suppose that you didn't mean to ask about copying, but are rather whether the base class sub object of a derived class will have the same padding as the individual type.

The answer is, as might be deduced from the above: The padding will be same, except the trailing padding may be re-used for other sub objects, unless the base class is a POD, in which case its padding can not be reused.

In the case where the padding may be reused, whether it will be is not specified by the standard, and there are differences between compilers.


Please explain or link to a definition of "standard layout types".

Current standard draft:

[basic.types]

... Scalar types, standard-layout class types ([class.prop]), arrays of such types and cv-qualified versions of these types are collectively called standard-layout types.


[class.prop] (in older versions of the standard, these may be found under [class] directly)

A class S is a standard-layout class if it:

  • (3.1) has no non-static data members of type non-standard-layout class (or array of such types) or reference,

  • (3.2) has no virtual functions and no virtual base classes,

  • (3.3) has the same access control for all non-static data members,

  • (3.4) has no non-standard-layout base classes,

  • (3.5) has at most one base class subobject of any given type,

  • (3.6) has all non-static data members and bit-fields in the class and its base classes first declared in the same class, and

  • (3.7) has no element of the set M(S) of types as a base class, where for any type X, M(X) is defined as follows.107 [ Note: M(X) is the set of the types of all non-base-class subobjects that may be at a zero offset in X. — end note]

    • (3.7.1) If X is a non-union class type with no (possibly inherited) non-static data members, the set M(X) is empty.

    • (3.7.2) If X is a non-union class type with a non-static data member of type X0 that is either of zero size or is the first non-static data member of X (where said member may be an anonymous union), the set M(X) consists of X0 and the elements of M(X0).

    • (3.7.3) If X is a union type, the set M(X) is the union of all M(Ui) and the set containing all Ui, where each Ui is the type of the ith non-static data member of X.

    • (3.7.4) If X is an array type with element type Xe, the set M(X) consists of Xe and the elements of M(Xe).

    • (3.7.5) If X is a non-class, non-array type, the set M(X) is empty.

Item (3.6) applies in this case. Some members of B are not first declared in B. In particular, B::A::valA, and B::A::a are declared first in A. A friendlier way to describe the rule is: The class must have either no direct members, or none of its ancestors must have members. In this case both the base and the derived class have members, so it is not standard layout.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    Please explain or link to a definition of "standard layout types". – einpoklum Oct 15 '18 at 14:25
  • @Sjoerd It's 3.6. Some members of `B` are not first declared in `B`. In particular, `B::A::valA`, and `B::A::a` are declared first in `A`. A friendlier way to describe the rule is: The class must have either no direct members, or none of its ancestors must have members. In this case both the base, and the derived class have members, so it is not standard layout. – eerorika Oct 15 '18 at 15:20
  • If I understand your assertion correctly, you are saying that if `B` is POD, then a `class A : B {...}` which derives from B can't re-use B's padding? – BeeOnRope Jan 22 '20 at 18:14
  • [This](https://godbolt.org/z/ipJnKo) seems to be a counter-example, at least if you assume the compilers are following the standard. `simple` is POD (at least by `std::is_pod`'s opinion and the C++11 rules, although not the C++03 rules), yet padding of `simple` is re-used by `derived`. – BeeOnRope Jan 22 '20 at 18:19
  • @BeeOnRope I guess the answer is wrong then. It might actually be that as long as the class is non-POD (or non-standard-layout), it can re-use the padding of sub objects (such as derived is and does in your example). But this doesn't explain lack of reuse in the OP where the derived B is non-POD. Either there is some rule that I missed, or the ABI simply doesn't take advantage of it (possibly due to backward compatibility). – eerorika Jan 22 '20 at 18:50
  • @eerorika - the rules are not clear to me. Also, there is a difference between what the standard allows and what the ABI does. We can probably assume anything the ABI does is allowed by the standard, but the reverse isn't true: the ABI may be more restrictive in some cases, e.g., to preserve backwards compatibility. E.g., in my example if you change `simple` to struct the padding is not re-used, perhaps for compatibility with an ABI introduced earlier. – BeeOnRope Jan 22 '20 at 19:35
  • I added an answer which explains the scenario as I see it, but it doesn't explain, e.g., why the same optimization is not applied to `B` wrt `A`. – BeeOnRope Jan 22 '20 at 19:53
0

C is the same size as B because on your platform the ABI chooses the use the padding in B to store the 1-byte member C::c. B has 3 bytes of padding at the end because the entire B object has alignment 4 (due to the int member in A).

B is not the same size as A, however, because in this case the ABI apparently does not allow storing B::b in the padding of A even though there is room. This happens when all of A members are public, as they are in your example: if you make any member private, the size of A, B and C will all be 8. I believe this may be for ABI backwards compatibility, rather than motivated by any language in the standard.

I don't know if there is language in the standard which directly allows this (but there doesn't need to be), but it certainly seems that this type of padding re-use is contemplated in the case of inheritance. For example, the documentation for std::memcpy says:

If the objects are potentially-overlapping or not TriviallyCopyable, the behavior of memcpy is not specified and may be undefined.

It goes on to define potentially-overlapping:

A subobject is potentially overlapping if it is either

  • a base class subobject, or
  • a non-static data member declared with the [[no_unique_address]] attribute.

The second condition applies only in C++20.

This seems to be written to allow padding to be shared: if this clause didn't exist, memcpy on a pointer to a B subclass of C would overwrite the value of C::c which is stored in what is usually padding for B.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386