The Inside of c++ Polymorphism

c + + polymorphism is the use of a secondary pointer (pointer array), each element in the array is pointed to, with virtual modified member functions.

Now that pointers are mentioned, let's prove them with memory addresses.

To prove this, we have to get the first address of the member function. Get the address of the member function by using the following function

template<typename dst_type,typename src_type>
dst_type pointer_cast(src_type src)
{
    return *static_cast<dst_type*>(static_cast<void*>(&src));
}

Method of calling the above function:

Void * P1 = pointer_cast < void *> (& class name:: member method name);

1. First, let's look at the memory layout of non-polymorphic member methods, through the following proof code:

#include "stdio.h"
#include "string.h"
#include <iostream>

template<typename dst_type,typename src_type>
dst_type pointer_cast(src_type src)
{
    return *static_cast<dst_type*>(static_cast<void*>(&src));
}

class A
{
    private:
        int a;
        char b;
        short c;
    public:
        A(int m,char n,short t)
        {
            a = m;b = n;c = t;
        }
   void funca(){
     std::cout << "A::func()" << std::endl;
   }
};
class B:public A
{
    private:
        char d;
    public:
        B(int m,char n,short t,char q):A(m,n,t)
        {
            d = q;
        }
  void funcb(){
     std::cout << "B::func()" << std::endl;
   }
};

int main()
{
    A x(1,2,3);
    B y(1,2,3,4);

    A* m = new B(1,2,3,4);
    void* p1 = pointer_cast<void*>(&A::funca);
    void* p2 = pointer_cast<void*>(&B::funcb);
    void(*a)() = (void(*)())p1;
    a();
    return 0;
}

The experimental environment: gcc version 7.3.0. Ubuntu 18.04

The following is a screenshot of GDB debugging

In step 1 of operation in gdb, [info line 22] command is used to get the address of the member function funca of class A. 22 is the line number of the member function funca.

Step 2 in gdb, because P1 is a pointer to the member function funca of class A, the result of [p1] is the address of the member function funca of class A.

Conclusion: According to the results of step 1 and step 2, the two addresses are identical.

What does it say? It shows that the address offset of non-polymorphic member functions is fixed in the compilation stage.

What is the address offset?

When running a program, the program is loaded into memory from the hard disk first, and the program becomes a running process. The operating system allocates virtual memory space to the process. In order to be able to call a function, the address of the virtual memory space in which the function exists must be known. How does the calling side know this address? In the compilation stage, the compiler automatically calculates the offset of the called function from the first address of the process and tells the calling test, so the calling side can find the address of the called function.

2. Let's look at the memory layout of the polymorphic membership method again, through the following proof code:

#include "stdio.h"
#include "string.h"
#include <iostream>

template<typename dst_type,typename src_type>
dst_type pointer_cast(src_type src)
{
    return *static_cast<dst_type*>(static_cast<void*>(&src));
}

class A
{
    private:
        int a;
        char b;
        short c;
    public:
        A(int m,char n,short t)
        {
            a = m;b = n;c = t;
        }
   virtual void func(){
     std::cout << "A::func()" << std::endl;
   }
   virtual void func1(){
     std::cout << "A::func1()" << std::endl;
   }
};
class B:public A
{
    private:
        char d;
    public:
        B(int m,char n,short t,char q):A(m,n,t)
        {
            d = q;
        }
  virtual void func(){
     std::cout << "B::func()" << std::endl;
   }
   virtual void func1(){
     std::cout << "B::func1()" << std::endl;
   }
};

int main()
{
    A x(1,2,3);
    B y(1,2,3,4);

    A* m = new B(1,2,3,4);
    void* p1 = pointer_cast<void*>(&A::func);
    void* p2 = pointer_cast<void*>(&B::func);
    m->func();//The func of B is called.
    m->func1();//The func1 of B is called.
    //void(*a)() = (void(*)())p1;
    //a();
    return 0;
}

The following is a screenshot of GDB debugging

The func member function of class A pointed to by pointer p1;

The func member function of class B pointed to by pointer p2;

However, according to the results of gdb, the addresses they point to are all 0x1, which means that they do not have the correct member functions pointing to classes.

Where is the address of the member function of the class? Look at the following screenshot of gdb

Looking at object x through [px], we find that there is an extra _vptr in X. This is the second-level pointer (pointer array) at the beginning.

Step 1: First use the info line 22 command to get the address of class A member function func.

Step 2: [p * (void **) 0x5555755d48] First convert the _vptr pointer to a void-type secondary pointer, and then use [*] to get the content of the address. It is found that the address class stores the address 0x55555555454cc4 of the member function func of class A.

Conclusion: _vptr points to the address of the first virtual function in all virtual functions.

The question arises. How can we get the second one? Because 64-bit system pointers occupy 8 bytes, the address of the second virtual function is (_vptr+8).

Now let the polymorphic truth come to the surface

When you have the following code: point to the object of the subclass with the pointer of the parent class (the ultimate goal of polymorphism: programming for the abstract class), and then call the functions of the subclass and the parent class that are exactly the same (must be virtual functions). It's confusing to know which one is calling.

A* m = new B(1,2,3,4);//B is a subclass of A.
m->func();
m->func1();

When calling a virtual function with the pointer of the parent class, first find _vptr in the memory it points to (the memory occupied by the subclass), and then find the address of the function from _vptr. The address of the non-virtual function is not in _vptr.

Step 1: [p *m] Find that M is an object of class A

Step 2: [set print object on], meaning to show the true type of the object

Step 3: [p *m] It is found that M is not an object of class A, but an object of class B.

Step 4: Look at the first pointer in _vptr and find that it points to the func of B. After adding 8, get the second pointer and find that it points to the func 1 of B.

The code that lets virtual functions store their addresses in _vptr must have been added to us by the compiler. Where did the code go?

Added to the constructor

c/c++ Learning Mutual Assistance QQ Group: 877684253

I am writing to xiaoshitou 5854

Tags: C++ Ubuntu Programming

Posted on Fri, 16 Aug 2019 02:42:09 -0700 by infyportalgroup