4

I think sizeof(Base) should be 12. Why is it 16?

Without the virtual function, I get 4 and 8.

class Base{
  public:
    int i;
    virtual void Print(){cout<<"Base Print";}
};

class Derived:public Base{
  public:
    int n;
    virtual void Print(){cout<<"Derived Print";}
};

int main(){
  Derived d;
  cout<<sizeof(Base)<<","<<sizeof(d);
  return 0;
}

expected result:12,16

actual result:16,16

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Z.Hc
  • 41
  • 2

2 Answers2

8

why sizeof(Base) is not different of sizeof(Derived)

Because of the alignment introduced by the compiler.

That is architecture-dependent, but for sake of simplicity, I am going to assume we are referring a 64-bit architecture.

Scenario 64 bit / Clang 8.0.

The alignment of the type Base is 8 bytes:

alignOfBase(): # @alignOfBase()
  mov eax, 8
  ret

The layout of Base is composed by the variable member (int) and the virtual table (vtptr).

If we assume a "common" architecture where:

  • int is 4 bytes size.
  • vtptr is a pointer. On a 64 bit architecture is 8 bytes size.

We should have a sum of 4 + 8 = 12, as you expect.

However, we need to remember the alignment of Base is 8 bytes. Therefore consecutive Base types should be stored in location multiple of 8.

In order to guarantee that, the compiler introduces padding for Base. That's why Base is 16 bytes size.

For example, if we consider 2 consecutive Base (base0 and base1) without padding:

0:  vtptr (base 0) + 8
8:  int   (base 0) + 4
12: vtptr (base 1) + 8  <--- Wrong! The address 12 is not multiple of 8.
20: int   (base 1) + 4

With padding:

0:  vtptr (base 0) + 8
8:  int   (base 0) + 4+4   (4 padding)
16: vtptr (base 1) +8      <--- Fine! The adress 16 is multiple of 8.
24: int   (base 1) +4+4    (4 padding)

The same story is for Derived type.

The layout of Derived should be: vtptr + int + int, that is, 8 + 4 + 4 = 16.

The alignment of Derived is 8 too:

alignOfDerived(): # @alignOfDerived()
  mov eax, 8
  ret

Indeed, in this case, there is no need to introduce padding in order to keep the Derived aligned with the memory. The layout size will be the same as the real size.

0:   vtptr (Derived 0)
8:   int   (Derived 0)
12:  int   (Derived 0)
16:  vtptr (Derived 1)  <---- Fine. 16 is multiple of 8.
24:  int   (Derived 1)
28:  int   (Derived 1)
BiagioF
  • 9,368
  • 2
  • 26
  • 50
  • 1
    You forgot to explain the difference between arrays and structs: `struct { T x1, x2; };` (in principle) could have padding, but `T x[2]` by definition can't, so anything that can be in an array (any concrete object type) must have a size compatible with its alignment. – curiousguy Mar 27 '19 at 12:30
1

This happens due to compiler deciding to align your classes.

If you want (or need) structs or classes to have their "real" sizes, you can use #pragma pack(1) like this:

#pragma pack(push, 1) // Set packing to 1 byte and push old packing value to stack

class Base{
  public:
    int i;
    virtual void Print(){cout<<"Base Print";}
};

class Derived:public Base{
  public:
    int n;
    virtual void Print(){cout<<"Derived Print";}
};

int main(){
  Derived d;
  cout<<sizeof(Base)<<","<<sizeof(d);
  return 0;
}

#pragma pack(pop) // restore old packing value
JustSomeGuy
  • 3,677
  • 1
  • 23
  • 31