1

Why the size of classes b and c is 4? Does the virtual key word create a vptr(does vptr exist without virtual function?) or it is something else? Please share your thoughts on this.

#include<iostream>
using namespace std;


class a{    
};

class b:public virtual a{
};

class c:public virtual a{
};

class d:public  b, public c{
};

main(){
    cout<<sizeof(a)<<"\n"; //1
    cout<<sizeof(b)<<"\n"; //4
    cout<<sizeof(c)<<"\n"; //4
    cout<<sizeof(d)<<"\n"; //8
}

If virtual not used anywhere then the o/p become: 1 1 1 2; Expected behavior.

Renuka
  • 217
  • 1
  • 6

5 Answers5

2

Yes, due to virtual inheritance vptr gets created by compiler even though there is no virtual function. Just to understand using gcc compiler we can use(-fdump-tree-all) flag and see the intermediate file(*.class) where vptr and vtable layout can be found.

$ g++ -fdump-tree-all -Wall basic.cpp -o basic

Now we can find the information about vptr and vtable layout from the intermediate basic.class file.

// class a information

Class a
   size=1 align=1
   base size=0 base align=1
a (0x0x7fc8d707e2a0) 0 empty

//class b vptr and size information

Vtable for b
b::_ZTV4b: 3u entries
0     0u
8     (int (*)(...))0
16    (int (*)(...))(& _ZTI4bbbb)

VTT for b
b::_ZTT4b: 1u entries
0     ((& b::_ZTV4b) + 24u)

Class b
   size=8 align=8
   base size=8 base align=8
b (0x0x7fc8d7053e38) 0 nearly-empty
    vptridx=0u vptr=((& b::_ZTV4bbbb) + 24u)
  a (0x0x7fc8d707e300) 0 empty virtual
      vbaseoffset=-24

//class c vptr and size information

Vtable for c
c::_ZTV4c: 3u entries
0     0u
8     (int (*)(...))0
16    (int (*)(...))(& _ZTI4cccc)

VTT for c
c::_ZTT4c: 1u entries
0     ((& c::_ZTV4c) + 24u)

Class c
   size=8 align=8
   base size=8 base align=8
c (0x0x7fc8d7053ea0) 0 nearly-empty
    vptridx=0u vptr=((& c::_ZTV4c) + 24u)
  a (0x0x7fc8d707e360) 0 empty virtual
      vbaseoffset=-24

//class d vptr and size information

Vtable for d
d::_ZTV4d: 6u entries
0     0u
8     (int (*)(...))0
16    (int (*)(...))(& _ZTI4d)
24    18446744073709551608u
32    (int (*)(...))-8
40    (int (*)(...))(& _ZTI4d)

Construction vtable for b (0x0x7fc8d70f8000 instance) in d
d::_ZTC4d0_4b: 3u entries
0     0u
8     (int (*)(...))0
16    (int (*)(...))(& _ZTI4b)

Construction vtable for c (0x0x7fc8d70f8068 instance) in d
d::_ZTC4d8_4c: 3u entries
0     18446744073709551608u
8     (int (*)(...))0
16    (int (*)(...))(& _ZTI4c)

VTT for d
d::_ZTT4d: 4u entries
0     ((& d::_ZTV4d) + 24u)
8     ((& d::_ZTC4d0_4b) + 24u)
16    ((& d::_ZTC4d8_4c) + 24u)
24    ((& d::_ZTV4d) + 48u)

Class d
   size=16 align=8
   base size=16 base align=8
d (0x0x7fc8d70cca80) 0
    vptridx=0u vptr=((& d::_ZTV4d) + 24u)
  b (0x0x7fc8d70f8000) 0 nearly-empty
      primary-for d (0x0x7fc8d70cca80)
      subvttidx=8u
    a (0x0x7fc8d707e3c0) 0 empty virtual
        vbaseoffset=-24
  c (0x0x7fc8d70f8068) 8 nearly-empty
      subvttidx=16u vptridx=24u vptr=((& d::_ZTV4d) + 48u)
    a (0x0x7fc8d707e3c0) alternative-path

This explains that whats going on over here and why and how objects size would vary depending on the how many vptr gets created. my machine is x86_64 GNU/Linux and hence the pointer size would be 8 not 4 as in original example.

Mantosh Kumar
  • 5,659
  • 3
  • 24
  • 48
1

When non-virtual inheritance is used, the complete layout of the object is determined at compile time. This is not the case when virtual inheritance is used - in that case the offset of then base sub-object is determined at run time.

The details of how this is achieved will vary from one compiler to the next, but will usually involve one or more additional pointers. See this question and it's answer for one explanation.

Note that this is separate to vtable pointers which are needed if you have virtual methods. As you have pointed out in your example there are no virtual methods in your example.

Community
  • 1
  • 1
harmic
  • 28,606
  • 5
  • 67
  • 91
0

Stroustrup describes this nicely in Multiple Inheritance for C++ section 7.1.

The object representing a virtual base class a object cannot be placed in a fixed position relative to both b and c in all objects. Consequently, a pointer to a must be stored in all objects directly accessing the a object to allow access independently of its relative position.

It does not add a vtable.

Blaz Bratanic
  • 2,279
  • 12
  • 17
0

Scott Meyers' More Effective C++, Item 24, "Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI", while not going into depth on this issue, explains how virtual base classes may increase object size by adding pointers for the virtual base class in both directly derived classes. It may not have anything do with virtual functions. The compiler may just find this the easiest approach to overcome the problem that b and c may have different sizes themselves and a-member access may be through any of b, c or d.

A lot of "may" here, and that's because the whole thing is completely compiler-specific, as the C++ standard does not specify memory layout for classes. It's strangely hard to find really good authorative documentation about memory layout for virtual base classes by compiler vendors on the Internet (which is perhaps because virtual inheritance is a very rarely used C++ language feature in the first place).

For GCC, you may find the -fdump-class-hierarchy compiler option useful. Here's what it generates for your example on my machine (removing the standard-library stuff):

Class a
   size=1 align=1
   base size=0 base align=1
a (0x0x344a0a8) 0 empty

Vtable for b
b::_ZTV1b: 3u entries
0     0u
4     (int (*)(...))0
8     (int (*)(...))(& _ZTI1b)

VTT for b
b::_ZTT1b: 1u entries
0     ((& b::_ZTV1b) + 12u)

Class b
   size=4 align=4
   base size=4 base align=4
b (0x0x3460b40) 0 nearly-empty
    vptridx=0u vptr=((& b::_ZTV1b) + 12u)
  a (0x0x344a0e0) 0 empty virtual
      vbaseoffset=-12

Vtable for c
c::_ZTV1c: 3u entries
0     0u
4     (int (*)(...))0
8     (int (*)(...))(& _ZTI1c)

VTT for c
c::_ZTT1c: 1u entries
0     ((& c::_ZTV1c) + 12u)

Class c
   size=4 align=4
   base size=4 base align=4
c (0x0x3460d40) 0 nearly-empty
    vptridx=0u vptr=((& c::_ZTV1c) + 12u)
  a (0x0x344a118) 0 empty virtual
      vbaseoffset=-12

Vtable for d
d::_ZTV1d: 6u entries
0     0u
4     (int (*)(...))0
8     (int (*)(...))(& _ZTI1d)
12    4294967292u
16    (int (*)(...))-4
20    (int (*)(...))(& _ZTI1d)

Construction vtable for b (0x0x3460f00 instance) in d
d::_ZTC1d0_1b: 3u entries
0     0u
4     (int (*)(...))0
8     (int (*)(...))(& _ZTI1b)

Construction vtable for c (0x0x3460f40 instance) in d
d::_ZTC1d4_1c: 3u entries
0     4294967292u
4     (int (*)(...))0
8     (int (*)(...))(& _ZTI1c)

VTT for d
d::_ZTT1d: 4u entries
0     ((& d::_ZTV1d) + 12u)
4     ((& d::_ZTC1d0_1b) + 12u)
8     ((& d::_ZTC1d4_1c) + 12u)
12    ((& d::_ZTV1d) + 24u)

Class d
   size=8 align=4
   base size=8 base align=4
d (0x0x3460ec0) 0
    vptridx=0u vptr=((& d::_ZTV1d) + 12u)
  b (0x0x3460f00) 0 nearly-empty
      primary-for d (0x0x3460ec0)
      subvttidx=4u
    a (0x0x344a150) 0 empty virtual
        vbaseoffset=-12
  c (0x0x3460f40) 4 nearly-empty
      subvttidx=8u vptridx=12u vptr=((& d::_ZTV1d) + 24u)
    a (0x0x344a150) alternative-path

For non-virtual inheritance, the memory layout would be much simpler:

Class a
   size=1 align=1
   base size=0 base align=1
a (0x0x344a0a8) 0 empty

Class b
   size=1 align=1
   base size=1 base align=1
b (0x0x346cb40) 0 empty
  a (0x0x344a0e0) 0 empty

Class c
   size=1 align=1
   base size=1 base align=1
c (0x0x346cbc0) 0 empty
  a (0x0x344a118) 0 empty

Class d
   size=2 align=1
   base size=2 base align=1
d (0x0x346cc40) 0 empty
  b (0x0x346cc80) 0 empty
    a (0x0x344a150) 0 empty
  c (0x0x346ccc0) 1 empty
    a (0x0x344a188) 1 empty

So GCC apparently implements virtual inheritance through vptrs and does not care about optimising it away when it's not needed.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
-1

I'm guessing that B is bigger for the vtable (even though there are no functions).

The implementation will depend on the compiler and all that of course, but I remember with Delphi back in the day, it'd create the virtual table 4 bytes in front of the instance of the class (eg &myInstance-4).

I'm guessing it has to create a table even with no functions, maybe just to put a 0 there.

Remember that the way Virtual Functions are handled has to be compatible throughout the system, so if you were to load a dynamic library that looked at your classes it'd need to know how to read them. I suspect for that reason that the compiler can't get rid of the extra space completely.

matiu
  • 7,469
  • 4
  • 44
  • 48