03从汇编的角度深入理解c++_继承的本质是什么?

现在有这么1个项目:

要写几千个结构,每个结构里面都有年龄,性别这2个属性,还有一些其他不同的属性,那么该如何实现?

一种方案是:

struct Teacher{
	int age;
	int sex;
	int Add;
};

struct Student{
	int age;
	int sex;
	int Learn;
};
int _tmain(int argc, _TCHAR* argv[])
{
	
	Teacher tt;
	tt.Add = 1;
	tt.sex = 2;
	tt.age = 3;

	Student ss;
	ss.age = 22;
	ss.sex = 33;
	ss.Learn = 44;

	getchar();
	return 0;
}

 然后查看赋值部分对应的反汇编代码:

013213A0 55                   push        ebp  
013213A1 8B EC                mov         ebp,esp  
013213A3 81 EC E8 00 00 00    sub         esp,0E8h  
013213A9 53                   push        ebx  
013213AA 56                   push        esi  
013213AB 57                   push        edi  
013213AC 8D BD 18 FF FF FF    lea         edi,[ebp+FFFFFF18h]  
013213B2 B9 3A 00 00 00       mov         ecx,3Ah  
013213B7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
013213BC F3 AB                rep stos    dword ptr es:[edi]  
013213BE C7 45 F8 01 00 00 00 mov         dword ptr [ebp-8],1  
013213C5 C7 45 F4 02 00 00 00 mov         dword ptr [ebp-0Ch],2  
013213CC C7 45 F0 03 00 00 00 mov         dword ptr [ebp-10h],3  
013213D3 C7 45 DC 16 00 00 00 mov         dword ptr [ebp-24h],16h  
013213DA C7 45 E0 21 00 00 00 mov         dword ptr [ebp-20h],21h  
013213E1 C7 45 E4 2C 00 00 00 mov         dword ptr [ebp-1Ch],2Ch  

那有没有别的方案呢?使用继承,减少重复代码。

struct Base{
	int age;
	int sex;
};

struct Teacher:Base{
	int Add;
};
struct Student:Base{
	int Learn;
};

int _tmain(int argc, _TCHAR* argv[])
{
	
	Teacher tt;
	tt.Add = 1;
	tt.sex = 2;
	tt.age = 3;

	Student ss;
	ss.age = 22;
	ss.sex = 33;
	ss.Learn = 44;

	getchar();
	return 0;
}

 再次查看反汇编代码:

013E13A0 55                   push        ebp  
013E13A1 8B EC                mov         ebp,esp  
013E13A3 81 EC E8 00 00 00    sub         esp,0E8h  
013E13A9 53                   push        ebx  
013E13AA 56                   push        esi  
013E13AB 57                   push        edi  
013E13AC 8D BD 18 FF FF FF    lea         edi,[ebp+FFFFFF18h]  
013E13B2 B9 3A 00 00 00       mov         ecx,3Ah  
013E13B7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
013E13BC F3 AB                rep stos    dword ptr es:[edi]  
013E13BE C7 45 F8 01 00 00 00 mov         dword ptr [ebp-8],1  
013E13C5 C7 45 F4 02 00 00 00 mov         dword ptr [ebp-0Ch],2  
013E13CC C7 45 F0 03 00 00 00 mov         dword ptr [ebp-10h],3  
013E13D3 C7 45 DC 16 00 00 00 mov         dword ptr [ebp-24h],16h  
013E13DA C7 45 E0 21 00 00 00 mov         dword ptr [ebp-20h],21h  
013E13E1 C7 45 E4 2C 00 00 00 mov         dword ptr [ebp-1Ch],2Ch  

比较一下,2种方案的反汇编代码:

普通方法:
001B13CE C7 45 F8 01 00 00 00 mov         dword ptr [ebp-8],1  
001B13D5 C7 45 F4 02 00 00 00 mov         dword ptr [ebp-0Ch],2  
001B13DC C7 45 F0 03 00 00 00 mov         dword ptr [ebp-10h],3  
001B13E3 C7 45 DC 16 00 00 00 mov         dword ptr [ebp-24h],16h  
001B13EA C7 45 E0 21 00 00 00 mov         dword ptr [ebp-20h],21h  
001B13F1 C7 45 E4 2C 00 00 00 mov         dword ptr [ebp-1Ch],2Ch
使用继承之后的:
001B13CE C7 45 F8 01 00 00 00 mov         dword ptr [ebp-8],1  
001B13D5 C7 45 F4 02 00 00 00 mov         dword ptr [ebp-0Ch],2  
001B13DC C7 45 F0 03 00 00 00 mov         dword ptr [ebp-10h],3  
001B13E3 C7 45 DC 16 00 00 00 mov         dword ptr [ebp-24h],16h  
001B13EA C7 45 E0 21 00 00 00 mov         dword ptr [ebp-20h],21h  
001B13F1 C7 45 E4 2C 00 00 00 mov         dword ptr [ebp-1Ch],2Ch    

比较之后,发现2种方案,反汇编代码竟然完全一样,没有任何区别。

结合第2种方案的源码和反汇编,能看出来继承是什么呢?

  继承就是一种数据的复制,就这么简单,这就是继承的本质。

 因此在反汇编的角度看,继承其实就是下图,因此产生一种现象:父类的对象指针可以指向子类对象。

这个图一样要记住,记住这个图,就学会了继承,并且以后多态也要用到这个图。

#include "stdafx.h"
#include <Windows.h>


struct Base{
	int age;
	int sex;
};

struct Teacher:Base{
	int Add;
};
struct Student:Base{
	int Learn;
};

int _tmain(int argc, _TCHAR* argv[])
{
	Base mm;
	mm.age = 55;
	mm.sex = 66;

	Teacher tt;
	tt.Add = 1;
	tt.sex = 2;
	tt.age = 3;

	Student ss;
	ss.age = 22;
	ss.sex = 33;
	ss.Learn = 44;

	Base* pBase = &tt;			//父类对象的指针指向子类,编译的时候,编译器没有任何提示。
	printf("%d\n",pBase->age);
	printf("%d\n",pBase->sex);
	printf("%d\n",pBase->Add);	//这里编译器会报错,提示没有Add成员(真的无法访问吗?学习底层的人来说,很简单,用指针都能访问。)

	getchar();
	return 0;
}

 根据上图,就可以知道,为什么父类对象的指针指向子类。

当然根据上图,我们也可以子类对象的指针指向父类,因此其实的地址是一样的,但是这个时候编译器会提示我们:编译器提示无法从“Base *”转换为“Teacher *”,很简单,强制转就行了。

struct Base{
	int age;
	int sex;
};

struct Teacher:Base{
	int Add;
};
struct Student:Base{
	int Learn;
};



int _tmain(int argc, _TCHAR* argv[])
{
	Base mm;
	mm.age = 55;
	mm.sex = 66;

	Teacher tt;
	tt.Add = 1;
	tt.sex = 2;
	tt.age = 3;

	Student ss;
	ss.age = 22;
	ss.sex = 33;
	ss.Learn = 44;

	Teacher* pt =(Teacher*)&mm;		//子类对象的指针指向父类,编译器提示无法从“Base *”转换为“Teacher *”
	printf("%d\n",pt->age);
	printf("%d\n",pt->sex);
	printf("%d\n",pt->Add);			//打印输出-858993460,为什么?

	getchar();
	return 0;
}

 但是我们打印输出的 pt->Add却是1个随机值,这是为什么?因为父类根本就没有pt->Add,我们是强制把他转换为子类了,所以才能输出。

子类对象的指针可以指向父类,但是不要这样做,为什么?

  因为当1个大指针指向了一个小对象,那么当这个大指针指向的部分超过小对象的时候,编译器不会报错,这样就容易产生1个逻辑错误,调bug的时候,很难调出来。

  但是当父类的指针指向子类对象的时候,小指针指向大对象,当小指针超出他所定义的范围的时候,编译器直接报错,这样不容易产生bug。

明白了继承之后,下面说一下:多层继承和多重

1、多层继承

struct X{
	int a;
	int b;
};

struct Y:X{
	int c;
	int d;
};
struct Z:Y{
	int e;
	int f;
};

void Test(){
	Z z;
	z.a = 11;
	z.b = 22;
	z.c = 33;
	z.d = 44;
	z.e = 55;
	z.f = 66;

	X* xx = &z;    //父类指针指向子类
	xx->a = 77;

	Y* yy = &z;    //父类指针指向子类
	yy->d = 88;

	printf("%d\n",sizeof(z));//24
}

int _tmain(int argc, _TCHAR* argv[])
{
	Test();

	getchar();
	return 0;
}

反汇编代码如下:

00183760 55                   push        ebp  
00183761 8B EC                mov         ebp,esp  
00183763 81 EC F8 00 00 00    sub         esp,0F8h  
00183769 53                   push        ebx  
0018376A 56                   push        esi  
0018376B 57                   push        edi  
0018376C 8D BD 08 FF FF FF    lea         edi,[ebp+FFFFFF08h]  
00183772 B9 3E 00 00 00       mov         ecx,3Eh  
00183777 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
0018377C F3 AB                rep stos    dword ptr es:[edi]  
0018377E C7 45 E4 0B 00 00 00 mov         dword ptr [ebp-1Ch],0Bh  
00183785 C7 45 E8 16 00 00 00 mov         dword ptr [ebp-18h],16h  
0018378C C7 45 EC 21 00 00 00 mov         dword ptr [ebp-14h],21h  
00183793 C7 45 F0 2C 00 00 00 mov         dword ptr [ebp-10h],2Ch  
0018379A C7 45 F4 37 00 00 00 mov         dword ptr [ebp-0Ch],37h  
001837A1 C7 45 F8 42 00 00 00 mov         dword ptr [ebp-8],42h  

 如果X、Y、Z里面有重复的值呢?

struct X{
	int a;
	int b;
};

struct Y:X{
	int a;
	int d;
};
struct Z:Y{
	int d;
	int f;
};

void Test(){
	Z z;
	z.a = 11;
	z.b = 22;
	z.a = 33;
	z.Y::d = 44;
	z.Z::d = 55;
	z.f = 66;

	X* xx = &z;
	xx->a = 77;

	Y* yy = &z;
	yy->d = 88;

	printf("%d\n",sizeof(z));//24
	printf("%d,%d\n",z.a,z.Y::a);
	printf("%d,%d\n",z.Y::d,z.d);
}

int _tmain(int argc, _TCHAR* argv[])
{
	Test();

	getchar();
	return 0;
}

 查看反汇编:

00C913DC F3 AB                rep stos    dword ptr es:[edi]  
00C913DE C7 45 EC 0B 00 00 00 mov         dword ptr [ebp-14h],0Bh      //z.a
00C913E5 C7 45 E8 16 00 00 00 mov         dword ptr [ebp-18h],16h  
00C913EC C7 45 EC 21 00 00 00 mov         dword ptr [ebp-14h],21h      //z.a编译器没有分清楚
00C913F3 C7 45 F0 2C 00 00 00 mov         dword ptr [ebp-10h],2Ch  
00C913FA C7 45 F4 37 00 00 00 mov         dword ptr [ebp-0Ch],37h  
00C91401 C7 45 F8 42 00 00 00 mov         dword ptr [ebp-8],42h  

 我们从反汇编代码中看出来什么?

  1、继承就是数据的复制

  2、当继承的时候,有重复的值的时候得告诉编译器用的哪个类里面的

z.X::a = 11;
z.b = 22;
z.Y::a = 33;
z.Y::d = 44;
z.Z::d = 55;
z.f = 66;

推荐使用多层继承。

2、多重继承:

struct X{
	int a;
	int b;
};

struct Y{
	int a;
	int d;
};
struct Z:X,Y{
	int d;
	int f;
};

void Test(){
	Z z;

	z.X::a = 11;
	z.b = 22;	
	z.Y::a=33;
	z.Y::d=44;
	z.Z::d=55;
	z.f=66;

	printf("%d\n",sizeof(z));//24

}

int _tmain(int argc, _TCHAR* argv[])
{
	Test();

	getchar();
	return 0;
}

查看反汇编代码:

00E313C0 55                   push        ebp  
00E313C1 8B EC                mov         ebp,esp  
00E313C3 81 EC E0 00 00 00    sub         esp,0E0h  
00E313C9 53                   push        ebx  
00E313CA 56                   push        esi  
00E313CB 57                   push        edi  
00E313CC 8D BD 20 FF FF FF    lea         edi,[ebp+FFFFFF20h]  
00E313D2 B9 38 00 00 00       mov         ecx,38h  
00E313D7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
00E313DC F3 AB                rep stos    dword ptr es:[edi]  
00E313DE C7 45 E4 0B 00 00 00 mov         dword ptr [ebp-1Ch],0Bh  
00E313E5 C7 45 E8 16 00 00 00 mov         dword ptr [ebp-18h],16h  
00E313EC C7 45 EC 21 00 00 00 mov         dword ptr [ebp-14h],21h  
00E313F3 C7 45 F0 2C 00 00 00 mov         dword ptr [ebp-10h],2Ch  
00E313FA C7 45 F4 37 00 00 00 mov         dword ptr [ebp-0Ch],37h  
00E31401 C7 45 F8 42 00 00 00 mov         dword ptr [ebp-8],42h  

 发现生成的代码还是这样,因此只要告诉编译器,哪个变量属于哪个,编译器就能正常识别。

 多重继承内存图是:

 不推荐这种方式,因为这种多重继承的时候,编译器要维护的指针会比较多,我们编码的时候,容易出错。

posted @ 2023-09-05 17:55  一日学一日功  阅读(69)  评论(0)    收藏  举报