Search code examples
c++virtual-functionsmemory-alignmentmemory-layoutvptr

Why are classes with virtual functions aligned differently than classes without?


Inspired by this cppcon talk by Richard Powell I have created the following code snippet to fool around:

#include <iostream>
using std::cout;
using std::endl;

struct erdos
{
  void who()
  {
    cout << "erdos" << endl;
  }
  float f1;
  float f2;
};

struct fermat : public erdos
{
  float f3;
};

struct fermat2 : public fermat
{
  float f4;
};

struct fermat3 : public fermat2
{
  float f5;
};

int main(void)
{
  erdos e;
  cout << "sizeof(e)" << sizeof(e) << endl;
  fermat f;
  cout << "sizeof(f)" << sizeof(f) << endl;
  fermat2 f2;
  cout << "sizeof(f2)" << sizeof(f2) << endl;
  fermat3 f3;
  cout << "sizeof(f3)" << sizeof(f3) << endl;
  cout << "sizeof(void*)" << sizeof(void*) << endl;
  cout << "sizeof(float)" << sizeof(float) << endl;
  return 0;
}

which would print:

sizeof(e)8
sizeof(f)12
sizeof(f2)16
sizeof(f3)20
sizeof(void*)8
sizeof(float)4

After adding virtual to who() I get this

sizeof(e)16
sizeof(f)24
sizeof(f2)24
sizeof(f3)32
sizeof(void*)8
sizeof(float)4

Now, adding the size of void* to the struct is straightforward but why would there be this padding (which is also mentioned by Richard in his talk) in virtual case and not in non-virtual case?

sizeof(e)16 - 8 = 8 
sizeof(f)24 - 8 = 16 but is in fact 12 (padding 4)
sizeof(f2)24 - 8 = 16 matches
sizeof(f3)32 - 8 = 24 but is in fact 20 (padding 4)

I've tested it with gcc 5.3.0 and clang 3.7.1 on Ubuntu 14.04 64 bit


Solution

  • sizeof(void*)8
    

    Well, there's your answer.

    Assuming your implementation only needs a pointer to handle virtual lookup, that's the cause of the alignment. A pointer, on 64-bit compilation, needs 64-bits of space (which is why we call it "64-bit"). But it also needs 64-bit alignment.

    Therefore, any data structure that stores a pointer in a 64-bit compilation must also be 64-bit aligned. The alignment of the object must be 8-byte aligned, and the size must be padded to 8 bytes (for array indexing reasons). You'd see the same thing if you made one of the float members a pointer.