滴水逆向笔记系列-c++总结2-36.权限控制-37.虚函数-38.多态_绑定

第三十六课 c++3 权限控制

1.定义和实现分开写

image.pngimage.png

2.private和public

private权限说明

私有变量在类外是无法访问的,只有在类内或者使用类内函数访问
image.png

类内函数访问

image.png

3.private真的不能访问吗

反汇编看看
image.png
t对象在初始化public和private成员时都是一视同仁的,在底层还是没区别,都是编译器给的限制
image.png
image.png

4.class和struct的区别

class默认都是private权限,struct默认都是public权限
image.pngimage.png
class继承的一致默认所有都是private,父类public的成员继承过来也变成private权限
如果class Sub:public Base,那么也只能访问父类的public成员,private成员还是不行
image.png
子类把父类的private继承过去了,在同一个内存里,但是它只能在那摆着,编译器不允许访问,但是从底层调用指针可以访问
image.png

第三十七课 c++4 虚函数

1.虚函数:间接调用

#include <stdio.h>
class Base
{
public:
    void Function_1()
    {
        printf("Function_1...\n");
    }
    virtual void Function_2()
    {
        printf("Function_2...\n");
    }
};
int main()
{
    Base base;
    base.Function_1();
    base.Function_2();
}

image.pngimage.png

总结:

  • 使用对象调用虚函数和正常函数没有任何区别

  • 使用指针调用虚函数是间接call,调用正常函数的直接call

2.深入虚函数调用

我们可以发现在类中,如果使用了虚函数,不管使用了几个虚函数都只会多四个字节,我们现在需要探索一下这四个字节是什么
image.png
反汇编看一下间接调用了什么
image.png
调用两个虚函数时,他call edx+4就是第二个虚函数的地址
image.png

总结:

  • 当类中有虚函数时,不管有几个虚函数,类的最前面会多出四个字节

  • 这四个字节就是虚函数表的地址,虚函数表存放了类内所有虚函数的函数地址

3.打印虚函数表

#include <stdio.h>
class Base
{
public:
    int x;
    int y;
    virtual void Function_1()
    {
        printf("Function_1...\n");
    }
    virtual void Function_2()
    {
        printf("Function_2...\n");
    }
    virtual void Function_3()
    {
        printf("Function_3...\n");
    }
};

int main()
{
    //查看 Sub 的虚函数表				
    Base base;
    //对象的前四个字节就是虚函数表				
    printf("base 的虚函数表地址为:%x\n", *(int*)&base);
    //通过函数指针调用函数,验证正确性				
    typedef void(*pFunction)(void);
    pFunction pFn;
    for (int i = 0; i < 3; i++)
    {
        int temp = *((int*)(*(int*)&base) + i);
        pFn = (pFunction)temp;
        pFn();
    }
}

注意:

  • &base就是对象的首地址

  • (int)&base才是虚函数的地址

  • ((int)((int)&base))解引用出第一个虚函数的地址,然后使用函数指针去调用这个虚函数地址**

  • ((int)((int)&base) + i)解引用出第i个虚函数的地址

总结:

image.png

作业

1、单继承无函数覆盖

虚函数是会继承的
image.png

2、单继承有函数覆盖

输出后程序会崩溃,因为虚函数表只有4个函数地址,我打印了六个,第五个开始内存都是0,调用不了
image.png
image.png

3、多继承无函数覆盖

(多个直接父类时有多张虚表且子类虚函数在第一个虚表里)

struct Base1							
{							
public:							
    virtual void Fn_1()							
    {							
        printf("Base1:Fn_1...\n");							
    }							
    virtual void Fn_2()							
    {							
        printf("Base1:Fn_2...\n");							
    }							
};							
struct Base2							
{							
public:							
    virtual void Fn_3()							
    {							
        printf("Base2:Fn_3...\n");							
    }							
    virtual void Fn_4()							
    {							
        printf("Base2:Fn_4...\n");							
    }							
};							
struct Sub:Base1,Base2							
{							
public:							
    virtual void Fn_5()							
    {							
        printf("Sub:Fn_5...\n");							
    }							
    virtual void Fn_6()							
    {							
        printf("Sub:Fn_6...\n");							
    }							
};							
int main(int argc, char* argv[])							
{							
	//查看 Sub 的虚函数表						
    Sub sub;							
	//通过函数指针调用函数,验证正确性						
    typedef void(*pFunction)(void);											
	//对象的前四个字节是第一个Base1的虚表						
	printf("Sub 的虚函数表地址为:%x\n",*(int*)&sub);										
	pFunction pFn;									
	for(int i=0;i<6;i++)						
	{						
		int temp = *((int*)(*(int*)&sub)+i);					
		if(temp == 0)					
		{					
			break;				
		}					
		pFn = (pFunction)temp;					
		pFn();					
	}										
	//对象的第二个四字节是Base2的虚表						
	printf("Sub 的虚函数表地址为:%x\n",*(int*)((int)&sub+4));											
	pFunction pFn1;									
	for(int k=0;k<2;k++)						
	{						
		int temp = *((int*)(*(int*)((int)&sub+4))+k);					
		pFn1 = (pFunction)temp;					
		pFn1();					
	}												
}							

image.png

4、多继承有函数覆盖

两个父类的虚函数先放两张表里,然后子类虚函数覆盖上去

struct Base1			
{			
public:			
    virtual void Fn_1()			
    {			
        printf("Base1:Fn_1...\n");			
    }			
    virtual void Fn_2()			
    {			
        printf("Base1:Fn_2...\n");			
    }			
};			
struct Base2			
{			
public:			
    virtual void Fn_3()			
    {			
        printf("Base2:Fn_3...\n");			
    }			
    virtual void Fn_4()			
    {			
        printf("Base2:Fn_4...\n");			
    }			
};			
struct Sub:Base1,Base2			
{			
public:			
    virtual void Fn_1()			
    {			
        printf("Sub:Fn_1...\n");			
    }			
    virtual void Fn_3()			
    {			
        printf("Sub:Fn_3...\n");			
    }			
	virtual void Fn_5()		
    {			
        printf("Sub:Fn_5...\n");			
    }			
};			

image.png

5、多重继承无函数覆盖

image.png

6、多重继承有函数覆盖

后来的覆盖前面的

struct Base1			
{			
public:			
    virtual void Fn_1()			
    {			
        printf("Base1:Fn_1...\n");			
    }			
    virtual void Fn_2()			
    {			
        printf("Base1:Fn_2...\n");			
    }			
};			
struct Base2:Base1			
{			
public:			
    virtual void Fn_1()			
    {			
        printf("Base2:Fn_1...\n");			
    }			
    virtual void Fn_3()			
    {			
        printf("Base2:Fn_3...\n");			
    }			
};			
struct Sub:Base2			
{			
public:			
	virtual void Fn_5()		
    {			
        printf("Sub:Fn_5...\n");			
    }			
};			

image.png

struct Base1			
{			
public:			
    virtual void Fn_1()			
    {			
        printf("Base1:Fn_1...\n");			
    }			
    virtual void Fn_2()			
    {			
        printf("Base1:Fn_2...\n");			
    }			
};			
struct Base2:Base1			
{			
public:			
    virtual void Fn_1()			
    {			
        printf("Base2:Fn_1...\n");			
    }			
    virtual void Fn_3()			
    {			
        printf("Base2:Fn_3...\n");			
    }			
};			
struct Sub:Base2			
{			
public:			
	virtual void Fn_1()		
    {			
        printf("Sub:Fn_1...\n");			
    }			
	virtual void Fn_5()		
    {			
        printf("Sub:Fn_5...\n");			
    }			
};			

image.png

第三十七课 c++5 多态-绑定

1.什么是绑定

编译器绑定(前期绑定)

变量x和funciton_1就属于编译器绑定,一编译完调用普通成员变量的地方的变量地址已经确定了

动态绑定(后期绑定)

function_2就是动态绑定,编译完调用函数的地方还没确定地址,这里只是call虚表第一个函数中的值,只有运行时确定虚表第一个值是多少
image.png

多态例子一:

class Base				
{				
public:				
	int x;			
public:				
	Base()			
	{			
		x = 100;		
	}			
    void Function_1()				
    {				
        printf("Base:Function_1...\n");				
    }				
    virtual void Function_2()				
    {				
        printf("Base:Function_2...virtual\n");				
    }				
};				
class Sub:public Base				
{				
public:				
	int x;			
public:				
	Sub()			
	{			
		x = 200;		
	}			
    void Function_1()				
    {				
        printf("Sub:Function_1...\n");				
    }				
    virtual void Function_2()				
    {				
        printf("Sub:Function_2...virtual\n");				
    }				
};				
				
void TestBound(Base* pb)				
{				
	int n = pb->x;			
	printf("%x\n",n);			
				
	pb->Function_1();		//函数调用	
				
	pb->Function_2();			
}				
int main(int argc, char* argv[])				
{						
	return 0;			
}				

注意:
一直想不懂为什么传入sub对象时pb->x依然是100?

  • 因为是子类对象传入父类指针,现在子类对象继承了父类的变量,这就涉及到继承的父子类变量重名问题,子类对象里面照样把重名的变量继承过来,所以现在子类对象里有两个x变量的值,但是我们现在用父类指针去访问,所以访问出父类的变量x

多态例子二:(父类指针数组指向多个子类对象)

class Base
{
public:
	int x;
	int y;
public:
	Base()
	{
		x = 1;
		y = 2;
	}
	virtual void print()
	{
		printf("Base:%d %d \n",x,y);
	}
};
class Sub1 :public Base
{
public:
	int A;
public:
	Sub1()
	{
		x = 3;
		y = 4;
		A = 7;
	}
	virtual void print()
	{
		printf("Sub1: %d %d %d\n", x, y,A);
	}
};
class Sub2 :public Base
{
public:
	int B;
public:
	Sub2()
	{
		x = 5;
		y = 6;
		B = 8;
	}
	virtual void print()
	{
		printf("Sub2:%d %d %d\n", x, y,B);
	}
};

void TestBound()
{
	/*int n = pb->x;
	printf("%x\n", n);*/
	Base base;
	Sub1 sub1;
	Sub2 sub2;

	Base* arr[] = { &base,&sub1,&sub2 };
	for (int i = 0; i < 3; i++)
	{
		arr[i]->print();
	}
	
}
int main(int argc, char* argv[])
{
	TestBound();
	return 0;
}

这是输出结果
image.png

注意(重点):

问题一:为什么只输出两个数?(上面代码还没加virtual虚函数时)
因为我们没有使用虚函数,一直调用的是父类函数,父类函数我们只输出了x和y
image.png
问题二:修改后为什么这里会输出sub1和sub2各自的xy变量,例子一不是说父类指针调用父类的xy吗?
在两个sub对象中,由于我们没有重新声明x和y变量,所以在sub对象内存空间里只有x,y,A三个变量,而且在sub的构造函数中给x和y重新赋值了。然而在例子一中,我们在父子类都声明了变量x和y,所以子类内存空间里会有五个变量,(x,y,x,y,A),而后用父类指针访问自然就是打印了父类的变量x和y。
下面是两种情况的内存情况
image.png
image.png

2.有继承关系中的析构函数最好使用virtual

因为释放内存等操作时最好释放当前类的内存

3.总结

  • 一种类型能体现多种行为的,就叫多态,通过父类指针可以指向父类或者子类对象,就可能导致同一个虚函数function_2体现出不同的行为
  • 多态 = 动态绑定
  • c++的动态绑定是通过虚表实现的
posted @ 2024-03-16 10:46  小新07  阅读(17)  评论(0编辑  收藏  举报