2

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

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Patryk
  • 22,602
  • 44
  • 128
  • 244
  • 5
    How do you _know_ it's padding? Maybe it's additional data like offsets and table pointers. It's an implementation detail and is not guaranteed to be the same across compilers. – Captain Obvlious Dec 17 '15 at 20:23
  • 2
    You've added a virtual method table, which didn't exist previously. – Donnie Dec 17 '15 at 20:27

1 Answers1

6
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.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • So what you're saying is that pointers (to functions) have to be aligned but to members (like in non-virtual case I presented) not? That's why non-virtual f3 is sized `sizeof(f3)20` ? – Patryk Dec 17 '15 at 20:33
  • 2
    @Patryk : non-virtual `f3` contains 5 members of size 4. 5×4 = 20. `float` is probably 4-bytes aligned. As 20 is divisible by 4, no padding nessesary. Virtual `f3 contains 5 members of size 4 and another one with size of 8. 5×4+8 = 28. `float` is probably 4-bytes aligned, pointer is probably 8-bytes aligned. As higher aligment requirement is used, structure has 8-byte aligment. As 20 is not divisible by 8 evenly, compiler need to add padding to nearest multiple of 8, which is 32. Of course it is just speculation. Compiler might just decide to add 2 6-bytes fields with no aligment requirements. – Revolver_Ocelot Dec 17 '15 at 20:41
  • @Revolver_Ocelot Thanks a lot. That sheds more light on this topic – Patryk Dec 17 '15 at 20:44
  • 1
    @Patryk: Also, do note that everything I and Ocelot said is based on the assumption that your compiler uses a vtable pointer to implement virtual dispatch. There's nothing in the standard that requires this; it is simply one possible implementation (though a common one). – Nicol Bolas Dec 17 '15 at 20:49