How to Determine Sizeof Class with Virtual Functions

how to determine sizeof class with virtual functions?

This is of course implementation-dependent. And it would make a terrible interview question. A good C++ programmer can just trust sizeof to be right and let the compiler worry about those vtable things.

But what's going on here is that a typical vtable-based implementation needs two vtables in objects of class C or D. Each base class needs its own vtable. The new virtual methods added by C and D can be handled by extending the vtable format from one base class, but the vtables used by A and B can't be combined.

In pseudo-C-code, here's how a most derived object of type D looks on my implementation (g++ 4.4.5 Linux x86):

void* D_vtable_part1[] = { (void*) 0, &D_typeinfo, &A::f1, &C::f3, &D::f4 };
void* D_vtable_part2[] = { (void*) -4, &D_typeinfo, &B::f2 };

struct D {
void** vtable_A;
void** vtable_B;
};

D d = { D_vtable_part1 + 1, D_vtable_part2 + 1 };

sizeof class with int , function, virtual function in C++?

First off, a virtual function is not a pointer with 8 bytes. In C++ nothing but sizeof(char) is guaranteed to be any number of bytes.

Second, only the first virtual function in a class increases its size (compiler-dependent, but on most - if not all - it's like this). All subsequent methods do not. Non-virtual functions do not affect the class's size.

This happens because a class instance doesn't hold pointers to methods themselves, but to a virtual function table, which is one per class.

So if you had:

class A
{
virtual void foo();
}

and

class B
{
virtual void goo();
virtual void test();
static void m();
void x();
}

you would have sizeof(A) == sizeof(B).

And now:

Why siszeof(A) is 1 and sizeof(C) is 1 too ?

A and C have size 1 just because it's not allowed for a class to be of size 0. The functions have nothing to do with it. It's just a dummy byte.

Why siszeof(H) is 16 but sizeof(G) is 4 ?

G has only one member that accounts for memory - the int. And on your platform, sizeof(int) == 4. H, besides the int, also has a pointer to the vftable (virtual function table, see above). The size of this, size of int and allignment are compiler specific.

Why siszeof(E) is 16 but sizeof(F) is 4 ?

Explained above - non virtual methods don't take up memory in the class.

Why siszeof(D) is 8 but sizeof(E) is 16 ?

D only contains the vftable pointer which is apparently 8 bytes on your platform. E also has an int, and the vftable is aligned to 8 bytes. So it's something like:

class E

4 bytes for int | 4 padding bytes | 8 bytes for vftable pointer |
| x | x | x | x | | | | | v | v | v | v | v | v | v | v |

sizeof derived class with virtual base and virtual function

Pointer denotes the virtual function table of Base class--4 bytes

Pointer denotes the virtual function Func2() which only belongs to
class Derived--4 bytes (as far as I'm concerned, derived class which
has no non-virtual base classes and gets its unique virtual functions
should has its own virtual table)

Ah, I see the problem now. That's not quite how virtual function tables work. When Base is defined, the compiler notices it requires a virtual table, and generates a virtual table for Base, with one pointer (Func), which points to the Base::Func implementation. When Derived is defined, the compiler notices it inherits from Base, and generates a function table for Base that has two pointers, Func points to Derived::Func, and Func2 points to Derived::Func2.

Then, if an instance of Base is created, it's function table pointer that you mention points at the Base table, and any calls to Func will be redirected to Base::Func.

If an instance of Derived is created, it's internal Base object's virtual function table pointer will point at the Derived table instead. Base only knows how to access the Func pointer, but that Func pointer points at Derived::Func now, and so that's what get's called. It doesn't realize it's pointing at a different table. In code, it might look more like this:

using voidFunctionType = void(*)();

struct BaseVTable {
voidFunctionType Func;
}BaseVTableGlobal;

struct Base {
Base() :vTable(&BaseVTableGlobal) {}
void Func() {vTable->Func();}

BaseVTable* vTable; //4 bytes
int BaseValue; //4 bytes
}; //total is 8 bytes

struct DerivedVTable : public BaseVTable {
voidFunctionType Func;
voidFunctionType Func2;
}DerivedVTableGlobal;

//inherits 8 bytes, +4 for virtual inheritance = 12
struct Derived : virtual public Base {
Derived() :Base() {vTable = &DerivedVTableGlobal;} //the shared vTable points at DerivedVTableGlobal
void Func() {vTable->Func();} //base knows about Func, so this is easy
void Func2() {((DerivedVTable*)vTable)->Func2();} //base doesn't know about Func2

int DerivedValue; //4 bytes
}; //16 bytes total

So XCode is right. Derived is "hijacking" Bases virtual function table, and in fact, that's exactly how virtual functions do their magic.

(Assumptions everywhere, none of this is well-defined, virtual inheretence complicates things, etc etc etc)

Size of class with virtual function

You're working on a platform where pointers are aligned to 8 bytes. Since the virtual table pointer is typically the first thing in the layout of an object, it too must be aligned to 8 bytes. So padding 4 bytes are inserted after the int member, that's why you get a size of 16 (8 bytes for the vf table pointer, 4 for the int and 4 padding bytes).

C++ object size with virtual methods

This is all implementation defined. I'm using VC10 Beta2. The key to help understanding this stuff (the implementation of virtual functions), you need to know about a secret switch in the Visual Studio compiler, /d1reportSingleClassLayoutXXX. I'll get to that in a second.

The basic rule is the vtable needs to be located at offset 0 for any pointer to an object. This implies multiple vtables for multiple inheritance.

Couple questions here, I'll start at the top:

Does it mean that only one vptr is there even both of class B and class A have virtual function? Why there is only one vptr?

This is how virtual functions work, you want the base class and derived class to share the same vtable pointer (pointing to the implementation in the derived class.

It seems that in this case, two vptrs are in the layout.....How does this happen? I think the two vptrs one is for class A and another is for class B....so there is no vptr for the virtual function of class C?

This is the layout of class C, as reported by /d1reportSingleClassLayoutC:

class C size(20):
+---
| +--- (base class A)
0 | | {vfptr}
4 | | a
| +---
| +--- (base class B)
8 | | {vfptr}
12 | | b
| +---
16 | c
+---

You are correct, there are two vtables, one for each base class. This is how it works in multiple inheritance; if the C* is casted to a B*, the pointer value gets adjusted by 8 bytes. A vtable still needs to be at offset 0 for virtual function calls to work.

The vtable in the above layout for class A is treated as class C's vtable (when called through a C*).

The sizeof B is 16 bytes -------------- Without virtual it should be 4 + 4 + 4 = 12. why there is another 4 bytes here? What's the layout of class B ?

This is the layout of class B in this example:

class B size(20):
+---
0 | {vfptr}
4 | {vbptr}
8 | b
+---
+--- (virtual base A)
12 | {vfptr}
16 | a
+---

As you can see, there is an extra pointer to handle virtual inheritance. Virtual inheritance is complicated.

The sizeof D is 32 bytes -------------- it should be 16(class B) + 12(class C) + 4(int d) = 32. Is that right?

No, 36 bytes. Same deal with the virtual inheritance. Layout of D in this example:

class D size(36):
+---
| +--- (base class B)
0 | | {vfptr}
4 | | {vbptr}
8 | | b
| +---
| +--- (base class C)
| | +--- (base class A)
12 | | | {vfptr}
16 | | | a
| | +---
20 | | c
| +---
24 | d
+---
+--- (virtual base A)
28 | {vfptr}
32 | a
+---

My question is , why there is an extra space when virtual inheritance is applied?

Virtual base class pointer, it's complicated. Base classes are "combined" in virtual inheritance. Instead of having a base class embedded into a class, the class will have a pointer to the base class object in the layout. If you have two base classes using virtual inheritance (the "diamond" class hierarchy), they will both point to the same virtual base class in the object, instead of having a separate copy of that base class.

What's the underneath rule for the object size in this case?

Important point; there are no rules: the compiler can do whatever it needs to do.

And a final detail; to make all these class layout diagrams I am compiling with:

cl test.cpp /d1reportSingleClassLayoutXXX

Where XXX is a substring match of the structs/classes you want to see the layout of. Using this you can explore the affects of various inheritance schemes yourself, as well as why/where padding is added, etc.

how to determine the size of virtual base class and derived classes from it?

The reason that sizeof(base1) and sizeof(test1) are 1 is solely to prevent a most-derived object from having size 0. That's all the standard forbids. Base class sub-objects are allowed to have size 0 (that is, allowed to occupy no bytes), and hence adding base1 as a base doesn't necessarily have to add anything to the size of the class.

The optimization your compiler has made, not allocating any bytes for a base-class sub-object whose type is an empty class, is called the "empty base class optimization". It's not required by the standard that the implementation apply it, but an implementation that didn't might not be considered fit for serious work.

I think derv22 is somewhat similar - if the compiler is capable of dealing with two virtual base classes using a single extra pointer, then it's entitled to do so. Hence, you might only have to "pay" once, rather then "paying" per virtual base. That could depend on the compiler and on the exact relationships between the classes, though, I've never surveyed different implementations to see if and when they're forced to add multiple pointers worth of overhead.

Apparently derv222 has done it, though, at least for your compiler. I suppose that this is because the base2 and test2 base class sub-objects need separate vtable pointers. Probably not that surprising if you consider what happens when you static_cast a derv222* as a pointer to one base or the other - both results need to be capable of having show() called on them, and calling different functions (albeit the show functions currently do nothing). I'm not sure whether it would be possible for another compiler to implement this inheritance in 8 bytes -- for one thing inheritance doesn't have to be implemented using vtables.

size of derived class in virtual inheritance

Virtual inheritance means, that the virtual base classes only exist once instead of multiple times. That is why the 8 bytes from ClassA are only in ClassD once. Virtual inheritance itself requires a certain overhead and hence you get an additional pointer. The exact implementation and therefore the exact overhead is not specified by the C++ standard and may vary depending on the hierarchy you are creating.

Size of derived class in virtual base class function

You can use the CRTP idiom to do this.
https://eli.thegreenplace.net/2011/05/17/the-curiously-recurring-template-pattern-in-c
The idea is the parent class is a template, so you can have access to the type of the child class directly in it.
With that, you'll be able to remove all "PrintSize" from child class.

Example :

template <typename Derived>
class A {
int x, y;
public:
A() {}
void PrintSize() { cout << sizeof(Derived) << endl; }
};

class B : public A<B> {
int a, b, c;
public:
B() {}
};

class C : public A<C> {
public:
C() {}
};

int main() {
C objc;
B objb;

objc.PrintSize();
objb.PrintSize();
}

The output is :

8

20



Related Topics



Leave a reply



Submit