Detail about How VPTR and Virtual table works

This is a discussion on Detail about How VPTR and Virtual table works within the C++ Programming forums, part of the General Programming Boards category; Assumption: machine is 32-bit . Here I am going to explain How Virtual table, Virtual pointer for Virtual functions are ...

  1. #1
    Registered User
    Join Date
    Mar 2008
    Posts
    2

    Detail about How VPTR and Virtual table works

    Assumption: machine is 32-bit .
    Here I am going to explain How Virtual table, Virtual pointer for Virtual functions are internally working.

    First we have understand memory layout.

    Example 1: How the class's memory layout
    Code:
    class Test 
    {
      public:
        int data1;
        int data2;
        int fun1();
    };
    
    int main() 
    {
      Test obj;
      cout << "obj's Size = " << sizeof(obj) << endl;
      cout << "obj 's Address = " << &obj << endl;
      return 0;
    }
    Output:

    Sobj's Size = 8
    obj 's Address = 0012FF7C

    Note: Any Plane member function does not take any memory.


    Example 2: Memory Layout of Derived class
    Code:
    class Test 
    {
    public:
      int a;
      int b;
    };
    
    class dTest : public Test
    {
    public:
      int c;
    };
    
    int main() 
    {
      Test obj1;
      cout << "obj1's Size = " << sizeof(obj1) << endl;
      cout << "obj1's Address = " << &obj1 << endl;
      dTest obj2;
      cout << "obj2's Size = "<< sizeof(obj2) << endl;
      cout << "obj2's Address = "<< &obj2 << endl;
      return 0;
    }
    OUTPUT:
    obj1's Size = 8
    obj1's Address = 0012FF78
    obj2's Size = 12
    obj2's Address = 0012FF6C

    Example 3: Memory layout If we have one virtual function.
    Code:
    class Test 
    {
    public:
      int data;
      virtual void fun1() 
      { 
        cout << "Test::fun1" << endl; 
      }
    };
    
    int main() 
    {
      Test obj;
      cout << "obj's Size = " << sizeof(obj) << endl;
      cout << "obj's Address = " << &obj << endl;
      return 0;
    }
    OUTPUT:

    obj's Size = 8
    obj's Address = 0012FF7C

    Note: Adding one virtual function in a class takes 4 Byte extra.

    Example 4: More than one Virtual function
    Code:
    class Test 
    {
    public:
      int data;
      virtual void fun1() { cout << "Test::fun1" << endl; }
      virtual void fun2() { cout << "Test::fun2" << endl; }
      virtual void fun3() { cout << "Test::fun3" << endl; }
      virtual void fun4() { cout << "Test::fun4" << endl; }
    };
    
    int main()
     {
      Test obj;
      cout << "obj's Size = " << sizeof(obj) << endl;
      cout << "obj's Address = " << &obj << endl;
      return 0;
     }
    OUTPUT:

    obj's Size = 8
    obj's Address = 0012FF7C

    Note: Adding more virtual functions in a class, no extra size taking i.e. Only one machine size taking(i.e. 4 byte)

    Example 5:
    Code:
    class Test
     {
    public:
      int a;
      int b;
      Test(int temp1 = 0, int temp2 = 0)
      {
         a=temp1 ;
         b=temp2 ; 
      }
      int getA()  
      {
         return a;
      }
      int getB()  
      {
         return b;
      }
      virtual ~Test();
    };
    
    int main() 
    {
      Test obj(5, 10);
    
    // Changing a and b
      int* pInt = (int*)&obj;
      *(pInt+0) = 100;   
      *(pInt+1) = 200;   
    
      cout << "a = " << obj.getA() << endl;
      cout << "b = " << obj.getB() << endl;
      return 0;
    }
    OUTPUT:
    a = 200
    b = 10

    If we Change the code as then

    // Changing a and b
    int* pInt = (int*)&obj;
    *(pInt+1) = 100; // In place of 0
    *(pInt+2) = 200; // In place of 1

    OUTPUT:
    a = 100
    b = 200

    Note: Who sits 1st place of Class : Answer is VPTR
    VPTR - 1st placed in class and rest sits after it.



    Example 6:
    Code:
    class Test 
    {
      virtual void fun1() 
      {
         cout << "Test::fun1" << endl;
      }
    };
    
    int main() 
    {
      Test obj;
      cout << "VPTR's Address " << (int*)(&obj+0) << endl;
      cout << "VPTR's Value " << (int*)*(int*)(&obj+0) << endl;
      return 0;
    }
    OUTPUT:

    VPTR's Address 0012FF7C
    VPTR's Value 0046C060

    NOTE: This VPTR's value is a address of Virtual table. Lets see in next Example.

    Example 7:
    Code:
    #include <iostream>
    using namespace std;
    
    class Test
     {
       virtual void fun1() 
       { 
        cout << "Test::fun1" << endl; 
       }
    };
    typedef void (*Fun)(void);
    
    int main() 
    {
      Test obj;
      cout << "VPTR's Address " << (int*)(&obj+0) << endl;
      cout << " VIRTUAL TABLE 's Address " << (int*)*(int*)(&obj+0) << endl; // Value of VPTR
      cout << "Value at first entry of VIRTUAL TABLE " << (int*)*(int*)*(int*)(&obj+0) << endl;
      
      Fun pFun = (Fun)*(int*)*(int*)(&obj+0);   // calling Virtual function
      pFun();
      return 0;
    }
    OUTPUT:
    VPTR's Address 0012FF7C
    VIRTUAL TABLE 's Address 0046C0EC
    Value at first entry of VIRTUAL TABLE 0040100A
    Test: fun1


    Example 8:

    Code:
    class Test
    {
      virtual void fun1() { cout << "Test::fun1" << endl; }
      virtual void func1() { cout << "Test::func1" << endl; }
    };
    
    int main()
     {
      Test obj;
    
      cout << "VPTR's Address " << (int*)(&obj+0) << endl;
      cout << "VIRTUAL TABLE 's Address"<< (int*)*(int*)(&obj+0) << endl;
    
      // Calling Virtual table functions
      cout << "Value at 1st entry of VTable " << (int*)*((int*)*(int*)(&obj+0)+0) << endl;
      cout << "Value at 2nd entry of VTable " << (int*)*((int*)*(int*)(&obj+0)+1) << endl;
    
      return 0;
    }
    OUTPUT:

    VPTR's Address 0012FF7C
    VIRTUAL TABLE 's Address 0046C0EC
    Value at first entry of VIRTUAL TABLE 0040100A
    Value at 2nd entry of VIRTUAL TABLE 004012


    Example :9
    Code:
    class Test
    {
      virtual void fun1() { cout << "Test::fun1" << endl; }
      virtual void func1() { cout << "Test::func1" << endl; }
    };
    
    typedef void(*Fun)(void);
    
    int main() 
    {
      Test obj;
      Fun pFun = NULL;
      
      // calling 1st virtual function
      pFun = (Fun)*((int*)*(int*)(&obj+0)+0);
      pFun();
    
      // calling 2nd virtual function
      pFun = (Fun)*((int*)*(int*)(&obj+0)+1);
      pFun();
    
      return 0;
    }
    OUTPUT:

    Test::fun1
    Test::func1

    Example 10: multiple Inheritance
    Code:
    class Base1 
    {
    public:
      virtual void fun();
    };
    
    class Base2 
    {
    public:
      virtual void fun();
    };
    
    class Base3 
    {
    public:
      virtual void fun();
    };
    
    class Derive : public Base1, public Base2, public Base3 
    {
    };
    
    int main() 
    {
      Derive obj;
      cout << "Derive's Size = " << sizeof(obj) << endl;
      return 0;
    }
    OUTPUT:

    Derive's Size = 12


    Example 11: Calling Virtual Functions in case of Multiple Inheritance
    Code:
    class Base1 
    {
      virtual void fun1() { cout << "Base1::fun1()" << endl; }
      virtual void func1() { cout << "Base1::func1()" << endl; }
    };
    
    class Base2 {
      virtual void fun1() { cout << "Base2::fun1()" << endl; }
      virtual void func1() { cout << "Base2::func1()" << endl; }
    };
    
    class Base3 {
      virtual void fun1() { cout << "Base3::fun1()" << endl; }
      virtual void func1() { cout << "Base3::func1()" << endl; }
    };
    
    class Derive : public Base1, public Base2, public Base3 
    {
    public:
      virtual void Fn() 
      { 
      cout << "Derive::Fn" << endl; 
      }
      virtual void Fnc() 
      { 
      cout << "Derive::Fnc" << endl; 
      }
    };
    
    typedef void(*Fun)(void);
    
    int main()
    {
      Derive obj;
      Fun pFun = NULL;
      
      // calling 1st virtual function of Base1
      pFun = (Fun)*((int*)*(int*)((int*)&obj+0)+0);
      pFun();
    
      // calling 2nd virtual function of Base1
      pFun = (Fun)*((int*)*(int*)((int*)&obj+0)+1);
      pFun();
    
      // calling 1st virtual function of Base2
      pFun = (Fun)*((int*)*(int*)((int*)&obj+1)+0);
      pFun();
    
      // calling 2nd virtual function of Base2
      pFun = (Fun)*((int*)*(int*)((int*)&obj+1)+1);
      pFun();
    
      // calling 1st virtual function of Base3
      pFun = (Fun)*((int*)*(int*)((int*)&obj+2)+0);
      pFun();
    
      // calling 2nd virtual function of Base3
      pFun = (Fun)*((int*)*(int*)((int*)&obj+2)+1);
      pFun();
    
      // calling 1st virtual function of Drive
      pFun = (Fun)*((int*)*(int*)((int*)&obj+0)+2);
      pFun();
    
      // calling 2nd virtual function of Drive
      pFun = (Fun)*((int*)*(int*)((int*)&obj+0)+3);
      pFun();
    
      return 0;
    }
    OUTPUT:

    Base1::fun
    Base1::func
    Base2::fun
    Base2::func
    Base3::fun
    Base3::func
    Drive::Fn
    Drive::Fnc

  2. #2
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    You can only rely on the location of the vtable to be first in a particular compiler implementation. It is by far not guaranteed to be so [but it is a fairly common case].

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

  3. #3
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,893
    You can only rely on the existence of a vtable in a particular compiler implementation.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  4. #4
    Kernel hacker
    Join Date
    Jul 2007
    Location
    Farncombe, Surrey, England
    Posts
    15,677
    Quote Originally Posted by CornedBee View Post
    You can only rely on the existence of a vtable in a particular compiler implementation.
    Yes, it is of course impossible to implement virtual functions without some sort of indirection via the object, and a vtable at the first posible location is one choice that the implementor can use.

    --
    Mats
    Compilers can produce warnings - make the compiler programmers happy: Use them!
    Please don't PM me for help - and no, I don't do help over instant messengers.

Popular pages Recent additions subscribe to a feed

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21