3
#include <iostream>
using namespace std;

struct A{
    virtual void f(){};
    int a;
    char ch;
};

struct B : public A{
    char d;
};

struct C{
    double dd;
    int a;
    char ch;
};

struct D : public C{
    char d;
};

int main(){
    B b;
    D d;
    cout<<"b="<<sizeof(b)<<endl;
    cout<<"d="<<sizeof(d)<<endl;
}

Why are the results of b and d different? The actual result is below:

b=16
d=24

I think that the offset of each member relative to the address of the structure variable is exactly a multiple of the size of that member's data type, and the final size is a multiple of the largest data type size among the members. In the presence of inheritance, base class members always appear before derived class members. Furthermore, even with byte alignment, derived class members do not occupy the padding bytes reserved for the base class. In other words, once the size of the base class is determined, these bytes are exclusively owned by the base class and cannot be utilized by members of the derived class. So why the sizeof(b) is 16 instead of 24?

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • 2
    The exact behavior depends on the compiler and the CPU architecture; however, with GCC, `struct B` does fill (occupy) the "reserved" bytes of the base class while `struct D` does not. I can see this behavior both for x86-64 and for m68k CPUs. I don't know why this is done this way. – Martin Rosenau Sep 02 '23 at 10:05
  • struct D has a double in place of struct B's function, how are they supposed to be equal in size? – Wais Kamal Sep 02 '23 at 12:08
  • 1
    Does this answer your question? [C++ + gcc: tail padding reuse and PODs](https://stackoverflow.com/questions/61548135/c-gcc-tail-padding-reuse-and-pods) – Homer512 Sep 02 '23 at 12:22
  • Due to the virtual method, `A` and `B` are not standard-layout (or POD, to use an older term). I haven't checked the C++98 standard but according to the quoted answer, padding could not be reused in PODs (such as `C` and `D`). C++20 does not contain such language but of course we cannot simply change the ABI – Homer512 Sep 02 '23 at 12:24
  • To clear it up a bit more: Nobody is using Itaniums any more but the [SystemV 64 bit ABI is based on Itanium's ABI](https://stackoverflow.com/questions/18133812/where-is-the-x86-64-system-v-abi-documented) – Homer512 Sep 02 '23 at 12:29
  • In order to either change my answer to be more relevant (or, possibly, even remove it), I think you should clarify what compiler and platform you are using. – Adrian Mole Sep 02 '23 at 12:59

1 Answers1

2

What you say about the sizes of structures with inherited base classes may be correct1, although I'm not sure that every assertion you make is true for "non-POD" base classes, like A (because of the member function). However, what you haven't done (and should) is check the sizes of the two (different) base classes.

I can compile your given code on a platform that reproduces your size values (i.e., MSVC, targeting 32-bit Windows); in that case, I added a couple of lines to show the sizes of the base classes:

// Earlier code as yours ...

int main() {
    B b;
    D d;
    cout << "b=" << sizeof(b) << endl;
    cout << "d=" << sizeof(d) << endl;

    cout << "A=" << sizeof(A) << endl;
    cout << "C=" << sizeof(C) << endl;
    return 0;
}

Output:

b=16
d=24
A=12
C=16

Here we see the issue! The struct A has three fields: a pointer to the class "vtable" (clearly 32 bits, here), an int (also 4 bytes) and a char (that last field is padded to 4 bytes); 3 x 4 bytes = 12 bytes; add 4 bytes for the (padded) char field of the derived class and you have 16 bytes.

However, although struct B also has three fields, the size of the first (a double, presumably IEEE-conformant) is 8 bytes, which increases the size of that structure to 16 bytes, and of that derived from it to 24.

When I run the same code built for a 64-bit system, I get the same sizes for the b, d and A, C pairs, because the pointer is then the same size (8 bytes) as the double.


EDIT: The above explanation works for the specific compiler/platform I used and may be the explanation for your observation. However, as pointed out in the comments (here and on the question), the GNU g++ compiler – when targeting 64-bit Windows – gives the same sizes for the two base classes (as expected) but still gives diferent sizes for the derived classes. [See it on Compiler Explorer]

The issue here is clearly different and, as mentioned in the comments and the various other Stack Overflow posts linked therein, is likely because g++ has recognized that struct A is not a "POD-type" and, accordingly, uses the space from its "tail padding" in the derived class.


1 As far as I can tell, the C++ Standard does not prohibit the re-use of tail padding space in derived classes, even for "POD" types. However, compilers may avoid doing so (as g++ does in the linked example) on PODs because it may prevent optimization and/or simplification when using memory-copy operations between base and derived classes (such copy operations should not be used on classes with virtual functions because they would involve overwriting the vtable entries).

Further discussion here: C++ + gcc: tail padding reuse and PODs (and in posts there linked).

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • With GCC on x86-64, A and B both have a size of 16: https://godbolt.org/z/3j7xqYKo9. By the way, `A` contains a vtable pointer, not a function pointer. – interjay Sep 02 '23 at 12:37
  • @interjay Edited the pointer reference. I have also checked the code on Compiler Explorer and found the same. I shall add an edit about that ... – Adrian Mole Sep 02 '23 at 12:39