01从汇编的角度深入理解c++_类是什么、封装是什么?

C++是什么?

C++其实就是c加上编译器,c++能做的事情,c语言都能做。但是,C++的编译器比c的编译器背地里做了很多事情,因此c就变成了c++。

因此如果前期学过c,那么学习c++就是一件很容易的事,从编译器的角度理解c++,会理解c++中的各种概念封装、继承、多态、模板等概念到底是什么意思,知道是怎么回事。

本章先介绍类:

  c语言中是没有class是,但是有结构体。

1、普通的函数调用,结构体直接作为参数传递实验


#include "stdafx.h" struct Base{ int x; int y; }; void Function(Base b){ } int _tmain(int argc, _TCHAR* argv[]) { Base base; Function(base); getchar(); return 0; }

 查看一下反汇编代码,就知道Base结构体是什么int _tmain(int argc, _TCHAR* argv[]){

00C613F0 55                   push        ebp  
00C613F1 8B EC                mov         ebp,esp  
00C613F3 81 EC DC 00 00 00    sub         esp,0DCh  
00C613F9 53                   push        ebx  
00C613FA 56                   push        esi  
00C613FB 57                   push        edi  
00C613FC 8D BD 24 FF FF FF    lea         edi,[ebp-0DCh]  
00C61402 B9 37 00 00 00       mov         ecx,37h  
00C61407 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
00C6140C F3 AB                rep stos    dword ptr es:[edi]  
00C6140E C6 85 2B FF FF FF 00 mov         byte ptr [ebp-0D5h],0  
	Base base;
	
	Function(base);
00C61415 80 BD 2B FF FF FF 00 cmp         byte ptr [ebp-0D5h],0  
00C6141C 75 0D                jne         wmain+3Bh (0C6142Bh)  
00C6141E 68 88 14 C6 00       push        offset  (0C61488h)  
00C61423 E8 87 FC FF FF       call        @ILT+170(__RTC_UninitUse) (0C610AFh)  //没有初始化的时候会调用这个函数
00C61428 83 C4 04             add         esp,4  
00C6142B 8B 45 F8             mov         eax,dword ptr [ebp-8]  
00C6142E 50                   push        eax  
00C6142F 8B 4D F4             mov         ecx,dword ptr [ebp-0xC]
00C61432 51 push ecx
00C61433 E8 3B FC FF FF
call Function (0C61073h)
00C61438 83 C4 08 add esp,8

最主要的代码是:

 这里把结构体Base拷贝到eax和ecx中,eax和ecx作为函数的参数传入函数中。当函数参数少的时候可以通过这种方式传递,我们再添加一个变量z,再看一下这个参数传递方式:

 上图可以看到:三个局部变量传递到了[eax]

                  [eax+4]

                  [eax+8]中

如果不看源码,只能看出来是有3个局部变量传递传递到3个连续的空间,这个空间可能是数组也可能是结构体。为什么说是数组,因为这里是大小相同连续的空间,所以也可以看成是数组。

这个eax的值,正好是作为参数压入堆栈的位置。

 

从上边的源码也可以看出来,结构体的本质是什么?

结构体的本质其实就是变量打包连续放在一起。结构体作为局部变量在反汇编中和其他局部变量特征是一样的,反汇编中是分辨不出来的。

数组的本质其实就是类型相同的变量连续放在一起。

结构体直接作为函数参数、返回值的时候传递的时候是以内存复制的情况传递传递的,并且传递的是副本。

2、普通的函数调用,结构体指针作为参数传递情况:

#include "stdafx.h"

struct Base{
	int x;
	int y;
};
int FunctionMax(Base* b){
	
	if(b->x > b->y)
		return b->x;
}

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

	Base base;
	base.x=10;
	base.y=20;
	int a = FunctionMax(&base);
	printf("%d\n",a);
	getchar();
	return 0;
}

查看反汇编:

00F61430 55                   push        ebp  
00F61431 8B EC                mov         ebp,esp  
00F61433 81 EC DC 00 00 00    sub         esp,0DCh  
00F61439 53                   push        ebx  
00F6143A 56                   push        esi  
00F6143B 57                   push        edi  
00F6143C 8D BD 24 FF FF FF    lea         edi,[ebp+FFFFFF24h]  
00F61442 B9 37 00 00 00       mov         ecx,37h  
00F61447 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
00F6144C F3 AB                rep stos    dword ptr es:[edi]  

	Base base;
	base.x=10;
00F6144E C7 45 F4 0A 00 00 00 mov         dword ptr [ebp-0Ch],0Ah  
	base.y=20;
00F61455 C7 45 F8 14 00 00 00 mov         dword ptr [ebp-8],14h  
	int a = FunctionMax(&base);
00F6145C 8D 45 F4             lea         eax,[ebp-0Ch]  
00F6145F 50                   push        eax  
00F61460 E8 76 FD FF FF       call        00F611DB  
00F61465 83 C4 04             add         esp,4  

结构体地址作为参数的时候:先进行内存拷贝,2个mov,然后使用lea,把结构体的首地址压栈。

3、当函数调用的时候,不管是结构体直接作为函数 还是结构体指针,结构体大小都为8:

#include "stdafx.h"

struct Base{
	int x;
	int y;
};
int FunctionMax(Base* b){
	
	if(b->x > b->y)
		return b->x;
}

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

	Base base;
	base.x=10;
	base.y=20;
	printf("%d\n",sizeof(base));//大小为8
	getchar();
	return 0;
}

4、当函数放在结构体里面的时候,结构体大小仍为8,说明什么?

#include "stdafx.h"

struct Base{
	int x;
	int y;

	int FunctionMax(Base* b){
	
		if(b->x > b->y)
			return b->x;
	}

};

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

	Base base;
	base.x=10;
	base.y=20;
	printf("%d\n",sizeof(base));//大小仍然为8,说明什么?
	getchar();
	return 0;
}

说明函数放在结构体外边和里面,结构体大小都没有任何影响,说明函数没有放在结构体里面。

对于我们学习底层的人来说,函数放在哪里都一样,没有任何区别,但是对于编译器,得让编译器找到它,因此得用base.FunctionMax,而不是直接的直接使用FunctionMax调用,因此:

#include "stdafx.h"

struct Base{
	int x;
	int y;

	int FunctionMax(Base* b){
	
		if(b->x > b->y)
			return b->x;
	}

};

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

	Base base;
	base.x=10;
	base.y=20;
	base.FunctionMax(&base);

	printf("%d\n",sizeof(base));
	getchar();
	return 0;
}

函数,放在结构体里面调用的反汇编如下:

int _tmain(int argc, _TCHAR* argv[])
{
011A1400 55                   push        ebp  
011A1401 8B EC                mov         ebp,esp  
011A1403 81 EC D0 00 00 00    sub         esp,0D0h  
011A1409 53                   push        ebx  
011A140A 56                   push        esi  
011A140B 57                   push        edi  
011A140C 8D BD 30 FF FF FF    lea         edi,[ebp+FFFFFF30h]  
011A1412 B9 34 00 00 00       mov         ecx,34h  
011A1417 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
011A141C F3 AB                rep stos    dword ptr es:[edi]  

	Base base;
	base.x=10;
011A141E C7 45 F4 0A 00 00 00 mov         dword ptr [ebp-0Ch],0Ah  
	base.y=20;
011A1425 C7 45 F8 14 00 00 00 mov         dword ptr [ebp-8],14h  
	base.FunctionMax(&base);
011A142C 8D 45 F4             lea         eax,[ebp-0Ch]  
011A142F 50                   push        eax  
011A1430 8D 4D F4             lea         ecx,[ebp-0Ch]  
011A1433 E8 AD FD FF FF       call        011A11E5  

发现函数放在结构体里面的时候,函数调用变了,我们源码的参数是1个,现在变成2个参数了,多传了1个结构体对象的指针(this指针,因为我们实验传递的是结构体对象的地址)

因此当函数放在结构体里面的时候,编译器会为函数会多传1个参数,这个参数是什么呢?继续做实验:

无参数的函数放在结构体内部,函数调用情况:

#include "stdafx.h"

struct Base{
	int x;
	int y;
	int FunctionMax(){
if(x>y)
          return x;
       else
          return y;
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	Base base;
	base.x=10;
	base.y=20;
	base.FunctionMax();
	printf("%d\n",sizeof(base));
	getchar();
	return 0;
}

继续查看反汇编:

int _tmain(int argc, _TCHAR* argv[])
{
01371400 55                   push        ebp  
01371401 8B EC                mov         ebp,esp  
01371403 81 EC D0 00 00 00    sub         esp,0D0h  
01371409 53                   push        ebx  
0137140A 56                   push        esi  
0137140B 57                   push        edi  
0137140C 8D BD 30 FF FF FF    lea         edi,[ebp+FFFFFF30h]  
01371412 B9 34 00 00 00       mov         ecx,34h  
01371417 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
0137141C F3 AB                rep stos    dword ptr es:[edi]  

	Base base;
	base.x=10;
0137141E C7 45 F4 0A 00 00 00 mov         dword ptr [ebp-0Ch],0Ah  
	base.y=20;
01371425 C7 45 F8 14 00 00 00 mov         dword ptr [ebp-8],14h  
	base.FunctionMax();
0137142C 8D 4D F4             lea         ecx,[ebp-0Ch]  
0137142F E8 B6 FD FF FF       call        013711EA 

当我们没有参数传递的时候,会发现确实是多了1个参数,并且这个参数就是结构体对象地址(即this指针)。

我们进入call 013711EA 查看这个函数的内部实现:

int FunctionMax(){
00BC14B0 55                   push        ebp  
00BC14B1 8B EC                mov         ebp,esp  
00BC14B3 81 EC CC 00 00 00    sub         esp,0CCh  
00BC14B9 53                   push        ebx  
00BC14BA 56                   push        esi  
00BC14BB 57                   push        edi  
00BC14BC 51                   push        ecx  
00BC14BD 8D BD 34 FF FF FF    lea         edi,[ebp+FFFFFF34h]  
00BC14C3 B9 33 00 00 00       mov         ecx,33h  
00BC14C8 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
00BC14CD F3 AB                rep stos    dword ptr es:[edi]  
00BC14CF 59                   pop         ecx  
00BC14D0 89 4D F8             mov         dword ptr [ebp-8],ecx  
		if(x>y)
00BC14D3 8B 45 F8             mov         eax,dword ptr [ebp-8]  
00BC14D6 8B 4D F8             mov         ecx,dword ptr [ebp-8]  
00BC14D9 8B 10                mov         edx,dword ptr [eax]  
00BC14DB 3B 51 04             cmp         edx,dword ptr [ecx+4]  
00BC14DE 7E 09                jle         00BC14E9  
			return x;
00BC14E0 8B 45 F8             mov         eax,dword ptr [ebp-8]  
00BC14E3 8B 00                mov         eax,dword ptr [eax]  
00BC14E5 EB 08                jmp         00BC14EF  
		else
00BC14E7 EB 06                jmp         00BC14EF  
			return y;
00BC14E9 8B 45 F8             mov         eax,dword ptr [ebp-8]  
00BC14EC 8B 40 04             mov         eax,dword ptr [eax+4]  
	}
00BC14EF 5F                   pop         edi  
00BC14F0 5E                   pop         esi  
00BC14F1 5B                   pop         ebx  
00BC14F2 8B E5                mov         esp,ebp  
00BC14F4 5D                   pop         ebp  
00BC14F5 C3                   ret  

  函数内部多了1个push ecx和pop ecx,因为在提升堆栈的过程中要用到ecx作为计数器,而当ecx作为参数传递到函数内部的时候,传递的是结构体对象的指针,因此这里需要先push,然后pop。

而当比较x和y的时候,是通过ecx传递过来的指针来找到结构体内部的x和y。

 从这个函数放在结构体中的参数传递,我们应该能明白一点,编译器为我们自动传递了1个参数,所以c++的本质是什么?

  c语言是我们自己写代码,

  c++是编译器为我们写代码。

编译器为我们做了很多事情,我们只要理解了编译器做了哪些事情,那么c++就算学明白了。

概念:所以封装是什么?

    封装就是函数放在结构体里面,然后函数在调用的时候,编译器会为自动传递1个地址,这个地址有个专用的名字:this指针

    函数为什么要放在结构体里面,编译器会为我们自动传递结构对象的首地址,使用起来很方便。

              函数放在结构体里面,也有1个名字叫做成员函数。其实成员函数对于我们来说:和普通函数没有任何区别,但是对于编译器来说是有区别的:首先是使用的时候:结构体对象.成员函数,其次是调用的时候自动传递this指针。

    而结构体另外的一个名字就叫类(class)。

              结构体定义的变量就叫对象。

this指针主要的作用:

    1、区分局部变量和结构体成员变量

    2、返回当前对象的首地址

struct Base{
	int x;
	int y;

	int FunctionMax(int x,int y){
		if(this->x>this->y)
			return x;
		else
			return y;
	}
	int GetAddr(){
		return *((int*)this);
	}
};

  

posted @ 2023-09-04 17:48  一日学一日功  阅读(137)  评论(0)    收藏  举报